annotate core.js @ 601:0df685136c89

Fixed null/period saves on test completion. specification projectReturn is currently deprecated pending new usage.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Thu, 10 Mar 2016 11:49:08 +0000
parents d1d7b49fbeea
children 6531e3903ea1
rev   line source
nicholas@1 1 /**
nicholas@1 2 * core.js
nicholas@1 3 *
nicholas@1 4 * Main script to run, calls all other core functions and manages loading/store to backend.
nicholas@1 5 * Also contains all global variables.
nicholas@1 6 */
nicholas@1 7
nicholas@1 8 /* create the web audio API context and store in audioContext*/
n@33 9 var audioContext; // Hold the browser web audio API
n@33 10 var projectXML; // Hold the parsed setup XML
n@453 11 var schemaXSD; // Hold the parsed schema XSD
n@181 12 var specification;
n@182 13 var interfaceContext;
n@453 14 var storage;
nicholas@116 15 var popup; // Hold the interfacePopup object
nicholas@129 16 var testState;
n@45 17 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
n@33 18 var audioEngineContext; // The custome AudioEngine object
n@33 19 var projectReturn; // Hold the URL for the return
n@153 20
nicholas@1 21
n@57 22 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
n@57 23 AudioBufferSourceNode.prototype.owner = undefined;
n@527 24 // Add a prototype to the bufferSourceNode to hold when the object was given a play command
n@527 25 AudioBufferSourceNode.prototype.playbackStartTime = undefined;
n@408 26 // Add a prototype to the bufferNode to hold the desired LINEAR gain
n@448 27 AudioBuffer.prototype.playbackGain = undefined;
n@408 28 // Add a prototype to the bufferNode to hold the computed LUFS loudness
n@408 29 AudioBuffer.prototype.lufs = undefined;
n@57 30
n@477 31 // Firefox does not have an XMLDocument.prototype.getElementsByName
n@477 32 // and there is no searchAll style command, this custom function will
n@477 33 // search all children recusrively for the name. Used for XSD where all
n@477 34 // element nodes must have a name and therefore can pull the schema node
n@477 35 XMLDocument.prototype.getAllElementsByName = function(name)
n@477 36 {
n@477 37 name = String(name);
n@477 38 var selected = this.documentElement.getAllElementsByName(name);
n@477 39 return selected;
n@477 40 }
n@477 41
n@477 42 Element.prototype.getAllElementsByName = function(name)
n@477 43 {
n@477 44 name = String(name);
n@477 45 var selected = [];
n@477 46 var node = this.firstElementChild;
n@477 47 while(node != null)
n@477 48 {
n@477 49 if (node.getAttribute('name') == name)
n@477 50 {
n@477 51 selected.push(node);
n@477 52 }
n@477 53 if (node.childElementCount > 0)
n@477 54 {
n@477 55 selected = selected.concat(node.getAllElementsByName(name));
n@477 56 }
n@477 57 node = node.nextElementSibling;
n@477 58 }
n@477 59 return selected;
n@477 60 }
n@477 61
n@477 62 XMLDocument.prototype.getAllElementsByTagName = function(name)
n@477 63 {
n@477 64 name = String(name);
n@477 65 var selected = this.documentElement.getAllElementsByTagName(name);
n@477 66 return selected;
n@477 67 }
n@477 68
n@477 69 Element.prototype.getAllElementsByTagName = function(name)
n@477 70 {
n@477 71 name = String(name);
n@477 72 var selected = [];
n@477 73 var node = this.firstElementChild;
n@477 74 while(node != null)
n@477 75 {
n@477 76 if (node.nodeName == name)
n@477 77 {
n@477 78 selected.push(node);
n@477 79 }
n@477 80 if (node.childElementCount > 0)
n@477 81 {
n@477 82 selected = selected.concat(node.getAllElementsByTagName(name));
n@477 83 }
n@477 84 node = node.nextElementSibling;
n@477 85 }
n@477 86 return selected;
n@477 87 }
n@477 88
n@477 89 // Firefox does not have an XMLDocument.prototype.getElementsByName
n@477 90 if (typeof XMLDocument.prototype.getElementsByName != "function") {
n@477 91 XMLDocument.prototype.getElementsByName = function(name)
n@477 92 {
n@477 93 name = String(name);
n@477 94 var node = this.documentElement.firstElementChild;
n@477 95 var selected = [];
n@477 96 while(node != null)
n@477 97 {
n@477 98 if (node.getAttribute('name') == name)
n@477 99 {
n@477 100 selected.push(node);
n@477 101 }
n@477 102 node = node.nextElementSibling;
n@477 103 }
n@477 104 return selected;
n@477 105 }
n@477 106 }
n@477 107
nicholas@1 108 window.onload = function() {
nicholas@1 109 // Function called once the browser has loaded all files.
nicholas@1 110 // This should perform any initial commands such as structure / loading documents
nicholas@1 111
nicholas@1 112 // Create a web audio API context
nicholas@21 113 // Fixed for cross-browser support
nicholas@21 114 var AudioContext = window.AudioContext || window.webkitAudioContext;
nicholas@7 115 audioContext = new AudioContext;
nicholas@1 116
nicholas@129 117 // Create test state
nicholas@129 118 testState = new stateMachine();
nicholas@129 119
nicholas@116 120 // Create the popup interface object
nicholas@116 121 popup = new interfacePopup();
n@501 122
n@501 123 // Create the specification object
n@181 124 specification = new Specification();
n@182 125
n@182 126 // Create the interface object
n@182 127 interfaceContext = new Interface(specification);
n@453 128
n@453 129 // Create the storage object
n@453 130 storage = new Storage();
n@379 131 // Define window callbacks for interface
n@379 132 window.onresize = function(event){interfaceContext.resizeWindow(event);};
n@16 133 };
nicholas@1 134
n@377 135 function loadProjectSpec(url) {
n@377 136 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
n@377 137 // If url is null, request client to upload project XML document
n@453 138 var xmlhttp = new XMLHttpRequest();
n@453 139 xmlhttp.open("GET",'test-schema.xsd',true);
n@453 140 xmlhttp.onload = function()
n@453 141 {
n@453 142 schemaXSD = xmlhttp.response;
n@453 143 var parse = new DOMParser();
n@453 144 specification.schema = parse.parseFromString(xmlhttp.response,'text/xml');
n@453 145 var r = new XMLHttpRequest();
n@453 146 r.open('GET',url,true);
n@453 147 r.onload = function() {
n@453 148 loadProjectSpecCallback(r.response);
n@453 149 };
n@543 150 r.onerror = function() {
n@543 151 document.getElementsByTagName('body')[0].innerHTML = null;
n@543 152 var msg = document.createElement("h3");
n@543 153 msg.textContent = "FATAL ERROR";
n@543 154 var span = document.createElement("p");
n@543 155 span.textContent = "There was an error when loading your XML file. Please check your path in the URL. After the path to this page, there should be '?url=path/to/your/file.xml'. Check the spelling of your filename as well. If you are still having issues, check the log of the python server or your webserver distribution for 404 codes for your file.";
n@543 156 document.getElementsByTagName('body')[0].appendChild(msg);
n@543 157 document.getElementsByTagName('body')[0].appendChild(span);
n@543 158 }
n@453 159 r.send();
n@377 160 };
n@453 161 xmlhttp.send();
n@377 162 };
n@377 163
n@377 164 function loadProjectSpecCallback(response) {
n@377 165 // Function called after asynchronous download of XML project specification
n@377 166 //var decode = $.parseXML(response);
n@377 167 //projectXML = $(decode);
n@377 168
n@584 169 // Check if XML is new or a resumption
n@584 170 var parse = new DOMParser();
n@584 171 var responseDocument = parse.parseFromString(response,'text/xml');
n@584 172 var errorNode = responseDocument.getElementsByTagName('parsererror');
n@430 173 if (errorNode.length >= 1)
n@430 174 {
n@430 175 var msg = document.createElement("h3");
n@430 176 msg.textContent = "FATAL ERROR";
n@430 177 var span = document.createElement("span");
n@430 178 span.textContent = "The XML parser returned the following errors when decoding your XML file";
n@433 179 document.getElementsByTagName('body')[0].innerHTML = null;
n@430 180 document.getElementsByTagName('body')[0].appendChild(msg);
n@430 181 document.getElementsByTagName('body')[0].appendChild(span);
n@430 182 document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
n@430 183 return;
n@430 184 }
n@584 185 if (responseDocument.children[0].nodeName == "waet") {
n@584 186 // document is a specification
n@584 187
n@584 188 // Perform XML schema validation
n@584 189 var Module = {
n@584 190 xml: response,
n@584 191 schema: schemaXSD,
n@584 192 arguments:["--noout", "--schema", 'test-schema.xsd','document.xml']
n@584 193 };
n@584 194 projectXML = responseDocument;
n@584 195 var xmllint = validateXML(Module);
n@584 196 console.log(xmllint);
n@584 197 if(xmllint != 'document.xml validates\n')
n@584 198 {
n@584 199 document.getElementsByTagName('body')[0].innerHTML = null;
n@584 200 var msg = document.createElement("h3");
n@584 201 msg.textContent = "FATAL ERROR";
n@584 202 var span = document.createElement("h4");
n@584 203 span.textContent = "The XML validator returned the following errors when decoding your XML file";
n@584 204 document.getElementsByTagName('body')[0].appendChild(msg);
n@584 205 document.getElementsByTagName('body')[0].appendChild(span);
n@584 206 xmllint = xmllint.split('\n');
n@584 207 for (var i in xmllint)
n@584 208 {
n@584 209 document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
n@584 210 var span = document.createElement("span");
n@584 211 span.textContent = xmllint[i];
n@584 212 document.getElementsByTagName('body')[0].appendChild(span);
n@584 213 }
n@584 214 return;
n@584 215 }
n@587 216 // Build the specification
n@587 217 specification.decode(projectXML);
n@584 218 // Generate the session-key
n@584 219 storage.initialise();
n@584 220
n@584 221 } else if (responseDocument.children[0].nodeName == "waetresult") {
n@584 222 // document is a result
n@584 223 projectXML = responseDocument.getElementsByTagName('waet')[0];
n@587 224 // Build the specification
n@587 225 specification.decode(projectXML);
n@584 226 // Use the session-key
n@584 227 var sessionKey = responseDocument.children[0].getAttribute(key);
n@584 228 storage.initialise(sessionKey);
n@584 229 }
n@468 230 /// CHECK FOR SAMPLE RATE COMPATIBILITY
n@468 231 if (specification.sampleRate != undefined) {
n@468 232 if (Number(specification.sampleRate) != audioContext.sampleRate) {
n@468 233 var errStr = 'Sample rates do not match! Requested '+Number(specification.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
n@468 234 alert(errStr);
n@468 235 return;
n@468 236 }
n@468 237 }
n@377 238
n@377 239 // Detect the interface to use and load the relevant javascripts.
n@377 240 var interfaceJS = document.createElement('script');
n@377 241 interfaceJS.setAttribute("type","text/javascript");
n@458 242 switch(specification.interface)
n@458 243 {
n@458 244 case "APE":
n@470 245 interfaceJS.setAttribute("src","interfaces/ape.js");
n@377 246
n@377 247 // APE comes with a css file
n@377 248 var css = document.createElement('link');
n@377 249 css.rel = 'stylesheet';
n@377 250 css.type = 'text/css';
n@470 251 css.href = 'interfaces/ape.css';
n@377 252
n@377 253 document.getElementsByTagName("head")[0].appendChild(css);
n@458 254 break;
n@458 255
n@458 256 case "MUSHRA":
n@470 257 interfaceJS.setAttribute("src","interfaces/mushra.js");
n@377 258
n@377 259 // MUSHRA comes with a css file
n@377 260 var css = document.createElement('link');
n@377 261 css.rel = 'stylesheet';
n@377 262 css.type = 'text/css';
n@470 263 css.href = 'interfaces/mushra.css';
n@377 264
n@377 265 document.getElementsByTagName("head")[0].appendChild(css);
n@458 266 break;
n@458 267
n@458 268 case "AB":
n@470 269 interfaceJS.setAttribute("src","interfaces/AB.js");
n@458 270
n@458 271 // AB comes with a css file
n@458 272 var css = document.createElement('link');
n@458 273 css.rel = 'stylesheet';
n@458 274 css.type = 'text/css';
n@470 275 css.href = 'interfaces/AB.css';
n@458 276
n@458 277 document.getElementsByTagName("head")[0].appendChild(css);
n@474 278 break;
n@474 279 case "Bipolar":
n@474 280 case "ACR":
n@474 281 case "DCR":
n@474 282 case "CCR":
n@472 283 case "ABC":
n@472 284 // Above enumerate to horizontal sliders
n@472 285 interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js");
n@472 286
n@472 287 // horizontal-sliders comes with a css file
n@472 288 var css = document.createElement('link');
n@472 289 css.rel = 'stylesheet';
n@472 290 css.type = 'text/css';
n@472 291 css.href = 'interfaces/horizontal-sliders.css';
n@472 292
n@472 293 document.getElementsByTagName("head")[0].appendChild(css);
n@474 294 break;
n@474 295 case "discrete":
n@474 296 case "likert":
n@474 297 // Above enumerate to horizontal discrete radios
n@474 298 interfaceJS.setAttribute("src","interfaces/discrete.js");
n@474 299
n@474 300 // horizontal-sliders comes with a css file
n@474 301 var css = document.createElement('link');
n@474 302 css.rel = 'stylesheet';
n@474 303 css.type = 'text/css';
n@474 304 css.href = 'interfaces/discrete.css';
n@474 305
n@474 306 document.getElementsByTagName("head")[0].appendChild(css);
n@474 307 break;
n@377 308 }
n@377 309 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
n@377 310
n@379 311 // Create the audio engine object
n@379 312 audioEngineContext = new AudioEngine(specification);
n@377 313 }
n@377 314
n@377 315 function createProjectSave(destURL) {
n@377 316 // Save the data from interface into XML and send to destURL
n@377 317 // If destURL is null then download XML in client
n@377 318 // Now time to render file locally
n@377 319 var xmlDoc = interfaceXMLSave();
n@377 320 var parent = document.createElement("div");
n@377 321 parent.appendChild(xmlDoc);
n@377 322 var file = [parent.innerHTML];
n@589 323 if (destURL == "local") {
n@377 324 var bb = new Blob(file,{type : 'application/xml'});
n@377 325 var dnlk = window.URL.createObjectURL(bb);
n@377 326 var a = document.createElement("a");
n@377 327 a.hidden = '';
n@377 328 a.href = dnlk;
n@377 329 a.download = "save.xml";
n@377 330 a.textContent = "Save File";
n@377 331
n@377 332 popup.showPopup();
n@461 333 popup.popupContent.innerHTML = "</span>Please save the file below to give to your test supervisor</span><br>";
n@377 334 popup.popupContent.appendChild(a);
n@377 335 } else {
n@377 336 var xmlhttp = new XMLHttpRequest;
n@601 337 xmlhttp.open("POST","\save.php?key="+storage.SessionKey.key,true);
n@377 338 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
n@377 339 xmlhttp.onerror = function(){
n@377 340 console.log('Error saving file to server! Presenting download locally');
n@601 341 createProjectSave("local");
n@377 342 };
n@589 343 xmlhttp.onload = function() {
n@589 344 console.log(xmlhttp);
n@589 345 if (this.status >= 300) {
n@589 346 console.log("WARNING - Could not update at this time");
n@601 347 createProjectSave("local");
n@589 348 } else {
n@589 349 var parser = new DOMParser();
n@589 350 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
n@589 351 var response = xmlDoc.getElementsByTagName('response')[0];
n@589 352 if (response.getAttribute("state") == "OK") {
n@589 353 var file = response.getElementsByTagName("file")[0];
n@589 354 console.log("Save: OK, written "+file.getAttribute("bytes")+"B");
n@596 355 popup.popupContent.textContent = "Thank you. Your session has been saved.";
n@589 356 } else {
n@589 357 var message = response.getElementsByTagName("message");
n@589 358 console.log("Save: Error! "+message.textContent);
n@589 359 createProjectSave("local");
n@589 360 }
n@589 361 }
n@589 362 };
n@377 363 xmlhttp.send(file);
n@461 364 popup.showPopup();
n@461 365 popup.popupContent.innerHTML = null;
n@461 366 popup.popupContent.textContent = "Submitting. Please Wait";
n@539 367 popup.hideNextButton();
n@539 368 popup.hidePreviousButton();
n@377 369 }
n@377 370 }
n@377 371
n@377 372 function errorSessionDump(msg){
n@377 373 // Create the partial interface XML save
n@377 374 // Include error node with message on why the dump occured
n@430 375 popup.showPopup();
n@430 376 popup.popupContent.innerHTML = null;
n@430 377 var err = document.createElement('error');
n@430 378 var parent = document.createElement("div");
n@430 379 if (typeof msg === "object")
n@430 380 {
n@430 381 err.appendChild(msg);
n@430 382 popup.popupContent.appendChild(msg);
n@430 383
n@430 384 } else {
n@430 385 err.textContent = msg;
n@430 386 popup.popupContent.innerHTML = "ERROR : "+msg;
n@430 387 }
n@377 388 var xmlDoc = interfaceXMLSave();
n@377 389 xmlDoc.appendChild(err);
n@377 390 parent.appendChild(xmlDoc);
n@377 391 var file = [parent.innerHTML];
n@377 392 var bb = new Blob(file,{type : 'application/xml'});
n@377 393 var dnlk = window.URL.createObjectURL(bb);
n@377 394 var a = document.createElement("a");
n@377 395 a.hidden = '';
n@377 396 a.href = dnlk;
n@377 397 a.download = "save.xml";
n@377 398 a.textContent = "Save File";
n@377 399
n@430 400
n@430 401
n@377 402 popup.popupContent.appendChild(a);
n@377 403 }
n@377 404
n@377 405 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
n@377 406 function interfaceXMLSave(){
n@377 407 // Create the XML string to be exported with results
n@453 408 return storage.finish();
n@377 409 }
n@377 410
n@400 411 function linearToDecibel(gain)
n@400 412 {
n@400 413 return 20.0*Math.log10(gain);
n@400 414 }
n@400 415
n@400 416 function decibelToLinear(gain)
n@400 417 {
n@400 418 return Math.pow(10,gain/20.0);
n@400 419 }
n@400 420
n@584 421 function randomString(length) {
n@584 422 return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
n@584 423 }
n@584 424
nicholas@116 425 function interfacePopup() {
nicholas@116 426 // Creates an object to manage the popup
nicholas@116 427 this.popup = null;
nicholas@116 428 this.popupContent = null;
n@303 429 this.popupTitle = null;
n@303 430 this.popupResponse = null;
n@197 431 this.buttonProceed = null;
n@199 432 this.buttonPrevious = null;
nicholas@116 433 this.popupOptions = null;
nicholas@116 434 this.currentIndex = null;
n@453 435 this.node = null;
n@453 436 this.store = null;
n@396 437 $(window).keypress(function(e){
n@396 438 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
n@396 439 {
n@396 440 console.log(e);
n@396 441 popup.buttonProceed.onclick();
n@398 442 e.preventDefault();
n@396 443 }
n@396 444 });
n@181 445
nicholas@116 446 this.createPopup = function(){
nicholas@116 447 // Create popup window interface
nicholas@116 448 var insertPoint = document.getElementById("topLevelBody");
nicholas@116 449
n@510 450 this.popup = document.getElementById('popupHolder');
nicholas@116 451 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
nicholas@116 452 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
nicholas@116 453
n@510 454 this.popupContent = document.getElementById('popupContent');
nicholas@116 455
n@510 456 this.popupTitle = document.getElementById('popupTitle');
n@303 457
n@510 458 this.popupResponse = document.getElementById('popupResponse');
n@303 459
n@510 460 this.buttonProceed = document.getElementById('popup-proceed');
n@197 461 this.buttonProceed.onclick = function(){popup.proceedClicked();};
n@199 462
n@510 463 this.buttonPrevious = document.getElementById('popup-previous');
n@199 464 this.buttonPrevious.onclick = function(){popup.previousClick();};
n@199 465
n@510 466 this.hidePopup();
n@510 467
n@181 468 this.popup.style.zIndex = -1;
n@181 469 this.popup.style.visibility = 'hidden';
nicholas@116 470 };
nicholas@114 471
nicholas@116 472 this.showPopup = function(){
n@181 473 if (this.popup == null) {
nicholas@116 474 this.createPopup();
nicholas@116 475 }
nicholas@116 476 this.popup.style.zIndex = 3;
nicholas@116 477 this.popup.style.visibility = 'visible';
nicholas@116 478 var blank = document.getElementsByClassName('testHalt')[0];
nicholas@116 479 blank.style.zIndex = 2;
nicholas@116 480 blank.style.visibility = 'visible';
nicholas@116 481 };
nicholas@116 482
nicholas@116 483 this.hidePopup = function(){
nicholas@116 484 this.popup.style.zIndex = -1;
nicholas@116 485 this.popup.style.visibility = 'hidden';
nicholas@116 486 var blank = document.getElementsByClassName('testHalt')[0];
nicholas@116 487 blank.style.zIndex = -2;
nicholas@116 488 blank.style.visibility = 'hidden';
n@303 489 this.buttonPrevious.style.visibility = 'inherit';
nicholas@116 490 };
nicholas@116 491
nicholas@116 492 this.postNode = function() {
nicholas@116 493 // This will take the node from the popupOptions and display it
nicholas@116 494 var node = this.popupOptions[this.currentIndex];
n@303 495 this.popupResponse.innerHTML = null;
n@453 496 this.popupTitle.textContent = node.specification.statement;
n@453 497 if (node.specification.type == 'question') {
nicholas@116 498 var textArea = document.createElement('textarea');
n@453 499 switch (node.specification.boxsize) {
n@191 500 case 'small':
n@191 501 textArea.cols = "20";
n@191 502 textArea.rows = "1";
n@191 503 break;
n@191 504 case 'normal':
n@191 505 textArea.cols = "30";
n@191 506 textArea.rows = "2";
n@191 507 break;
n@191 508 case 'large':
n@191 509 textArea.cols = "40";
n@191 510 textArea.rows = "5";
n@191 511 break;
n@191 512 case 'huge':
n@191 513 textArea.cols = "50";
n@191 514 textArea.rows = "10";
n@191 515 break;
n@191 516 }
n@490 517 if (node.response == undefined) {
n@490 518 node.response = "";
n@490 519 } else {
n@490 520 textArea.value = node.response;
n@490 521 }
n@303 522 this.popupResponse.appendChild(textArea);
n@303 523 textArea.focus();
n@513 524 this.popupResponse.style.textAlign="center";
n@513 525 this.popupResponse.style.left="0%";
n@453 526 } else if (node.specification.type == 'checkbox') {
n@490 527 if (node.response == undefined) {
n@490 528 node.response = Array(node.specification.options.length);
n@490 529 }
n@490 530 var index = 0;
n@513 531 var max_w = 0;
n@453 532 for (var option of node.specification.options) {
nicholas@188 533 var input = document.createElement('input');
n@448 534 input.id = option.name;
nicholas@188 535 input.type = 'checkbox';
nicholas@188 536 var span = document.createElement('span');
nicholas@188 537 span.textContent = option.text;
nicholas@188 538 var hold = document.createElement('div');
nicholas@188 539 hold.setAttribute('name','option');
nicholas@188 540 hold.style.padding = '4px';
nicholas@188 541 hold.appendChild(input);
nicholas@188 542 hold.appendChild(span);
n@453 543 this.popupResponse.appendChild(hold);
n@490 544 if (node.response[index] != undefined){
n@490 545 if (node.response[index].checked == true) {
n@490 546 input.checked = "true";
n@490 547 }
n@490 548 }
n@513 549 var w = $(span).width();
n@513 550 if (w > max_w)
n@513 551 max_w = w;
n@490 552 index++;
nicholas@188 553 }
n@513 554 max_w += 12;
n@513 555 this.popupResponse.style.textAlign="";
n@513 556 var leftP = ((max_w/500)/2)*100;
n@513 557 this.popupResponse.style.left=leftP+"%";
n@453 558 } else if (node.specification.type == 'radio') {
n@490 559 if (node.response == undefined) {
n@490 560 node.response = {name: "", text: ""};
n@490 561 }
n@490 562 var index = 0;
n@513 563 var max_w = 0;
n@453 564 for (var option of node.specification.options) {
nicholas@189 565 var input = document.createElement('input');
nicholas@189 566 input.id = option.name;
nicholas@189 567 input.type = 'radio';
n@453 568 input.name = node.specification.id;
nicholas@189 569 var span = document.createElement('span');
nicholas@189 570 span.textContent = option.text;
nicholas@189 571 var hold = document.createElement('div');
nicholas@189 572 hold.setAttribute('name','option');
nicholas@189 573 hold.style.padding = '4px';
nicholas@189 574 hold.appendChild(input);
nicholas@189 575 hold.appendChild(span);
n@453 576 this.popupResponse.appendChild(hold);
n@490 577 if (input.id == node.response.name) {
n@490 578 input.checked = "true";
n@490 579 }
n@513 580 var w = $(span).width();
n@513 581 if (w > max_w)
n@513 582 max_w = w;
nicholas@189 583 }
n@513 584 max_w += 12;
n@513 585 this.popupResponse.style.textAlign="";
n@513 586 var leftP = ((max_w/500)/2)*100;
n@513 587 this.popupResponse.style.left=leftP+"%";
n@453 588 } else if (node.specification.type == 'number') {
n@196 589 var input = document.createElement('input');
nicholas@224 590 input.type = 'textarea';
n@453 591 if (node.min != null) {input.min = node.specification.min;}
n@453 592 if (node.max != null) {input.max = node.specification.max;}
n@453 593 if (node.step != null) {input.step = node.specification.step;}
n@490 594 if (node.response != undefined) {
n@490 595 input.value = node.response;
n@490 596 }
n@303 597 this.popupResponse.appendChild(input);
n@513 598 this.popupResponse.style.textAlign="center";
n@513 599 this.popupResponse.style.left="0%";
nicholas@116 600 }
n@199 601 if(this.currentIndex+1 == this.popupOptions.length) {
n@453 602 if (this.node.location == "pre") {
nicholas@268 603 this.buttonProceed.textContent = 'Start';
nicholas@268 604 } else {
nicholas@268 605 this.buttonProceed.textContent = 'Submit';
nicholas@268 606 }
n@199 607 } else {
n@199 608 this.buttonProceed.textContent = 'Next';
n@199 609 }
n@199 610 if(this.currentIndex > 0)
n@303 611 this.buttonPrevious.style.visibility = 'visible';
n@303 612 else
n@303 613 this.buttonPrevious.style.visibility = 'hidden';
n@155 614 };
nicholas@116 615
n@453 616 this.initState = function(node,store) {
nicholas@116 617 //Call this with your preTest and postTest nodes when needed to
nicholas@116 618 // initialise the popup procedure.
n@453 619 if (node.options.length > 0) {
n@453 620 this.popupOptions = [];
n@453 621 this.node = node;
n@453 622 this.store = store;
n@453 623 for (var opt of node.options)
n@453 624 {
n@453 625 this.popupOptions.push({
n@453 626 specification: opt,
n@453 627 response: null
n@453 628 });
n@453 629 }
nicholas@116 630 this.currentIndex = 0;
nicholas@116 631 this.showPopup();
nicholas@116 632 this.postNode();
n@181 633 } else {
n@181 634 advanceState();
nicholas@116 635 }
n@155 636 };
nicholas@116 637
n@197 638 this.proceedClicked = function() {
nicholas@116 639 // Each time the popup button is clicked!
nicholas@116 640 var node = this.popupOptions[this.currentIndex];
n@453 641 if (node.specification.type == 'question') {
nicholas@116 642 // Must extract the question data
nicholas@116 643 var textArea = $(popup.popupContent).find('textarea')[0];
n@453 644 if (node.specification.mandatory == true && textArea.value.length == 0) {
nicholas@116 645 alert('This question is mandatory');
nicholas@116 646 return;
nicholas@116 647 } else {
nicholas@116 648 // Save the text content
n@453 649 console.log("Question: "+ node.specification.statement);
nicholas@117 650 console.log("Question Response: "+ textArea.value);
n@453 651 node.response = textArea.value;
nicholas@116 652 }
n@453 653 } else if (node.specification.type == 'checkbox') {
nicholas@188 654 // Must extract checkbox data
n@455 655 console.log("Checkbox: "+ node.specification.statement);
n@453 656 var inputs = this.popupResponse.getElementsByTagName('input');
n@453 657 node.response = [];
n@453 658 for (var i=0; i<node.specification.options.length; i++) {
n@453 659 node.response.push({
n@453 660 name: node.specification.options[i].name,
n@453 661 text: node.specification.options[i].text,
n@453 662 checked: inputs[i].checked
n@453 663 });
n@455 664 console.log(node.specification.options[i].name+": "+ inputs[i].checked);
n@453 665 }
n@453 666 } else if (node.specification.type == "radio") {
n@303 667 var optHold = this.popupResponse;
n@453 668 console.log("Radio: "+ node.specification.statement);
n@453 669 node.response = null;
nicholas@189 670 var i=0;
n@453 671 var inputs = optHold.getElementsByTagName('input');
n@453 672 while(node.response == null) {
n@453 673 if (i == inputs.length)
n@453 674 {
n@453 675 if (node.specification.mandatory == true)
n@453 676 {
n@453 677 alert("This radio is mandatory");
n@453 678 } else {
n@453 679 node.response = -1;
n@453 680 }
n@453 681 return;
n@453 682 }
n@453 683 if (inputs[i].checked == true) {
n@453 684 node.response = node.specification.options[i];
n@453 685 console.log("Selected: "+ node.specification.options[i].name);
nicholas@189 686 }
nicholas@189 687 i++;
nicholas@189 688 }
n@453 689 } else if (node.specification.type == "number") {
n@196 690 var input = this.popupContent.getElementsByTagName('input')[0];
n@196 691 if (node.mandatory == true && input.value.length == 0) {
n@197 692 alert('This question is mandatory. Please enter a number');
n@197 693 return;
n@197 694 }
n@197 695 var enteredNumber = Number(input.value);
nicholas@224 696 if (isNaN(enteredNumber)) {
n@197 697 alert('Please enter a valid number');
n@197 698 return;
n@197 699 }
n@197 700 if (enteredNumber < node.min && node.min != null) {
n@197 701 alert('Number is below the minimum value of '+node.min);
n@197 702 return;
n@197 703 }
n@197 704 if (enteredNumber > node.max && node.max != null) {
n@197 705 alert('Number is above the maximum value of '+node.max);
n@196 706 return;
n@196 707 }
n@453 708 node.response = input.value;
nicholas@116 709 }
nicholas@116 710 this.currentIndex++;
nicholas@116 711 if (this.currentIndex < this.popupOptions.length) {
nicholas@116 712 this.postNode();
nicholas@116 713 } else {
nicholas@116 714 // Reached the end of the popupOptions
nicholas@116 715 this.hidePopup();
n@453 716 for (var node of this.popupOptions)
n@453 717 {
n@453 718 this.store.postResult(node);
nicholas@129 719 }
nicholas@116 720 advanceState();
nicholas@116 721 }
n@155 722 };
n@199 723
n@199 724 this.previousClick = function() {
n@199 725 // Triggered when the 'Back' button is clicked in the survey
n@199 726 if (this.currentIndex > 0) {
n@199 727 this.currentIndex--;
n@199 728 this.postNode();
n@199 729 }
n@199 730 };
n@395 731
n@395 732 this.resize = function(event)
n@395 733 {
n@395 734 // Called on window resize;
n@473 735 if (this.popup != null) {
n@473 736 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
n@473 737 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
n@473 738 var blank = document.getElementsByClassName('testHalt')[0];
n@473 739 blank.style.width = window.innerWidth;
n@473 740 blank.style.height = window.innerHeight;
n@473 741 }
n@395 742 };
n@539 743 this.hideNextButton = function() {
n@539 744 this.buttonProceed.style.visibility = "hidden";
n@539 745 }
n@539 746 this.hidePreviousButton = function() {
n@539 747 this.buttonPrevious.style.visibility = "hidden";
n@539 748 }
n@539 749 this.showNextButton = function() {
n@539 750 this.buttonProceed.style.visibility = "visible";
n@539 751 }
n@539 752 this.showPreviousButton = function() {
n@539 753 this.buttonPrevious.style.visibility = "visible";
n@539 754 }
nicholas@114 755 }
nicholas@114 756
nicholas@116 757 function advanceState()
nicholas@114 758 {
nicholas@129 759 // Just for complete clarity
nicholas@129 760 testState.advanceState();
nicholas@129 761 }
nicholas@129 762
nicholas@129 763 function stateMachine()
nicholas@129 764 {
nicholas@129 765 // Object prototype for tracking and managing the test state
nicholas@129 766 this.stateMap = [];
n@453 767 this.preTestSurvey = null;
n@453 768 this.postTestSurvey = null;
nicholas@129 769 this.stateIndex = null;
n@453 770 this.currentStateMap = null;
n@453 771 this.currentStatePosition = null;
n@483 772 this.currentStore = null;
nicholas@129 773 this.initialise = function(){
n@453 774
n@453 775 // Get the data from Specification
n@453 776 var pageHolder = [];
n@453 777 for (var page of specification.pages)
n@453 778 {
n@511 779 var repeat = page.repeatCount;
n@511 780 while(repeat >= 0)
n@511 781 {
n@511 782 pageHolder.push(page);
n@511 783 repeat--;
n@511 784 }
n@453 785 }
n@453 786 if (specification.randomiseOrder)
n@453 787 {
n@453 788 pageHolder = randomiseOrder(pageHolder);
n@453 789 }
n@453 790 for (var i=0; i<pageHolder.length; i++)
n@453 791 {
n@453 792 pageHolder[i].presentedId = i;
n@453 793 }
n@453 794 for (var i=0; i<specification.pages.length; i++)
n@453 795 {
n@558 796 if (specification.testPages <= i && specification.testPages != 0) {break;}
n@453 797 this.stateMap.push(pageHolder[i]);
n@587 798 storage.createTestPageStore(pageHolder[i]);
n@591 799 for (var element of pageHolder[i].audioElements) {
n@591 800 var URL = pageHolder[i].hostURL + element.url;
n@591 801 var buffer = null;
n@591 802 for (var buffObj of audioEngineContext.buffers) {
n@591 803 if (URL == buffObj.url) {
n@591 804 buffer = buffObj;
n@591 805 break;
n@591 806 }
n@591 807 }
n@591 808 if (buffer == null) {
n@591 809 buffer = new audioEngineContext.bufferObj();
n@591 810 buffer.getMedia(URL);
n@591 811 audioEngineContext.buffers.push(buffer);
n@591 812 }
n@591 813 }
n@453 814 }
n@558 815
n@453 816 if (specification.preTest != null) {this.preTestSurvey = specification.preTest;}
n@453 817 if (specification.postTest != null) {this.postTestSurvey = specification.postTest;}
n@453 818
nicholas@129 819 if (this.stateMap.length > 0) {
nicholas@129 820 if(this.stateIndex != null) {
nicholas@129 821 console.log('NOTE - State already initialise');
nicholas@129 822 }
nicholas@129 823 this.stateIndex = -1;
nicholas@129 824 } else {
b@254 825 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
nicholas@116 826 }
nicholas@129 827 };
nicholas@129 828 this.advanceState = function(){
nicholas@129 829 if (this.stateIndex == null) {
nicholas@129 830 this.initialise();
nicholas@129 831 }
n@589 832 storage.update();
nicholas@129 833 if (this.stateIndex == -1) {
n@471 834 this.stateIndex++;
nicholas@129 835 console.log('Starting test...');
n@453 836 if (this.preTestSurvey != null)
n@453 837 {
n@453 838 popup.initState(this.preTestSurvey,storage.globalPreTest);
n@471 839 } else {
n@471 840 this.advanceState();
nicholas@129 841 }
n@453 842 } else if (this.stateIndex == this.stateMap.length)
n@453 843 {
n@453 844 // All test pages complete, post test
n@453 845 console.log('Ending test ...');
n@453 846 this.stateIndex++;
n@453 847 if (this.postTestSurvey == null) {
n@453 848 this.advanceState();
nicholas@129 849 } else {
n@453 850 popup.initState(this.postTestSurvey,storage.globalPostTest);
n@453 851 }
n@453 852 } else if (this.stateIndex > this.stateMap.length)
n@453 853 {
n@453 854 createProjectSave(specification.projectReturn);
n@453 855 }
n@453 856 else
n@453 857 {
n@453 858 if (this.currentStateMap == null)
n@453 859 {
nicholas@129 860 this.currentStateMap = this.stateMap[this.stateIndex];
n@463 861 if (this.currentStateMap.randomiseOrder)
n@463 862 {
n@463 863 this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements);
n@463 864 }
n@587 865 this.currentStore = storage.testPages[this.stateIndex];
n@453 866 if (this.currentStateMap.preTest != null)
n@453 867 {
n@453 868 this.currentStatePosition = 'pre';
n@453 869 popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
nicholas@129 870 } else {
n@453 871 this.currentStatePosition = 'test';
n@453 872 }
n@453 873 interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
n@453 874 return;
n@453 875 }
n@453 876 switch(this.currentStatePosition)
n@453 877 {
n@453 878 case 'pre':
n@453 879 this.currentStatePosition = 'test';
n@453 880 break;
n@453 881 case 'test':
n@453 882 this.currentStatePosition = 'post';
n@453 883 // Save the data
n@453 884 this.testPageCompleted();
n@453 885 if (this.currentStateMap.postTest == null)
n@453 886 {
nicholas@129 887 this.advanceState();
n@453 888 return;
n@453 889 } else {
n@453 890 popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
nicholas@129 891 }
n@453 892 break;
n@453 893 case 'post':
n@453 894 this.stateIndex++;
n@453 895 this.currentStateMap = null;
n@453 896 this.advanceState();
n@453 897 break;
n@453 898 };
nicholas@129 899 }
nicholas@129 900 };
nicholas@129 901
n@453 902 this.testPageCompleted = function() {
nicholas@129 903 // Function called each time a test page has been completed
n@453 904 var storePoint = storage.testPages[this.stateIndex];
n@453 905 // First get the test metric
n@453 906
n@453 907 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
n@381 908 if (audioEngineContext.metric.enableTestTimer)
n@381 909 {
n@453 910 var testTime = storePoint.parent.document.createElement('metricresult');
n@381 911 testTime.id = 'testTime';
n@381 912 testTime.textContent = audioEngineContext.timer.testDuration;
n@381 913 metric.appendChild(testTime);
n@381 914 }
n@453 915
n@381 916 var audioObjects = audioEngineContext.audioObjects;
n@453 917 for (var ao of audioEngineContext.audioObjects)
n@381 918 {
n@453 919 ao.exportXMLDOM();
n@381 920 }
n@453 921 for (var element of interfaceContext.commentQuestions)
n@453 922 {
n@453 923 element.exportXMLDOM(storePoint);
n@453 924 }
n@453 925 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
n@176 926 };
nicholas@114 927 }
nicholas@114 928
n@377 929 function AudioEngine(specification) {
nicholas@1 930
nicholas@1 931 // Create two output paths, the main outputGain and fooGain.
nicholas@1 932 // Output gain is default to 1 and any items for playback route here
nicholas@1 933 // Foo gain is used for analysis to ensure paths get processed, but are not heard
nicholas@1 934 // because web audio will optimise and any route which does not go to the destination gets ignored.
nicholas@1 935 this.outputGain = audioContext.createGain();
nicholas@1 936 this.fooGain = audioContext.createGain();
nicholas@1 937 this.fooGain.gain = 0;
nicholas@1 938
nicholas@7 939 // Use this to detect playback state: 0 - stopped, 1 - playing
nicholas@7 940 this.status = 0;
nicholas@7 941
nicholas@1 942 // Connect both gains to output
nicholas@1 943 this.outputGain.connect(audioContext.destination);
nicholas@1 944 this.fooGain.connect(audioContext.destination);
nicholas@1 945
n@49 946 // Create the timer Object
n@49 947 this.timer = new timer();
n@49 948 // Create session metrics
n@377 949 this.metric = new sessionMetrics(this,specification);
n@49 950
n@57 951 this.loopPlayback = false;
n@57 952
n@453 953 this.pageStore = null;
n@453 954
nicholas@1 955 // Create store for new audioObjects
nicholas@1 956 this.audioObjects = [];
nicholas@1 957
n@379 958 this.buffers = [];
n@408 959 this.bufferObj = function()
n@379 960 {
n@408 961 this.url = null;
n@379 962 this.buffer = null;
n@379 963 this.xmlRequest = new XMLHttpRequest();
nicholas@418 964 this.xmlRequest.parent = this;
n@379 965 this.users = [];
n@496 966 this.progress = 0;
n@496 967 this.status = 0;
n@471 968 this.ready = function()
n@471 969 {
n@496 970 if (this.status >= 2)
n@496 971 {
n@496 972 this.status = 3;
n@496 973 }
n@471 974 for (var i=0; i<this.users.length; i++)
n@471 975 {
n@471 976 this.users[i].state = 1;
n@471 977 if (this.users[i].interfaceDOM != null)
n@471 978 {
n@471 979 this.users[i].bufferLoaded(this);
n@471 980 }
n@471 981 }
n@471 982 };
n@408 983 this.getMedia = function(url) {
n@408 984 this.url = url;
n@408 985 this.xmlRequest.open('GET',this.url,true);
n@408 986 this.xmlRequest.responseType = 'arraybuffer';
n@408 987
n@408 988 var bufferObj = this;
n@408 989
n@408 990 // Create callback to decode the data asynchronously
n@408 991 this.xmlRequest.onloadend = function() {
n@482 992 // Use inbuilt WAVE decoder first
n@546 993 if (this.status == -1) {return;}
n@482 994 var waveObj = new WAVE();
n@482 995 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
n@482 996 {
n@482 997 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
n@482 998 for (var c=0; c<waveObj.num_channels; c++)
n@482 999 {
n@482 1000 var buffer_ptr = bufferObj.buffer.getChannelData(c);
n@482 1001 for (var n=0; n<waveObj.num_samples; n++)
n@482 1002 {
n@482 1003 buffer_ptr[n] = waveObj.decoded_data[c][n];
n@482 1004 }
n@482 1005 }
n@496 1006
n@482 1007 delete waveObj;
n@482 1008 } else {
n@482 1009 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
n@482 1010 bufferObj.buffer = decodedData;
n@482 1011 }, function(e){
n@482 1012 // Should only be called if there was an error, but sometimes gets called continuously
n@482 1013 // Check here if the error is genuine
n@482 1014 if (bufferObj.xmlRequest.response == undefined) {
n@482 1015 // Genuine error
n@482 1016 console.log('FATAL - Error loading buffer on '+audioObj.id);
n@482 1017 if (request.status == 404)
n@482 1018 {
n@482 1019 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
n@482 1020 console.log('URL: '+audioObj.url);
n@482 1021 errorSessionDump('Fragment '+audioObj.id+' 404 error');
n@482 1022 }
n@546 1023 this.parent.status = -1;
n@482 1024 }
n@482 1025 });
n@482 1026 }
n@482 1027 if (bufferObj.buffer != undefined)
n@482 1028 {
n@496 1029 bufferObj.status = 2;
n@482 1030 calculateLoudness(bufferObj,"I");
n@482 1031 }
n@408 1032 };
n@546 1033
n@546 1034 // Create callback for any error in loading
n@546 1035 this.xmlRequest.onerror = function() {
n@546 1036 this.parent.status = -1;
n@546 1037 for (var i=0; i<this.parent.users.length; i++)
n@546 1038 {
n@546 1039 this.parent.users[i].state = -1;
n@546 1040 if (this.parent.users[i].interfaceDOM != null)
n@546 1041 {
n@546 1042 this.parent.users[i].bufferLoaded(this);
n@546 1043 }
n@546 1044 }
n@546 1045 }
n@546 1046
n@411 1047 this.progress = 0;
n@411 1048 this.progressCallback = function(event){
n@411 1049 if (event.lengthComputable)
n@411 1050 {
nicholas@418 1051 this.parent.progress = event.loaded / event.total;
nicholas@418 1052 for (var i=0; i<this.parent.users.length; i++)
nicholas@418 1053 {
nicholas@418 1054 if(this.parent.users[i].interfaceDOM != null)
nicholas@418 1055 {
nicholas@418 1056 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
nicholas@418 1057 {
nicholas@418 1058 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
nicholas@418 1059 }
nicholas@418 1060 }
nicholas@418 1061 }
n@411 1062 }
n@411 1063 };
n@411 1064 this.xmlRequest.addEventListener("progress", this.progressCallback);
n@496 1065 this.status = 1;
n@408 1066 this.xmlRequest.send();
n@379 1067 };
n@496 1068
n@496 1069 this.registerAudioObject = function(audioObject)
n@496 1070 {
n@496 1071 // Called by an audioObject to register to the buffer for use
n@496 1072 // First check if already in the register pool
n@496 1073 for (var objects of this.users)
n@496 1074 {
n@496 1075 if (audioObject.id == objects.id){return 0;}
n@496 1076 }
n@496 1077 this.users.push(audioObject);
n@546 1078 if (this.status == 3 || this.status == -1)
n@496 1079 {
n@496 1080 // The buffer is already ready, trigger bufferLoaded
n@496 1081 audioObject.bufferLoaded(this);
n@496 1082 }
n@496 1083 }
n@379 1084 };
n@379 1085
n@202 1086 this.play = function(id) {
n@113 1087 // Start the timer and set the audioEngine state to playing (1)
n@300 1088 if (this.status == 0 && this.loopPlayback) {
n@113 1089 // Check if all audioObjects are ready
n@300 1090 if(this.checkAllReady())
n@300 1091 {
n@202 1092 this.status = 1;
n@300 1093 this.setSynchronousLoop();
n@202 1094 }
n@202 1095 }
n@300 1096 else
n@300 1097 {
n@300 1098 this.status = 1;
n@300 1099 }
n@202 1100 if (this.status== 1) {
n@300 1101 this.timer.startTest();
n@204 1102 if (id == undefined) {
n@204 1103 id = -1;
n@300 1104 console.log('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
n@300 1105 return;
n@204 1106 } else {
n@204 1107 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
n@204 1108 }
n@202 1109 if (this.loopPlayback) {
n@524 1110 var setTime = audioContext.currentTime;
n@202 1111 for (var i=0; i<this.audioObjects.length; i++)
n@202 1112 {
n@524 1113 this.audioObjects[i].play(setTime);
n@202 1114 if (id == i) {
n@489 1115 this.audioObjects[i].loopStart(setTime);
n@202 1116 } else {
n@489 1117 this.audioObjects[i].loopStop(setTime);
nicholas@131 1118 }
nicholas@131 1119 }
n@202 1120 } else {
n@489 1121 var setTime = audioContext.currentTime+0.1;
n@202 1122 for (var i=0; i<this.audioObjects.length; i++)
n@202 1123 {
n@202 1124 if (i != id) {
n@489 1125 this.audioObjects[i].stop(setTime);
n@202 1126 } else if (i == id) {
n@489 1127 this.audioObjects[id].play(setTime);
n@202 1128 }
n@202 1129 }
n@113 1130 }
n@204 1131 interfaceContext.playhead.start();
n@113 1132 }
n@113 1133 };
nicholas@1 1134
n@113 1135 this.stop = function() {
n@509 1136 // Send stop and reset command to all playback buffers
n@113 1137 if (this.status == 1) {
n@489 1138 var setTime = audioContext.currentTime+0.1;
n@113 1139 for (var i=0; i<this.audioObjects.length; i++)
n@113 1140 {
n@489 1141 this.audioObjects[i].stop(setTime);
n@113 1142 }
n@204 1143 interfaceContext.playhead.stop();
n@113 1144 }
n@113 1145 };
nicholas@8 1146
n@182 1147 this.newTrack = function(element) {
nicholas@1 1148 // Pull data from given URL into new audio buffer
nicholas@1 1149 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
nicholas@7 1150
nicholas@1 1151 // Create the audioObject with ID of the new track length;
n@49 1152 audioObjectId = this.audioObjects.length;
nicholas@1 1153 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
nicholas@7 1154
n@379 1155 // Check if audioObject buffer is currently stored by full URL
n@453 1156 var URL = testState.currentStateMap.hostURL + element.url;
n@379 1157 var buffer = null;
n@379 1158 for (var i=0; i<this.buffers.length; i++)
n@379 1159 {
n@379 1160 if (URL == this.buffers[i].url)
n@379 1161 {
n@379 1162 buffer = this.buffers[i];
n@379 1163 break;
n@379 1164 }
n@379 1165 }
n@379 1166 if (buffer == null)
n@379 1167 {
n@400 1168 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
n@408 1169 buffer = new this.bufferObj();
n@496 1170 this.buffers.push(buffer);
n@408 1171 buffer.getMedia(URL);
n@379 1172 }
n@182 1173 this.audioObjects[audioObjectId].specification = element;
n@400 1174 this.audioObjects[audioObjectId].url = URL;
n@453 1175 // Obtain store node
n@453 1176 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
n@453 1177 for (var i=0; i<aeNodes.length; i++)
n@453 1178 {
n@453 1179 if(aeNodes[i].id == element.id)
n@453 1180 {
n@453 1181 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
n@453 1182 break;
n@453 1183 }
n@453 1184 }
n@496 1185 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
n@179 1186 return this.audioObjects[audioObjectId];
n@16 1187 };
nicholas@1 1188
n@500 1189 this.newTestPage = function(audioHolderObject,store) {
n@453 1190 this.pageStore = store;
n@561 1191 this.status = 0;
n@113 1192 this.audioObjectsReady = false;
n@113 1193 this.metric.reset();
n@379 1194 for (var i=0; i < this.buffers.length; i++)
n@379 1195 {
n@379 1196 this.buffers[i].users = [];
n@379 1197 }
n@113 1198 this.audioObjects = [];
n@500 1199 this.timer = new timer();
n@500 1200 this.loopPlayback = audioHolderObject.loop;
n@113 1201 };
n@113 1202
nicholas@107 1203 this.checkAllPlayed = function() {
nicholas@107 1204 arr = [];
nicholas@107 1205 for (var id=0; id<this.audioObjects.length; id++) {
nicholas@142 1206 if (this.audioObjects[id].metric.wasListenedTo == false) {
nicholas@107 1207 arr.push(this.audioObjects[id].id);
nicholas@107 1208 }
nicholas@107 1209 }
nicholas@107 1210 return arr;
nicholas@107 1211 };
nicholas@107 1212
n@113 1213 this.checkAllReady = function() {
n@113 1214 var ready = true;
n@113 1215 for (var i=0; i<this.audioObjects.length; i++) {
n@113 1216 if (this.audioObjects[i].state == 0) {
n@113 1217 // Track not ready
n@113 1218 console.log('WAIT -- audioObject '+i+' not ready yet!');
n@113 1219 ready = false;
n@113 1220 };
n@113 1221 }
n@113 1222 return ready;
n@113 1223 };
n@113 1224
nicholas@272 1225 this.setSynchronousLoop = function() {
nicholas@272 1226 // Pads the signals so they are all exactly the same length
n@300 1227 var length = 0;
n@300 1228 var maxId;
n@300 1229 for (var i=0; i<this.audioObjects.length; i++)
nicholas@272 1230 {
n@383 1231 if (length < this.audioObjects[i].buffer.buffer.length)
nicholas@272 1232 {
n@383 1233 length = this.audioObjects[i].buffer.buffer.length;
n@300 1234 maxId = i;
nicholas@272 1235 }
n@300 1236 }
n@300 1237 // Extract the audio and zero-pad
n@408 1238 for (var i=0; i<this.audioObjects.length; i++)
n@300 1239 {
n@383 1240 var orig = this.audioObjects[i].buffer.buffer;
n@300 1241 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate);
n@300 1242 for (var c=0; c<orig.numberOfChannels; c++)
nicholas@272 1243 {
n@300 1244 var inData = hold.getChannelData(c);
n@300 1245 var outData = orig.getChannelData(c);
n@300 1246 for (var n=0; n<orig.length; n++)
n@300 1247 {inData[n] = outData[n];}
nicholas@272 1248 }
n@448 1249 hold.playbackGain = orig.playbackGain;
n@408 1250 hold.lufs = orig.lufs;
n@383 1251 this.audioObjects[i].buffer.buffer = hold;
nicholas@272 1252 }
nicholas@272 1253 };
n@501 1254
n@501 1255 this.exportXML = function()
n@501 1256 {
n@501 1257
n@501 1258 };
nicholas@272 1259
nicholas@1 1260 }
nicholas@1 1261
nicholas@1 1262 function audioObject(id) {
nicholas@1 1263 // The main buffer object with common control nodes to the AudioEngine
nicholas@1 1264
n@182 1265 this.specification;
nicholas@1 1266 this.id = id;
nicholas@1 1267 this.state = 0; // 0 - no data, 1 - ready
n@24 1268 this.url = null; // Hold the URL given for the output back to the results.
n@139 1269 this.metric = new metricTracker(this);
n@453 1270 this.storeDOM = null;
nicholas@1 1271
n@177 1272 // Bindings for GUI
n@183 1273 this.interfaceDOM = null;
n@177 1274 this.commentDOM = null;
n@177 1275
nicholas@1 1276 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
n@57 1277 this.bufferNode = undefined;
nicholas@1 1278 this.outputGain = audioContext.createGain();
nicholas@1 1279
n@453 1280 this.onplayGain = 1.0;
nicholas@8 1281
nicholas@1 1282 // Connect buffer to the audio graph
nicholas@1 1283 this.outputGain.connect(audioEngineContext.outputGain);
nicholas@1 1284
nicholas@1 1285 // the audiobuffer is not designed for multi-start playback
nicholas@1 1286 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
nicholas@1 1287 this.buffer;
n@412 1288
n@412 1289 this.bufferLoaded = function(callee)
n@412 1290 {
n@412 1291 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
n@412 1292 // audioObject and trigger the interfaceDOM.enable() function for user feedback
n@546 1293 if (callee.status == -1) {
n@546 1294 // ERROR
n@546 1295 this.state = -1;
n@546 1296 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
n@546 1297 this.buffer = callee;
n@546 1298 return;
n@546 1299 }
n@412 1300 if (audioEngineContext.loopPlayback){
n@412 1301 // First copy the buffer into this.buffer
n@412 1302 this.buffer = new audioEngineContext.bufferObj();
n@412 1303 this.buffer.url = callee.url;
n@412 1304 this.buffer.buffer = audioContext.createBuffer(callee.buffer.numberOfChannels, callee.buffer.length, callee.buffer.sampleRate);
n@412 1305 for (var c=0; c<callee.buffer.numberOfChannels; c++)
n@412 1306 {
n@412 1307 var src = callee.buffer.getChannelData(c);
n@412 1308 var dst = this.buffer.buffer.getChannelData(c);
n@412 1309 for (var n=0; n<src.length; n++)
n@412 1310 {
n@412 1311 dst[n] = src[n];
n@412 1312 }
n@412 1313 }
n@412 1314 } else {
n@412 1315 this.buffer = callee;
n@412 1316 }
n@412 1317 this.state = 1;
n@448 1318 this.buffer.buffer.playbackGain = callee.buffer.playbackGain;
n@412 1319 this.buffer.buffer.lufs = callee.buffer.lufs;
n@477 1320 var targetLUFS = this.specification.parent.loudness || specification.loudness;
n@412 1321 if (typeof targetLUFS === "number")
n@412 1322 {
n@448 1323 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
n@412 1324 } else {
n@448 1325 this.buffer.buffer.playbackGain = 1.0;
n@412 1326 }
n@412 1327 if (this.interfaceDOM != null) {
n@412 1328 this.interfaceDOM.enable();
n@412 1329 }
n@453 1330 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
n@453 1331 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
n@412 1332 };
n@454 1333
n@454 1334 this.bindInterface = function(interfaceObject)
n@454 1335 {
n@454 1336 this.interfaceDOM = interfaceObject;
n@454 1337 this.metric.initialise(interfaceObject.getValue());
n@454 1338 if (this.state == 1)
n@454 1339 {
n@454 1340 this.interfaceDOM.enable();
n@546 1341 } else if (this.state == -1) {
n@546 1342 // ERROR
n@546 1343 this.interfaceDOM.error();
n@546 1344 return;
n@546 1345 }
n@467 1346 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
n@454 1347 };
b@134 1348
n@489 1349 this.loopStart = function(setTime) {
n@489 1350 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
nicholas@132 1351 this.metric.startListening(audioEngineContext.timer.getTestTime());
n@489 1352 this.interfaceDOM.startPlayback();
n@177 1353 };
nicholas@132 1354
n@489 1355 this.loopStop = function(setTime) {
nicholas@132 1356 if (this.outputGain.gain.value != 0.0) {
n@489 1357 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
nicholas@132 1358 this.metric.stopListening(audioEngineContext.timer.getTestTime());
nicholas@132 1359 }
n@489 1360 this.interfaceDOM.stopPlayback();
n@177 1361 };
nicholas@132 1362
nicholas@1 1363 this.play = function(startTime) {
n@379 1364 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
n@202 1365 this.bufferNode = audioContext.createBufferSource();
n@202 1366 this.bufferNode.owner = this;
n@202 1367 this.bufferNode.connect(this.outputGain);
n@379 1368 this.bufferNode.buffer = this.buffer.buffer;
n@202 1369 this.bufferNode.loop = audioEngineContext.loopPlayback;
n@299 1370 this.bufferNode.onended = function(event) {
n@202 1371 // Safari does not like using 'this' to reference the calling object!
n@347 1372 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
n@553 1373 if (event.currentTarget != null) {
n@553 1374 event.currentTarget.owner.stop(audioContext.currentTime+1);
n@553 1375 }
n@202 1376 };
n@202 1377 if (this.bufferNode.loop == false) {
n@202 1378 this.metric.startListening(audioEngineContext.timer.getTestTime());
n@489 1379 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
n@489 1380 this.interfaceDOM.startPlayback();
n@489 1381 } else {
n@489 1382 this.outputGain.gain.setValueAtTime(0.0,startTime);
n@489 1383 }
n@202 1384 this.bufferNode.start(startTime);
n@560 1385 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
nicholas@110 1386 }
n@16 1387 };
nicholas@1 1388
n@489 1389 this.stop = function(stopTime) {
n@489 1390 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
n@97 1391 if (this.bufferNode != undefined)
n@97 1392 {
n@203 1393 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
n@489 1394 this.bufferNode.stop(stopTime);
n@97 1395 this.bufferNode = undefined;
n@97 1396 }
n@489 1397 this.outputGain.gain.value = 0.0;
n@489 1398 this.interfaceDOM.stopPlayback();
n@16 1399 };
n@164 1400
n@164 1401 this.getCurrentPosition = function() {
n@164 1402 var time = audioEngineContext.timer.getTestTime();
n@164 1403 if (this.bufferNode != undefined) {
n@575 1404 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
n@575 1405 if (isNaN(position)){return 0;}
n@575 1406 return position;
n@164 1407 } else {
n@164 1408 return 0;
n@164 1409 }
n@164 1410 };
n@183 1411
n@183 1412 this.exportXMLDOM = function() {
n@453 1413 var file = storage.document.createElement('file');
nicholas@387 1414 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
nicholas@387 1415 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
nicholas@387 1416 file.setAttribute('sampleCount',this.buffer.buffer.length);
nicholas@387 1417 file.setAttribute('duration',this.buffer.buffer.duration);
n@453 1418 this.storeDOM.appendChild(file);
n@453 1419 if (this.specification.type != 'outside-reference') {
n@383 1420 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
n@469 1421 if (interfaceXML != null)
n@469 1422 {
n@469 1423 if (interfaceXML.length == undefined) {
n@469 1424 this.storeDOM.appendChild(interfaceXML);
n@469 1425 } else {
n@469 1426 for (var i=0; i<interfaceXML.length; i++)
n@469 1427 {
n@469 1428 this.storeDOM.appendChild(interfaceXML[i]);
n@469 1429 }
n@383 1430 }
n@383 1431 }
n@459 1432 if (this.commentDOM != null) {
n@459 1433 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
n@459 1434 }
nicholas@236 1435 }
n@453 1436 var nodes = this.metric.exportXMLDOM();
n@453 1437 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
n@453 1438 for (var i=0; i<nodes.length; i++)
n@453 1439 {
n@453 1440 mroot.appendChild(nodes[i]);
n@453 1441 }
n@183 1442 };
n@49 1443 }
n@49 1444
n@49 1445 function timer()
n@49 1446 {
n@49 1447 /* Timer object used in audioEngine to keep track of session timings
n@49 1448 * Uses the timer of the web audio API, so sample resolution
n@49 1449 */
n@49 1450 this.testStarted = false;
n@49 1451 this.testStartTime = 0;
n@49 1452 this.testDuration = 0;
n@49 1453 this.minimumTestTime = 0; // No minimum test time
n@49 1454 this.startTest = function()
n@49 1455 {
n@49 1456 if (this.testStarted == false)
n@49 1457 {
n@49 1458 this.testStartTime = audioContext.currentTime;
n@49 1459 this.testStarted = true;
n@49 1460 this.updateTestTime();
n@52 1461 audioEngineContext.metric.initialiseTest();
n@49 1462 }
n@49 1463 };
n@49 1464 this.stopTest = function()
n@49 1465 {
n@49 1466 if (this.testStarted)
n@49 1467 {
n@49 1468 this.testDuration = this.getTestTime();
n@49 1469 this.testStarted = false;
n@49 1470 } else {
n@49 1471 console.log('ERR: Test tried to end before beginning');
n@49 1472 }
n@49 1473 };
n@49 1474 this.updateTestTime = function()
n@49 1475 {
n@49 1476 if (this.testStarted)
n@49 1477 {
n@49 1478 this.testDuration = audioContext.currentTime - this.testStartTime;
n@49 1479 }
n@49 1480 };
n@49 1481 this.getTestTime = function()
n@49 1482 {
n@49 1483 this.updateTestTime();
n@49 1484 return this.testDuration;
n@49 1485 };
n@49 1486 }
n@49 1487
n@377 1488 function sessionMetrics(engine,specification)
n@49 1489 {
n@49 1490 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
n@49 1491 */
n@49 1492 this.engine = engine;
n@49 1493 this.lastClicked = -1;
n@49 1494 this.data = -1;
n@113 1495 this.reset = function() {
n@113 1496 this.lastClicked = -1;
n@113 1497 this.data = -1;
n@113 1498 };
n@377 1499
n@377 1500 this.enableElementInitialPosition = false;
n@377 1501 this.enableElementListenTracker = false;
n@377 1502 this.enableElementTimer = false;
n@377 1503 this.enableElementTracker = false;
n@377 1504 this.enableFlagListenedTo = false;
n@377 1505 this.enableFlagMoved = false;
n@377 1506 this.enableTestTimer = false;
n@377 1507 // Obtain the metrics enabled
n@453 1508 for (var i=0; i<specification.metrics.enabled.length; i++)
n@377 1509 {
n@453 1510 var node = specification.metrics.enabled[i];
n@453 1511 switch(node)
n@377 1512 {
n@377 1513 case 'testTimer':
n@377 1514 this.enableTestTimer = true;
n@377 1515 break;
n@377 1516 case 'elementTimer':
n@377 1517 this.enableElementTimer = true;
n@377 1518 break;
n@377 1519 case 'elementTracker':
n@377 1520 this.enableElementTracker = true;
n@377 1521 break;
n@377 1522 case 'elementListenTracker':
n@377 1523 this.enableElementListenTracker = true;
n@377 1524 break;
n@377 1525 case 'elementInitialPosition':
n@377 1526 this.enableElementInitialPosition = true;
n@377 1527 break;
n@377 1528 case 'elementFlagListenedTo':
n@377 1529 this.enableFlagListenedTo = true;
n@377 1530 break;
n@377 1531 case 'elementFlagMoved':
n@377 1532 this.enableFlagMoved = true;
n@377 1533 break;
n@377 1534 case 'elementFlagComments':
n@377 1535 this.enableFlagComments = true;
n@377 1536 break;
n@377 1537 }
n@377 1538 }
n@52 1539 this.initialiseTest = function(){};
n@49 1540 }
n@49 1541
n@139 1542 function metricTracker(caller)
n@49 1543 {
n@49 1544 /* Custom object to track and collect metric data
n@49 1545 * Used only inside the audioObjects object.
n@49 1546 */
n@49 1547
n@49 1548 this.listenedTimer = 0;
n@49 1549 this.listenStart = 0;
nicholas@110 1550 this.listenHold = false;
n@51 1551 this.initialPosition = -1;
n@49 1552 this.movementTracker = [];
n@164 1553 this.listenTracker =[];
n@49 1554 this.wasListenedTo = false;
n@49 1555 this.wasMoved = false;
n@49 1556 this.hasComments = false;
n@139 1557 this.parent = caller;
n@49 1558
n@453 1559 this.initialise = function(position)
n@49 1560 {
n@51 1561 if (this.initialPosition == -1) {
n@51 1562 this.initialPosition = position;
n@454 1563 this.moved(0,position);
n@51 1564 }
n@49 1565 };
n@49 1566
n@49 1567 this.moved = function(time,position)
n@49 1568 {
n@454 1569 if (time > 0) {this.wasMoved = true;}
n@49 1570 this.movementTracker[this.movementTracker.length] = [time, position];
n@49 1571 };
n@49 1572
nicholas@132 1573 this.startListening = function(time)
n@49 1574 {
nicholas@110 1575 if (this.listenHold == false)
n@49 1576 {
n@49 1577 this.wasListenedTo = true;
n@49 1578 this.listenStart = time;
nicholas@110 1579 this.listenHold = true;
n@164 1580
n@164 1581 var evnt = document.createElement('event');
n@164 1582 var testTime = document.createElement('testTime');
n@164 1583 testTime.setAttribute('start',time);
n@164 1584 var bufferTime = document.createElement('bufferTime');
n@164 1585 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
n@164 1586 evnt.appendChild(testTime);
n@164 1587 evnt.appendChild(bufferTime);
n@164 1588 this.listenTracker.push(evnt);
n@164 1589
n@139 1590 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
n@139 1591 }
n@139 1592 };
nicholas@132 1593
n@203 1594 this.stopListening = function(time,bufferStopTime)
nicholas@132 1595 {
nicholas@132 1596 if (this.listenHold == true)
nicholas@132 1597 {
n@164 1598 var diff = time - this.listenStart;
n@164 1599 this.listenedTimer += (diff);
n@49 1600 this.listenStart = 0;
nicholas@110 1601 this.listenHold = false;
n@164 1602
n@164 1603 var evnt = this.listenTracker[this.listenTracker.length-1];
n@164 1604 var testTime = evnt.getElementsByTagName('testTime')[0];
n@164 1605 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
n@164 1606 testTime.setAttribute('stop',time);
n@203 1607 if (bufferStopTime == undefined) {
n@203 1608 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
n@203 1609 } else {
n@203 1610 bufferTime.setAttribute('stop',bufferStopTime);
n@203 1611 }
n@164 1612 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
n@49 1613 }
n@49 1614 };
n@177 1615
n@177 1616 this.exportXMLDOM = function() {
n@453 1617 var storeDOM = [];
n@177 1618 if (audioEngineContext.metric.enableElementTimer) {
n@453 1619 var mElementTimer = storage.document.createElement('metricresult');
n@177 1620 mElementTimer.setAttribute('name','enableElementTimer');
n@177 1621 mElementTimer.textContent = this.listenedTimer;
n@453 1622 storeDOM.push(mElementTimer);
n@177 1623 }
n@177 1624 if (audioEngineContext.metric.enableElementTracker) {
n@453 1625 var elementTrackerFull = storage.document.createElement('metricResult');
n@177 1626 elementTrackerFull.setAttribute('name','elementTrackerFull');
n@177 1627 for (var k=0; k<this.movementTracker.length; k++)
n@177 1628 {
n@575 1629 var timePos = storage.document.createElement('movement');
n@575 1630 timePos.setAttribute("time",this.movementTracker[k][0]);
n@575 1631 timePos.setAttribute("value",this.movementTracker[k][1]);
n@177 1632 elementTrackerFull.appendChild(timePos);
n@177 1633 }
n@453 1634 storeDOM.push(elementTrackerFull);
n@177 1635 }
n@177 1636 if (audioEngineContext.metric.enableElementListenTracker) {
n@453 1637 var elementListenTracker = storage.document.createElement('metricResult');
n@177 1638 elementListenTracker.setAttribute('name','elementListenTracker');
n@177 1639 for (var k=0; k<this.listenTracker.length; k++) {
n@177 1640 elementListenTracker.appendChild(this.listenTracker[k]);
n@177 1641 }
n@453 1642 storeDOM.push(elementListenTracker);
n@177 1643 }
n@177 1644 if (audioEngineContext.metric.enableElementInitialPosition) {
n@453 1645 var elementInitial = storage.document.createElement('metricResult');
n@177 1646 elementInitial.setAttribute('name','elementInitialPosition');
n@177 1647 elementInitial.textContent = this.initialPosition;
n@453 1648 storeDOM.push(elementInitial);
n@177 1649 }
n@177 1650 if (audioEngineContext.metric.enableFlagListenedTo) {
n@453 1651 var flagListenedTo = storage.document.createElement('metricResult');
n@177 1652 flagListenedTo.setAttribute('name','elementFlagListenedTo');
n@177 1653 flagListenedTo.textContent = this.wasListenedTo;
n@453 1654 storeDOM.push(flagListenedTo);
n@177 1655 }
n@177 1656 if (audioEngineContext.metric.enableFlagMoved) {
n@453 1657 var flagMoved = storage.document.createElement('metricResult');
n@177 1658 flagMoved.setAttribute('name','elementFlagMoved');
n@177 1659 flagMoved.textContent = this.wasMoved;
n@453 1660 storeDOM.push(flagMoved);
n@177 1661 }
n@177 1662 if (audioEngineContext.metric.enableFlagComments) {
n@453 1663 var flagComments = storage.document.createElement('metricResult');
n@177 1664 flagComments.setAttribute('name','elementFlagComments');
n@177 1665 if (this.parent.commentDOM == null)
n@177 1666 {flag.textContent = 'false';}
n@177 1667 else if (this.parent.commentDOM.textContent.length == 0)
n@177 1668 {flag.textContent = 'false';}
n@177 1669 else
n@177 1670 {flag.textContet = 'true';}
n@453 1671 storeDOM.push(flagComments);
n@177 1672 }
n@453 1673 return storeDOM;
n@177 1674 };
n@54 1675 }
n@54 1676
n@54 1677 function randomiseOrder(input)
n@54 1678 {
n@54 1679 // This takes an array of information and randomises the order
n@54 1680 var N = input.length;
b@207 1681
b@207 1682 var inputSequence = []; // For safety purposes: keep track of randomisation
b@207 1683 for (var counter = 0; counter < N; ++counter)
b@207 1684 inputSequence.push(counter) // Fill array
b@207 1685 var inputSequenceClone = inputSequence.slice(0);
b@207 1686
n@54 1687 var holdArr = [];
b@207 1688 var outputSequence = [];
n@54 1689 for (var n=0; n<N; n++)
n@54 1690 {
n@54 1691 // First pick a random number
n@54 1692 var r = Math.random();
n@54 1693 // Multiply and floor by the number of elements left
n@54 1694 r = Math.floor(r*input.length);
n@54 1695 // Pick out that element and delete from the array
n@54 1696 holdArr.push(input.splice(r,1)[0]);
b@207 1697 // Do the same with sequence
b@207 1698 outputSequence.push(inputSequence.splice(r,1)[0]);
n@54 1699 }
b@207 1700 console.log(inputSequenceClone.toString()); // print original array to console
b@207 1701 console.log(outputSequence.toString()); // print randomised array to console
n@54 1702 return holdArr;
n@125 1703 }
n@125 1704
n@125 1705 function returnDateNode()
n@125 1706 {
n@125 1707 // Create an XML Node for the Date and Time a test was conducted
n@125 1708 // Structure is
n@125 1709 // <datetime>
n@125 1710 // <date year="##" month="##" day="##">DD/MM/YY</date>
n@125 1711 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
n@125 1712 // </datetime>
n@125 1713 var dateTime = new Date();
n@125 1714 var year = document.createAttribute('year');
n@125 1715 var month = document.createAttribute('month');
n@125 1716 var day = document.createAttribute('day');
n@125 1717 var hour = document.createAttribute('hour');
n@125 1718 var minute = document.createAttribute('minute');
n@125 1719 var secs = document.createAttribute('secs');
n@125 1720
n@125 1721 year.nodeValue = dateTime.getFullYear();
n@125 1722 month.nodeValue = dateTime.getMonth()+1;
n@125 1723 day.nodeValue = dateTime.getDate();
n@125 1724 hour.nodeValue = dateTime.getHours();
n@125 1725 minute.nodeValue = dateTime.getMinutes();
n@125 1726 secs.nodeValue = dateTime.getSeconds();
n@125 1727
n@125 1728 var hold = document.createElement("datetime");
n@125 1729 var date = document.createElement("date");
n@125 1730 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
n@125 1731 var time = document.createElement("time");
n@125 1732 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
n@125 1733
n@125 1734 date.setAttributeNode(year);
n@125 1735 date.setAttributeNode(month);
n@125 1736 date.setAttributeNode(day);
n@125 1737 time.setAttributeNode(hour);
n@125 1738 time.setAttributeNode(minute);
n@125 1739 time.setAttributeNode(secs);
n@125 1740
n@125 1741 hold.appendChild(date);
n@125 1742 hold.appendChild(time);
n@377 1743 return hold;
n@125 1744
nicholas@135 1745 }
nicholas@135 1746
n@180 1747 function Specification() {
n@180 1748 // Handles the decoding of the project specification XML into a simple JavaScript Object.
n@180 1749
n@453 1750 this.interface = null;
n@504 1751 this.projectReturn = "null";
n@453 1752 this.randomiseOrder = null;
n@453 1753 this.testPages = null;
n@453 1754 this.pages = [];
n@453 1755 this.metrics = null;
n@453 1756 this.interfaces = null;
n@453 1757 this.loudness = null;
n@453 1758 this.errors = [];
n@453 1759 this.schema = null;
n@380 1760
n@453 1761 this.processAttribute = function(attribute,schema)
n@453 1762 {
n@453 1763 // attribute is the string returned from getAttribute on the XML
n@453 1764 // schema is the <xs:attribute> node
n@453 1765 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
n@453 1766 {
n@477 1767 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
n@453 1768 }
n@453 1769 var defaultOpt = schema.getAttribute('default');
n@453 1770 if (attribute == null) {
n@453 1771 attribute = defaultOpt;
n@453 1772 }
n@453 1773 var dataType = schema.getAttribute('type');
n@453 1774 if (typeof dataType == "string") { dataType = dataType.substr(3);}
n@453 1775 else {dataType = "string";}
n@453 1776 if (attribute == null)
n@453 1777 {
n@453 1778 return attribute;
n@453 1779 }
n@453 1780 switch(dataType)
n@453 1781 {
n@453 1782 case "boolean":
n@453 1783 if (attribute == 'true'){attribute = true;}else{attribute=false;}
n@453 1784 break;
n@453 1785 case "negativeInteger":
n@453 1786 case "positiveInteger":
n@453 1787 case "nonNegativeInteger":
n@453 1788 case "nonPositiveInteger":
n@453 1789 case "integer":
n@453 1790 case "decimal":
n@453 1791 case "short":
n@453 1792 attribute = Number(attribute);
n@453 1793 break;
n@453 1794 case "string":
n@453 1795 default:
n@453 1796 attribute = String(attribute);
n@453 1797 break;
n@453 1798 }
n@453 1799 return attribute;
n@453 1800 };
n@180 1801
n@374 1802 this.decode = function(projectXML) {
n@453 1803 this.errors = [];
n@180 1804 // projectXML - DOM Parsed document
nicholas@240 1805 this.projectXML = projectXML.childNodes[0];
n@180 1806 var setupNode = projectXML.getElementsByTagName('setup')[0];
n@477 1807 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@453 1808 // First decode the attributes
n@477 1809 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@453 1810 for (var i in attributes)
n@297 1811 {
n@453 1812 if (isNaN(Number(i)) == true){break;}
n@453 1813 var attributeName = attributes[i].getAttribute('name');
n@453 1814 var projectAttr = setupNode.getAttribute(attributeName);
n@453 1815 projectAttr = this.processAttribute(projectAttr,attributes[i]);
n@453 1816 switch(typeof projectAttr)
n@410 1817 {
n@453 1818 case "number":
n@453 1819 case "boolean":
n@453 1820 eval('this.'+attributeName+' = '+projectAttr);
n@453 1821 break;
n@453 1822 case "string":
n@453 1823 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 1824 break;
n@410 1825 }
n@453 1826
n@374 1827 }
n@374 1828
n@501 1829 this.metrics = new this.metricNode();
n@180 1830
n@453 1831 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
n@453 1832
n@453 1833 // Now process the survey node options
n@453 1834 var survey = setupNode.getElementsByTagName('survey');
n@453 1835 for (var i in survey) {
n@453 1836 if (isNaN(Number(i)) == true){break;}
n@453 1837 var location = survey[i].getAttribute('location');
n@453 1838 if (location == 'pre' || location == 'before')
n@453 1839 {
n@453 1840 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@453 1841 else {
n@453 1842 this.preTest = new this.surveyNode();
n@501 1843 this.preTest.decode(this,survey[i]);
n@453 1844 }
n@453 1845 } else if (location == 'post' || location == 'after') {
n@453 1846 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@453 1847 else {
n@453 1848 this.postTest = new this.surveyNode();
n@501 1849 this.postTest.decode(this,survey[i]);
n@453 1850 }
n@180 1851 }
n@180 1852 }
n@180 1853
n@453 1854 var interfaceNode = setupNode.getElementsByTagName('interface');
n@453 1855 if (interfaceNode.length > 1)
n@453 1856 {
n@453 1857 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
n@453 1858 }
n@453 1859 this.interfaces = new this.interfaceNode();
n@453 1860 if (interfaceNode.length != 0)
n@453 1861 {
n@453 1862 interfaceNode = interfaceNode[0];
n@477 1863 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
nicholas@213 1864 }
nicholas@213 1865
n@453 1866 // Page tags
n@453 1867 var pageTags = projectXML.getElementsByTagName('page');
n@477 1868 var pageSchema = this.schema.getAllElementsByName('page')[0];
n@453 1869 for (var i=0; i<pageTags.length; i++)
n@297 1870 {
n@453 1871 var node = new this.page();
n@453 1872 node.decode(this,pageTags[i],pageSchema);
n@453 1873 this.pages.push(node);
n@297 1874 }
n@180 1875 };
n@180 1876
n@374 1877 this.encode = function()
n@374 1878 {
n@503 1879 var RootDocument = document.implementation.createDocument(null,"waet");
n@503 1880 var root = RootDocument.children[0];
n@503 1881 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
n@503 1882 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
n@453 1883 // Build setup node
n@503 1884 var setup = RootDocument.createElement("setup");
n@503 1885 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@503 1886 // First decode the attributes
n@503 1887 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@503 1888 for (var i=0; i<attributes.length; i++)
n@503 1889 {
n@503 1890 var name = attributes[i].getAttribute("name");
n@503 1891 if (name == undefined) {
n@503 1892 name = attributes[i].getAttribute("ref");
n@503 1893 }
n@503 1894 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 1895 {
n@503 1896 eval("setup.setAttribute('"+name+"',this."+name+")");
n@503 1897 }
n@503 1898 }
n@503 1899 root.appendChild(setup);
n@503 1900 // Survey node
n@503 1901 setup.appendChild(this.preTest.encode(RootDocument));
n@503 1902 setup.appendChild(this.postTest.encode(RootDocument));
n@503 1903 setup.appendChild(this.metrics.encode(RootDocument));
n@503 1904 setup.appendChild(this.interfaces.encode(RootDocument));
n@503 1905 for (var page of this.pages)
n@503 1906 {
n@503 1907 root.appendChild(page.encode(RootDocument));
n@503 1908 }
n@503 1909 return RootDocument;
n@374 1910 };
n@374 1911
n@453 1912 this.surveyNode = function() {
n@453 1913 this.location = null;
n@180 1914 this.options = [];
n@501 1915 this.schema = specification.schema.getAllElementsByName('survey')[0];
n@180 1916
n@374 1917 this.OptionNode = function() {
n@374 1918 this.type = undefined;
n@501 1919 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
n@374 1920 this.id = undefined;
n@597 1921 this.name = undefined;
n@374 1922 this.mandatory = undefined;
n@374 1923 this.statement = undefined;
n@374 1924 this.boxsize = undefined;
n@374 1925 this.options = [];
n@374 1926 this.min = undefined;
n@374 1927 this.max = undefined;
n@374 1928 this.step = undefined;
n@374 1929
n@501 1930 this.decode = function(parent,child)
n@374 1931 {
n@501 1932 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 1933 for (var i in attributeMap){
n@453 1934 if(isNaN(Number(i)) == true){break;}
n@453 1935 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 1936 var projectAttr = child.getAttribute(attributeName);
n@453 1937 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
n@453 1938 switch(typeof projectAttr)
n@453 1939 {
n@453 1940 case "number":
n@453 1941 case "boolean":
n@453 1942 eval('this.'+attributeName+' = '+projectAttr);
n@453 1943 break;
n@453 1944 case "string":
n@453 1945 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 1946 break;
n@374 1947 }
n@453 1948 }
n@453 1949 this.statement = child.getElementsByTagName('statement')[0].textContent;
n@453 1950 if (this.type == "checkbox" || this.type == "radio") {
n@453 1951 var children = child.getElementsByTagName('option');
n@453 1952 if (children.length == null) {
n@374 1953 console.log('Malformed' +child.nodeName+ 'entry');
n@374 1954 this.statement = 'Malformed' +child.nodeName+ 'entry';
n@374 1955 this.type = 'statement';
n@374 1956 } else {
n@374 1957 this.options = [];
n@453 1958 for (var i in children)
n@453 1959 {
n@453 1960 if (isNaN(Number(i))==true){break;}
n@453 1961 this.options.push({
n@453 1962 name: children[i].getAttribute('name'),
n@453 1963 text: children[i].textContent
n@453 1964 });
n@374 1965 }
n@374 1966 }
n@191 1967 }
n@374 1968 };
n@374 1969
n@503 1970 this.exportXML = function(doc)
n@374 1971 {
n@544 1972 var node = doc.createElement('surveyentry');
n@453 1973 node.setAttribute('type',this.type);
n@503 1974 var statement = doc.createElement('statement');
n@453 1975 statement.textContent = this.statement;
n@453 1976 node.appendChild(statement);
n@597 1977 if (this.type != "statement") {
n@544 1978 node.id = this.id;
n@597 1979 if (this.name != undefined) { node.setAttribute("name",this.name);}
n@544 1980 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
n@597 1981 switch(this.type)
n@597 1982 {
n@597 1983 case "question":
n@597 1984 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
n@597 1985 break;
n@597 1986 case "number":
n@597 1987 if (this.min != undefined) {node.setAttribute("min", this.min);}
n@597 1988 if (this.max != undefined) {node.setAttribute("max", this.max);}
n@597 1989 break;
n@597 1990 case "checkbox":
n@597 1991 case "radio":
n@597 1992 for (var i=0; i<this.options.length; i++)
n@597 1993 {
n@597 1994 var option = this.options[i];
n@597 1995 var optionNode = doc.createElement("option");
n@597 1996 optionNode.setAttribute("name",option.name);
n@597 1997 optionNode.textContent = option.text;
n@597 1998 node.appendChild(optionNode);
n@597 1999 }
n@597 2000 break;
n@597 2001 }
n@597 2002 }
n@374 2003 return node;
n@374 2004 };
n@374 2005 };
n@501 2006 this.decode = function(parent,xml) {
n@453 2007 this.location = xml.getAttribute('location');
n@453 2008 if (this.location == 'before'){this.location = 'pre';}
n@453 2009 else if (this.location == 'after'){this.location = 'post';}
n@453 2010 for (var i in xml.children)
n@453 2011 {
n@453 2012 if(isNaN(Number(i))==true){break;}
n@374 2013 var node = new this.OptionNode();
n@501 2014 node.decode(parent,xml.children[i]);
n@374 2015 this.options.push(node);
n@453 2016 }
n@453 2017 };
n@503 2018 this.encode = function(doc) {
n@503 2019 var node = doc.createElement('survey');
n@453 2020 node.setAttribute('location',this.location);
n@453 2021 for (var i=0; i<this.options.length; i++)
n@453 2022 {
n@503 2023 node.appendChild(this.options[i].exportXML(doc));
n@453 2024 }
n@453 2025 return node;
n@453 2026 };
n@453 2027 };
n@453 2028
n@453 2029 this.interfaceNode = function()
n@453 2030 {
n@453 2031 this.title = null;
n@453 2032 this.name = null;
n@453 2033 this.options = [];
n@453 2034 this.scales = [];
n@501 2035 this.schema = specification.schema.getAllElementsByName('interface')[1];
n@453 2036
n@501 2037 this.decode = function(parent,xml) {
n@453 2038 this.name = xml.getAttribute('name');
n@453 2039 var titleNode = xml.getElementsByTagName('title');
n@453 2040 if (titleNode.length == 1)
n@453 2041 {
n@453 2042 this.title = titleNode[0].textContent;
n@453 2043 }
n@453 2044 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
n@453 2045 // Extract interfaceoption node schema
n@501 2046 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
n@477 2047 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
n@453 2048 for (var i=0; i<interfaceOptionNodes.length; i++)
n@453 2049 {
n@453 2050 var ioNode = interfaceOptionNodes[i];
n@453 2051 var option = {};
n@453 2052 for (var j=0; j<attributeMap.length; j++)
n@453 2053 {
n@453 2054 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
n@453 2055 var projectAttr = ioNode.getAttribute(attributeName);
n@453 2056 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
n@453 2057 switch(typeof projectAttr)
n@453 2058 {
n@453 2059 case "number":
n@453 2060 case "boolean":
n@453 2061 eval('option.'+attributeName+' = '+projectAttr);
n@453 2062 break;
n@453 2063 case "string":
n@453 2064 eval('option.'+attributeName+' = "'+projectAttr+'"');
n@453 2065 break;
n@453 2066 }
n@453 2067 }
n@453 2068 this.options.push(option);
n@453 2069 }
n@453 2070
n@453 2071 // Now the scales nodes
n@453 2072 var scaleParent = xml.getElementsByTagName('scales');
n@453 2073 if (scaleParent.length == 1) {
n@453 2074 scaleParent = scaleParent[0];
n@453 2075 for (var i=0; i<scaleParent.children.length; i++) {
n@453 2076 var child = scaleParent.children[i];
n@453 2077 this.scales.push({
n@453 2078 text: child.textContent,
n@453 2079 position: Number(child.getAttribute('position'))
n@453 2080 });
n@374 2081 }
n@180 2082 }
n@180 2083 };
n@453 2084
n@503 2085 this.encode = function(doc) {
n@503 2086 var node = doc.createElement("interface");
n@503 2087 if (typeof name == "string")
n@503 2088 node.setAttribute("name",this.name);
n@503 2089 for (var option of this.options)
n@503 2090 {
n@503 2091 var child = doc.createElement("interfaceoption");
n@503 2092 child.setAttribute("type",option.type);
n@503 2093 child.setAttribute("name",option.name);
n@503 2094 node.appendChild(child);
n@503 2095 }
n@503 2096 if (this.scales.length != 0) {
n@503 2097 var scales = doc.createElement("scales");
n@503 2098 for (var scale of this.scales)
n@503 2099 {
n@503 2100 var child = doc.createElement("scalelabel");
n@503 2101 child.setAttribute("position",scale.position);
n@503 2102 child.textContent = scale.text;
n@503 2103 scales.appendChild(child);
n@503 2104 }
n@503 2105 node.appendChild(scales);
n@503 2106 }
n@503 2107 return node;
n@453 2108 };
n@180 2109 };
n@180 2110
n@501 2111 this.metricNode = function() {
n@501 2112 this.enabled = [];
n@501 2113 this.decode = function(parent, xml) {
n@501 2114 var children = xml.getElementsByTagName('metricenable');
n@501 2115 for (var i in children) {
n@501 2116 if (isNaN(Number(i)) == true){break;}
n@501 2117 this.enabled.push(children[i].textContent);
n@501 2118 }
n@501 2119 }
n@503 2120 this.encode = function(doc) {
n@503 2121 var node = doc.createElement('metric');
n@501 2122 for (var i in this.enabled)
n@501 2123 {
n@501 2124 if (isNaN(Number(i)) == true){break;}
n@503 2125 var child = doc.createElement('metricenable');
n@501 2126 child.textContent = this.enabled[i];
n@501 2127 node.appendChild(child);
n@501 2128 }
n@501 2129 return node;
n@501 2130 }
n@501 2131 }
n@501 2132
n@453 2133 this.page = function() {
n@374 2134 this.presentedId = undefined;
n@374 2135 this.id = undefined;
n@374 2136 this.hostURL = undefined;
n@374 2137 this.randomiseOrder = undefined;
n@374 2138 this.loop = undefined;
n@453 2139 this.showElementComments = undefined;
n@374 2140 this.outsideReference = null;
n@410 2141 this.loudness = null;
n@453 2142 this.preTest = null;
n@453 2143 this.postTest = null;
n@374 2144 this.interfaces = [];
n@374 2145 this.commentBoxPrefix = "Comment on track";
n@374 2146 this.audioElements = [];
n@374 2147 this.commentQuestions = [];
n@501 2148 this.schema = specification.schema.getAllElementsByName("page")[0];
n@374 2149
n@501 2150 this.decode = function(parent,xml)
n@374 2151 {
n@477 2152 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2153 for (var i=0; i<attributeMap.length; i++)
n@410 2154 {
n@453 2155 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2156 var projectAttr = xml.getAttribute(attributeName);
n@453 2157 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
n@453 2158 switch(typeof projectAttr)
nicholas@417 2159 {
n@453 2160 case "number":
n@453 2161 case "boolean":
n@453 2162 eval('this.'+attributeName+' = '+projectAttr);
n@453 2163 break;
n@453 2164 case "string":
n@453 2165 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2166 break;
n@374 2167 }
n@374 2168 }
n@374 2169
n@453 2170 // Get the Comment Box Prefix
n@453 2171 var CBP = xml.getElementsByTagName('commentboxprefix');
n@453 2172 if (CBP.length != 0) {
n@453 2173 this.commentBoxPrefix = CBP[0].textContent;
n@427 2174 }
n@427 2175
n@453 2176 // Now decode the interfaces
n@453 2177 var interfaceNode = xml.getElementsByTagName('interface');
n@453 2178 for (var i=0; i<interfaceNode.length; i++)
n@453 2179 {
n@453 2180 var node = new parent.interfaceNode();
n@477 2181 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
n@453 2182 this.interfaces.push(node);
n@453 2183 }
n@380 2184
n@453 2185 // Now process the survey node options
n@453 2186 var survey = xml.getElementsByTagName('survey');
n@477 2187 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
n@453 2188 for (var i in survey) {
n@453 2189 if (isNaN(Number(i)) == true){break;}
n@453 2190 var location = survey[i].getAttribute('location');
n@453 2191 if (location == 'pre' || location == 'before')
n@453 2192 {
n@453 2193 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@453 2194 else {
n@453 2195 this.preTest = new parent.surveyNode();
n@453 2196 this.preTest.decode(parent,survey[i],surveySchema);
n@453 2197 }
n@453 2198 } else if (location == 'post' || location == 'after') {
n@453 2199 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@453 2200 else {
n@453 2201 this.postTest = new parent.surveyNode();
n@453 2202 this.postTest.decode(parent,survey[i],surveySchema);
n@453 2203 }
n@453 2204 }
n@453 2205 }
n@453 2206
n@453 2207 // Now process the audioelement tags
n@453 2208 var audioElements = xml.getElementsByTagName('audioelement');
n@453 2209 for (var i=0; i<audioElements.length; i++)
n@453 2210 {
n@453 2211 var node = new this.audioElementNode();
n@501 2212 node.decode(this,audioElements[i]);
n@453 2213 this.audioElements.push(node);
n@453 2214 }
n@453 2215
n@453 2216 // Now decode the commentquestions
n@453 2217 var commentQuestions = xml.getElementsByTagName('commentquestion');
n@453 2218 for (var i=0; i<commentQuestions.length; i++)
n@453 2219 {
n@374 2220 var node = new this.commentQuestionNode();
n@501 2221 node.decode(parent,commentQuestions[i]);
n@374 2222 this.commentQuestions.push(node);
n@180 2223 }
n@180 2224 };
n@180 2225
n@374 2226 this.encode = function(root)
n@374 2227 {
n@503 2228 var AHNode = root.createElement("page");
n@503 2229 // First decode the attributes
n@503 2230 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@503 2231 for (var i=0; i<attributes.length; i++)
n@503 2232 {
n@503 2233 var name = attributes[i].getAttribute("name");
n@503 2234 if (name == undefined) {
n@503 2235 name = attributes[i].getAttribute("ref");
n@503 2236 }
n@503 2237 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 2238 {
n@503 2239 eval("AHNode.setAttribute('"+name+"',this."+name+")");
n@503 2240 }
n@503 2241 }
n@410 2242 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
n@503 2243 // <commentboxprefix>
n@503 2244 var commentboxprefix = root.createElement("commentboxprefix");
n@503 2245 commentboxprefix.textContent = this.commentBoxPrefix;
n@503 2246 AHNode.appendChild(commentboxprefix);
n@503 2247
n@374 2248 for (var i=0; i<this.interfaces.length; i++)
n@324 2249 {
n@374 2250 AHNode.appendChild(this.interfaces[i].encode(root));
n@374 2251 }
n@374 2252
n@374 2253 for (var i=0; i<this.audioElements.length; i++) {
n@374 2254 AHNode.appendChild(this.audioElements[i].encode(root));
n@374 2255 }
n@374 2256 // Create <CommentQuestion>
n@374 2257 for (var i=0; i<this.commentQuestions.length; i++)
n@374 2258 {
n@503 2259 AHNode.appendChild(this.commentQuestions[i].encode(root));
n@374 2260 }
n@374 2261
n@503 2262 AHNode.appendChild(this.preTest.encode(root));
n@503 2263 AHNode.appendChild(this.postTest.encode(root));
n@374 2264 return AHNode;
n@374 2265 };
n@374 2266
n@453 2267 this.commentQuestionNode = function() {
n@453 2268 this.id = null;
n@597 2269 this.name = undefined;
n@453 2270 this.type = undefined;
n@374 2271 this.options = [];
n@453 2272 this.statement = undefined;
n@501 2273 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
n@501 2274 this.decode = function(parent,xml)
n@374 2275 {
n@453 2276 this.id = xml.id;
n@597 2277 this.name = xml.getAttribute('name');
n@453 2278 this.type = xml.getAttribute('type');
n@453 2279 this.statement = xml.getElementsByTagName('statement')[0].textContent;
n@453 2280 var optNodes = xml.getElementsByTagName('option');
n@453 2281 for (var i=0; i<optNodes.length; i++)
n@453 2282 {
n@453 2283 var optNode = optNodes[i];
n@453 2284 this.options.push({
n@453 2285 name: optNode.getAttribute('name'),
n@453 2286 text: optNode.textContent
n@453 2287 });
n@374 2288 }
n@374 2289 };
n@453 2290
n@374 2291 this.encode = function(root)
n@374 2292 {
n@503 2293 var node = root.createElement("commentquestion");
n@503 2294 node.id = this.id;
n@503 2295 node.setAttribute("type",this.type);
n@597 2296 if (this.name != undefined){node.setAttribute("name",this.name);}
n@503 2297 var statement = root.createElement("statement");
n@503 2298 statement.textContent = this.statement;
n@503 2299 node.appendChild(statement);
n@503 2300 for (var option of this.options)
n@503 2301 {
n@503 2302 var child = root.createElement("option");
n@503 2303 child.setAttribute("name",option.name);
n@503 2304 child.textContent = option.text;
n@503 2305 node.appendChild(child);
n@503 2306 }
n@503 2307 return node;
n@374 2308 };
n@374 2309 };
n@374 2310
n@374 2311 this.audioElementNode = function() {
n@374 2312 this.url = null;
n@374 2313 this.id = null;
n@597 2314 this.name = null;
n@374 2315 this.parent = null;
n@453 2316 this.type = null;
n@525 2317 this.marker = null;
n@374 2318 this.enforce = false;
n@564 2319 this.gain = 0.0;
n@501 2320 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
n@453 2321 this.parent = null;
n@501 2322 this.decode = function(parent,xml)
n@374 2323 {
n@374 2324 this.parent = parent;
n@477 2325 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2326 for (var i=0; i<attributeMap.length; i++)
n@400 2327 {
n@453 2328 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2329 var projectAttr = xml.getAttribute(attributeName);
n@453 2330 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
n@453 2331 switch(typeof projectAttr)
n@374 2332 {
n@453 2333 case "number":
n@453 2334 case "boolean":
n@453 2335 eval('this.'+attributeName+' = '+projectAttr);
n@453 2336 break;
n@453 2337 case "string":
n@453 2338 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2339 break;
n@324 2340 }
n@324 2341 }
n@453 2342
n@374 2343 };
n@374 2344 this.encode = function(root)
n@374 2345 {
n@503 2346 var AENode = root.createElement("audioelement");
n@503 2347 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@503 2348 for (var i=0; i<attributes.length; i++)
n@503 2349 {
n@503 2350 var name = attributes[i].getAttribute("name");
n@503 2351 if (name == undefined) {
n@503 2352 name = attributes[i].getAttribute("ref");
n@503 2353 }
n@503 2354 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 2355 {
n@503 2356 eval("AENode.setAttribute('"+name+"',this."+name+")");
n@503 2357 }
n@503 2358 }
n@374 2359 return AENode;
n@374 2360 };
n@180 2361 };
n@180 2362 };
n@180 2363 }
n@374 2364
n@182 2365 function Interface(specificationObject) {
n@180 2366 // This handles the bindings between the interface and the audioEngineContext;
n@182 2367 this.specification = specificationObject;
n@182 2368 this.insertPoint = document.getElementById("topLevelBody");
n@180 2369
n@453 2370 this.newPage = function(audioHolderObject,store)
n@375 2371 {
n@500 2372 audioEngineContext.newTestPage(audioHolderObject,store);
n@550 2373 interfaceContext.commentBoxes.deleteCommentBoxes();
n@375 2374 interfaceContext.deleteCommentQuestions();
n@453 2375 loadTest(audioHolderObject,store);
n@375 2376 };
n@375 2377
n@182 2378 // Bounded by interface!!
n@182 2379 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
n@182 2380 // For example, APE returns the slider position normalised in a <value> tag.
n@182 2381 this.interfaceObjects = [];
n@182 2382 this.interfaceObject = function(){};
n@182 2383
n@302 2384 this.resizeWindow = function(event)
n@302 2385 {
n@395 2386 popup.resize(event);
n@302 2387 for(var i=0; i<this.commentBoxes.length; i++)
n@302 2388 {this.commentBoxes[i].resize();}
n@302 2389 for(var i=0; i<this.commentQuestions.length; i++)
n@302 2390 {this.commentQuestions[i].resize();}
n@302 2391 try
n@302 2392 {
n@302 2393 resizeWindow(event);
n@302 2394 }
n@302 2395 catch(err)
n@302 2396 {
n@302 2397 console.log("Warning - Interface does not have Resize option");
n@302 2398 console.log(err);
n@302 2399 }
n@302 2400 };
n@302 2401
n@356 2402 this.returnNavigator = function()
n@356 2403 {
n@491 2404 var node = storage.document.createElement("navigator");
n@491 2405 var platform = storage.document.createElement("platform");
n@356 2406 platform.textContent = navigator.platform;
n@491 2407 var vendor = storage.document.createElement("vendor");
n@356 2408 vendor.textContent = navigator.vendor;
n@491 2409 var userAgent = storage.document.createElement("uagent");
n@356 2410 userAgent.textContent = navigator.userAgent;
n@491 2411 var screen = storage.document.createElement("window");
n@491 2412 screen.setAttribute('innerWidth',window.innerWidth);
n@491 2413 screen.setAttribute('innerHeight',window.innerHeight);
n@356 2414 node.appendChild(platform);
n@356 2415 node.appendChild(vendor);
n@356 2416 node.appendChild(userAgent);
n@491 2417 node.appendChild(screen);
n@356 2418 return node;
n@356 2419 };
n@356 2420
n@550 2421 this.commentBoxes = new function() {
n@550 2422 this.boxes = [];
n@550 2423 this.injectPoint = null;
n@550 2424 this.elementCommentBox = function(audioObject) {
n@550 2425 var element = audioObject.specification;
n@550 2426 this.audioObject = audioObject;
n@550 2427 this.id = audioObject.id;
n@550 2428 var audioHolderObject = audioObject.specification.parent;
n@550 2429 // Create document objects to hold the comment boxes
n@550 2430 this.trackComment = document.createElement('div');
n@550 2431 this.trackComment.className = 'comment-div';
n@550 2432 this.trackComment.id = 'comment-div-'+audioObject.id;
n@550 2433 // Create a string next to each comment asking for a comment
n@550 2434 this.trackString = document.createElement('span');
n@550 2435 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
n@550 2436 // Create the HTML5 comment box 'textarea'
n@550 2437 this.trackCommentBox = document.createElement('textarea');
n@550 2438 this.trackCommentBox.rows = '4';
n@550 2439 this.trackCommentBox.cols = '100';
n@550 2440 this.trackCommentBox.name = 'trackComment'+audioObject.id;
n@550 2441 this.trackCommentBox.className = 'trackComment';
n@550 2442 var br = document.createElement('br');
n@550 2443 // Add to the holder.
n@550 2444 this.trackComment.appendChild(this.trackString);
n@550 2445 this.trackComment.appendChild(br);
n@550 2446 this.trackComment.appendChild(this.trackCommentBox);
n@550 2447
n@550 2448 this.exportXMLDOM = function() {
n@550 2449 var root = document.createElement('comment');
n@550 2450 var question = document.createElement('question');
n@550 2451 question.textContent = this.trackString.textContent;
n@550 2452 var response = document.createElement('response');
n@550 2453 response.textContent = this.trackCommentBox.value;
n@550 2454 console.log("Comment frag-"+this.id+": "+response.textContent);
n@550 2455 root.appendChild(question);
n@550 2456 root.appendChild(response);
n@550 2457 return root;
n@550 2458 };
n@550 2459 this.resize = function()
n@550 2460 {
n@550 2461 var boxwidth = (window.innerWidth-100)/2;
n@550 2462 if (boxwidth >= 600)
n@550 2463 {
n@550 2464 boxwidth = 600;
n@550 2465 }
n@550 2466 else if (boxwidth < 400)
n@550 2467 {
n@550 2468 boxwidth = 400;
n@550 2469 }
n@550 2470 this.trackComment.style.width = boxwidth+"px";
n@550 2471 this.trackCommentBox.style.width = boxwidth-6+"px";
n@550 2472 };
n@550 2473 this.resize();
n@550 2474 };
n@550 2475 this.createCommentBox = function(audioObject) {
n@550 2476 var node = new this.elementCommentBox(audioObject);
n@550 2477 this.boxes.push(node);
n@550 2478 audioObject.commentDOM = node;
n@550 2479 return node;
n@550 2480 };
n@550 2481 this.sortCommentBoxes = function() {
n@550 2482 this.boxes.sort(function(a,b){return a.id - b.id;});
n@550 2483 };
n@550 2484
n@550 2485 this.showCommentBoxes = function(inject, sort) {
n@550 2486 this.injectPoint = inject;
n@550 2487 if (sort) {this.sortCommentBoxes();}
n@550 2488 for (var box of this.boxes) {
n@550 2489 inject.appendChild(box.trackComment);
n@550 2490 }
n@550 2491 };
n@550 2492
n@550 2493 this.deleteCommentBoxes = function() {
n@550 2494 if (this.injectPoint != null) {
n@550 2495 for (var box of this.boxes) {
n@550 2496 this.injectPoint.removeChild(box.trackComment);
n@550 2497 }
n@550 2498 this.injectPoint = null;
n@550 2499 }
n@550 2500 this.boxes = [];
n@550 2501 };
n@550 2502 }
n@182 2503
n@193 2504 this.commentQuestions = [];
n@193 2505
n@193 2506 this.commentBox = function(commentQuestion) {
n@193 2507 this.specification = commentQuestion;
n@193 2508 // Create document objects to hold the comment boxes
n@193 2509 this.holder = document.createElement('div');
n@193 2510 this.holder.className = 'comment-div';
n@193 2511 // Create a string next to each comment asking for a comment
n@193 2512 this.string = document.createElement('span');
n@453 2513 this.string.innerHTML = commentQuestion.statement;
n@193 2514 // Create the HTML5 comment box 'textarea'
n@193 2515 this.textArea = document.createElement('textarea');
n@193 2516 this.textArea.rows = '4';
n@193 2517 this.textArea.cols = '100';
n@193 2518 this.textArea.className = 'trackComment';
n@193 2519 var br = document.createElement('br');
n@193 2520 // Add to the holder.
n@193 2521 this.holder.appendChild(this.string);
n@193 2522 this.holder.appendChild(br);
n@193 2523 this.holder.appendChild(this.textArea);
n@193 2524
n@520 2525 this.exportXMLDOM = function(storePoint) {
n@520 2526 var root = storePoint.parent.document.createElement('comment');
n@193 2527 root.id = this.specification.id;
n@193 2528 root.setAttribute('type',this.specification.type);
b@254 2529 console.log("Question: "+this.string.textContent);
b@254 2530 console.log("Response: "+root.textContent);
n@520 2531 var question = storePoint.parent.document.createElement('question');
n@520 2532 question.textContent = this.string.textContent;
n@520 2533 var response = storePoint.parent.document.createElement('response');
n@520 2534 response.textContent = this.textArea.value;
n@520 2535 root.appendChild(question);
n@520 2536 root.appendChild(response);
n@520 2537 storePoint.XMLDOM.appendChild(root);
n@193 2538 return root;
n@193 2539 };
n@302 2540 this.resize = function()
n@302 2541 {
n@302 2542 var boxwidth = (window.innerWidth-100)/2;
n@302 2543 if (boxwidth >= 600)
n@302 2544 {
n@302 2545 boxwidth = 600;
n@302 2546 }
n@302 2547 else if (boxwidth < 400)
n@302 2548 {
n@302 2549 boxwidth = 400;
n@302 2550 }
n@302 2551 this.holder.style.width = boxwidth+"px";
n@302 2552 this.textArea.style.width = boxwidth-6+"px";
n@302 2553 };
n@302 2554 this.resize();
n@193 2555 };
n@193 2556
n@193 2557 this.radioBox = function(commentQuestion) {
n@193 2558 this.specification = commentQuestion;
n@193 2559 // Create document objects to hold the comment boxes
n@193 2560 this.holder = document.createElement('div');
n@193 2561 this.holder.className = 'comment-div';
n@193 2562 // Create a string next to each comment asking for a comment
n@193 2563 this.string = document.createElement('span');
n@193 2564 this.string.innerHTML = commentQuestion.statement;
n@193 2565 var br = document.createElement('br');
n@193 2566 // Add to the holder.
n@193 2567 this.holder.appendChild(this.string);
n@193 2568 this.holder.appendChild(br);
n@193 2569 this.options = [];
n@193 2570 this.inputs = document.createElement('div');
n@193 2571 this.span = document.createElement('div');
n@193 2572 this.inputs.align = 'center';
n@193 2573 this.inputs.style.marginLeft = '12px';
n@193 2574 this.span.style.marginLeft = '12px';
n@193 2575 this.span.align = 'center';
n@193 2576 this.span.style.marginTop = '15px';
n@193 2577
n@193 2578 var optCount = commentQuestion.options.length;
n@453 2579 for (var optNode of commentQuestion.options)
n@193 2580 {
n@193 2581 var div = document.createElement('div');
n@301 2582 div.style.width = '80px';
n@193 2583 div.style.float = 'left';
n@193 2584 var input = document.createElement('input');
n@193 2585 input.type = 'radio';
n@193 2586 input.name = commentQuestion.id;
n@453 2587 input.setAttribute('setvalue',optNode.name);
n@193 2588 input.className = 'comment-radio';
n@193 2589 div.appendChild(input);
n@193 2590 this.inputs.appendChild(div);
n@193 2591
n@193 2592
n@193 2593 div = document.createElement('div');
n@301 2594 div.style.width = '80px';
n@193 2595 div.style.float = 'left';
n@193 2596 div.align = 'center';
n@193 2597 var span = document.createElement('span');
n@453 2598 span.textContent = optNode.text;
n@193 2599 span.className = 'comment-radio-span';
n@193 2600 div.appendChild(span);
n@193 2601 this.span.appendChild(div);
n@193 2602 this.options.push(input);
n@193 2603 }
n@193 2604 this.holder.appendChild(this.span);
n@193 2605 this.holder.appendChild(this.inputs);
n@193 2606
n@520 2607 this.exportXMLDOM = function(storePoint) {
n@520 2608 var root = storePoint.parent.document.createElement('comment');
n@193 2609 root.id = this.specification.id;
n@193 2610 root.setAttribute('type',this.specification.type);
n@193 2611 var question = document.createElement('question');
n@193 2612 question.textContent = this.string.textContent;
n@193 2613 var response = document.createElement('response');
n@193 2614 var i=0;
n@193 2615 while(this.options[i].checked == false) {
n@193 2616 i++;
n@193 2617 if (i >= this.options.length) {
n@193 2618 break;
n@193 2619 }
n@193 2620 }
n@193 2621 if (i >= this.options.length) {
n@193 2622 response.textContent = 'null';
n@193 2623 } else {
n@193 2624 response.textContent = this.options[i].getAttribute('setvalue');
n@193 2625 response.setAttribute('number',i);
n@193 2626 }
n@195 2627 console.log('Comment: '+question.textContent);
n@195 2628 console.log('Response: '+response.textContent);
n@193 2629 root.appendChild(question);
n@193 2630 root.appendChild(response);
n@520 2631 storePoint.XMLDOM.appendChild(root);
n@193 2632 return root;
n@193 2633 };
n@302 2634 this.resize = function()
n@302 2635 {
n@302 2636 var boxwidth = (window.innerWidth-100)/2;
n@302 2637 if (boxwidth >= 600)
n@302 2638 {
n@302 2639 boxwidth = 600;
n@302 2640 }
n@302 2641 else if (boxwidth < 400)
n@302 2642 {
n@302 2643 boxwidth = 400;
n@302 2644 }
n@302 2645 this.holder.style.width = boxwidth+"px";
n@302 2646 var text = this.holder.children[2];
n@302 2647 var options = this.holder.children[3];
n@302 2648 var optCount = options.children.length;
n@302 2649 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@302 2650 var options = options.firstChild;
n@302 2651 var text = text.firstChild;
n@302 2652 options.style.marginRight = spanMargin;
n@302 2653 options.style.marginLeft = spanMargin;
n@302 2654 text.style.marginRight = spanMargin;
n@302 2655 text.style.marginLeft = spanMargin;
n@302 2656 while(options.nextSibling != undefined)
n@302 2657 {
n@302 2658 options = options.nextSibling;
n@302 2659 text = text.nextSibling;
n@302 2660 options.style.marginRight = spanMargin;
n@302 2661 options.style.marginLeft = spanMargin;
n@302 2662 text.style.marginRight = spanMargin;
n@302 2663 text.style.marginLeft = spanMargin;
n@302 2664 }
n@302 2665 };
n@302 2666 this.resize();
n@193 2667 };
n@193 2668
n@195 2669 this.checkboxBox = function(commentQuestion) {
n@195 2670 this.specification = commentQuestion;
n@195 2671 // Create document objects to hold the comment boxes
n@195 2672 this.holder = document.createElement('div');
n@195 2673 this.holder.className = 'comment-div';
n@195 2674 // Create a string next to each comment asking for a comment
n@195 2675 this.string = document.createElement('span');
n@195 2676 this.string.innerHTML = commentQuestion.statement;
n@195 2677 var br = document.createElement('br');
n@195 2678 // Add to the holder.
n@195 2679 this.holder.appendChild(this.string);
n@195 2680 this.holder.appendChild(br);
n@195 2681 this.options = [];
n@195 2682 this.inputs = document.createElement('div');
n@195 2683 this.span = document.createElement('div');
n@195 2684 this.inputs.align = 'center';
n@195 2685 this.inputs.style.marginLeft = '12px';
n@195 2686 this.span.style.marginLeft = '12px';
n@195 2687 this.span.align = 'center';
n@195 2688 this.span.style.marginTop = '15px';
n@195 2689
n@195 2690 var optCount = commentQuestion.options.length;
n@195 2691 for (var i=0; i<optCount; i++)
n@195 2692 {
n@195 2693 var div = document.createElement('div');
n@301 2694 div.style.width = '80px';
n@195 2695 div.style.float = 'left';
n@195 2696 var input = document.createElement('input');
n@195 2697 input.type = 'checkbox';
n@195 2698 input.name = commentQuestion.id;
n@195 2699 input.setAttribute('setvalue',commentQuestion.options[i].name);
n@195 2700 input.className = 'comment-radio';
n@195 2701 div.appendChild(input);
n@195 2702 this.inputs.appendChild(div);
n@195 2703
n@195 2704
n@195 2705 div = document.createElement('div');
n@301 2706 div.style.width = '80px';
n@195 2707 div.style.float = 'left';
n@195 2708 div.align = 'center';
n@195 2709 var span = document.createElement('span');
n@195 2710 span.textContent = commentQuestion.options[i].text;
n@195 2711 span.className = 'comment-radio-span';
n@195 2712 div.appendChild(span);
n@195 2713 this.span.appendChild(div);
n@195 2714 this.options.push(input);
n@195 2715 }
n@195 2716 this.holder.appendChild(this.span);
n@195 2717 this.holder.appendChild(this.inputs);
n@195 2718
n@520 2719 this.exportXMLDOM = function(storePoint) {
n@520 2720 var root = storePoint.parent.document.createElement('comment');
n@195 2721 root.id = this.specification.id;
n@195 2722 root.setAttribute('type',this.specification.type);
n@195 2723 var question = document.createElement('question');
n@195 2724 question.textContent = this.string.textContent;
n@195 2725 root.appendChild(question);
n@195 2726 console.log('Comment: '+question.textContent);
n@195 2727 for (var i=0; i<this.options.length; i++) {
n@195 2728 var response = document.createElement('response');
n@195 2729 response.textContent = this.options[i].checked;
n@195 2730 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
n@195 2731 root.appendChild(response);
n@195 2732 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
n@195 2733 }
n@520 2734 storePoint.XMLDOM.appendChild(root);
n@195 2735 return root;
n@195 2736 };
n@302 2737 this.resize = function()
n@302 2738 {
n@302 2739 var boxwidth = (window.innerWidth-100)/2;
n@302 2740 if (boxwidth >= 600)
n@302 2741 {
n@302 2742 boxwidth = 600;
n@302 2743 }
n@302 2744 else if (boxwidth < 400)
n@302 2745 {
n@302 2746 boxwidth = 400;
n@302 2747 }
n@302 2748 this.holder.style.width = boxwidth+"px";
n@302 2749 var text = this.holder.children[2];
n@302 2750 var options = this.holder.children[3];
n@302 2751 var optCount = options.children.length;
n@302 2752 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@302 2753 var options = options.firstChild;
n@302 2754 var text = text.firstChild;
n@302 2755 options.style.marginRight = spanMargin;
n@302 2756 options.style.marginLeft = spanMargin;
n@302 2757 text.style.marginRight = spanMargin;
n@302 2758 text.style.marginLeft = spanMargin;
n@302 2759 while(options.nextSibling != undefined)
n@302 2760 {
n@302 2761 options = options.nextSibling;
n@302 2762 text = text.nextSibling;
n@302 2763 options.style.marginRight = spanMargin;
n@302 2764 options.style.marginLeft = spanMargin;
n@302 2765 text.style.marginRight = spanMargin;
n@302 2766 text.style.marginLeft = spanMargin;
n@302 2767 }
n@302 2768 };
n@302 2769 this.resize();
n@195 2770 };
nicholas@211 2771
n@193 2772 this.createCommentQuestion = function(element) {
n@193 2773 var node;
n@453 2774 if (element.type == 'question') {
n@193 2775 node = new this.commentBox(element);
n@193 2776 } else if (element.type == 'radio') {
n@193 2777 node = new this.radioBox(element);
n@195 2778 } else if (element.type == 'checkbox') {
n@195 2779 node = new this.checkboxBox(element);
n@193 2780 }
n@193 2781 this.commentQuestions.push(node);
n@193 2782 return node;
n@193 2783 };
n@201 2784
nicholas@237 2785 this.deleteCommentQuestions = function()
nicholas@237 2786 {
nicholas@237 2787 this.commentQuestions = [];
nicholas@237 2788 };
nicholas@237 2789
n@201 2790 this.playhead = new function()
n@201 2791 {
n@201 2792 this.object = document.createElement('div');
n@201 2793 this.object.className = 'playhead';
n@201 2794 this.object.align = 'left';
n@201 2795 var curTime = document.createElement('div');
n@201 2796 curTime.style.width = '50px';
n@201 2797 this.curTimeSpan = document.createElement('span');
n@201 2798 this.curTimeSpan.textContent = '00:00';
n@201 2799 curTime.appendChild(this.curTimeSpan);
n@201 2800 this.object.appendChild(curTime);
n@201 2801 this.scrubberTrack = document.createElement('div');
n@201 2802 this.scrubberTrack.className = 'playhead-scrub-track';
n@201 2803
n@201 2804 this.scrubberHead = document.createElement('div');
n@201 2805 this.scrubberHead.id = 'playhead-scrubber';
n@201 2806 this.scrubberTrack.appendChild(this.scrubberHead);
n@201 2807 this.object.appendChild(this.scrubberTrack);
n@201 2808
n@201 2809 this.timePerPixel = 0;
n@201 2810 this.maxTime = 0;
n@201 2811
n@204 2812 this.playbackObject;
n@204 2813
n@204 2814 this.setTimePerPixel = function(audioObject) {
n@201 2815 //maxTime must be in seconds
n@204 2816 this.playbackObject = audioObject;
n@379 2817 this.maxTime = audioObject.buffer.buffer.duration;
n@201 2818 var width = 490; //500 - 10, 5 each side of the tracker head
n@204 2819 this.timePerPixel = this.maxTime/490;
n@204 2820 if (this.maxTime < 60) {
n@201 2821 this.curTimeSpan.textContent = '0.00';
n@201 2822 } else {
n@201 2823 this.curTimeSpan.textContent = '00:00';
n@201 2824 }
n@201 2825 };
n@201 2826
n@204 2827 this.update = function() {
n@201 2828 // Update the playhead position, startPlay must be called
n@201 2829 if (this.timePerPixel > 0) {
n@204 2830 var time = this.playbackObject.getCurrentPosition();
n@498 2831 if (time > 0 && time < this.maxTime) {
nicholas@267 2832 var width = 490;
nicholas@267 2833 var pix = Math.floor(time/this.timePerPixel);
nicholas@267 2834 this.scrubberHead.style.left = pix+'px';
nicholas@267 2835 if (this.maxTime > 60.0) {
nicholas@267 2836 var secs = time%60;
nicholas@267 2837 var mins = Math.floor((time-secs)/60);
nicholas@267 2838 secs = secs.toString();
nicholas@267 2839 secs = secs.substr(0,2);
nicholas@267 2840 mins = mins.toString();
nicholas@267 2841 this.curTimeSpan.textContent = mins+':'+secs;
nicholas@267 2842 } else {
nicholas@267 2843 time = time.toString();
nicholas@267 2844 this.curTimeSpan.textContent = time.substr(0,4);
nicholas@267 2845 }
n@201 2846 } else {
nicholas@267 2847 this.scrubberHead.style.left = '0px';
nicholas@267 2848 if (this.maxTime < 60) {
nicholas@267 2849 this.curTimeSpan.textContent = '0.00';
nicholas@267 2850 } else {
nicholas@267 2851 this.curTimeSpan.textContent = '00:00';
nicholas@267 2852 }
n@201 2853 }
n@201 2854 }
n@201 2855 };
n@204 2856
n@204 2857 this.interval = undefined;
n@204 2858
n@204 2859 this.start = function() {
n@204 2860 if (this.playbackObject != undefined && this.interval == undefined) {
nicholas@267 2861 if (this.maxTime < 60) {
nicholas@267 2862 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nicholas@267 2863 } else {
nicholas@267 2864 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nicholas@267 2865 }
n@204 2866 }
n@204 2867 };
n@204 2868 this.stop = function() {
n@204 2869 clearInterval(this.interval);
n@204 2870 this.interval = undefined;
n@527 2871 this.scrubberHead.style.left = '0px';
nicholas@267 2872 if (this.maxTime < 60) {
nicholas@267 2873 this.curTimeSpan.textContent = '0.00';
nicholas@267 2874 } else {
nicholas@267 2875 this.curTimeSpan.textContent = '00:00';
nicholas@267 2876 }
n@204 2877 };
n@201 2878 };
n@483 2879
n@483 2880 this.volume = new function()
n@483 2881 {
n@483 2882 // An in-built volume module which can be viewed on page
n@483 2883 // Includes trackers on page-by-page data
n@483 2884 // Volume does NOT reset to 0dB on each page load
n@483 2885 this.valueLin = 1.0;
n@483 2886 this.valueDB = 0.0;
n@483 2887 this.object = document.createElement('div');
n@483 2888 this.object.id = 'master-volume-holder';
n@483 2889 this.slider = document.createElement('input');
n@483 2890 this.slider.id = 'master-volume-control';
n@483 2891 this.slider.type = 'range';
n@483 2892 this.valueText = document.createElement('span');
n@483 2893 this.valueText.id = 'master-volume-feedback';
n@483 2894 this.valueText.textContent = '0dB';
n@483 2895
n@483 2896 this.slider.min = -60;
n@483 2897 this.slider.max = 12;
n@483 2898 this.slider.value = 0;
n@483 2899 this.slider.step = 1;
n@483 2900 this.slider.onmousemove = function(event)
n@483 2901 {
n@483 2902 interfaceContext.volume.valueDB = event.currentTarget.value;
n@483 2903 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
n@483 2904 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
n@483 2905 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
n@483 2906 }
n@483 2907 this.slider.onmouseup = function(event)
n@483 2908 {
n@526 2909 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
n@483 2910 if (storePoint.length == 0)
n@483 2911 {
n@483 2912 storePoint = storage.document.createElement('metricresult');
n@483 2913 storePoint.setAttribute('name','volumeTracker');
n@526 2914 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
n@483 2915 }
n@483 2916 else {
n@483 2917 storePoint = storePoint[0];
n@483 2918 }
n@483 2919 var node = storage.document.createElement('movement');
n@483 2920 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
n@483 2921 node.setAttribute('volume',interfaceContext.volume.valueDB);
n@483 2922 node.setAttribute('format','dBFS');
n@483 2923 storePoint.appendChild(node);
n@483 2924 }
n@483 2925
n@484 2926 var title = document.createElement('div');
n@484 2927 title.innerHTML = '<span>Master Volume Control</span>';
n@484 2928 title.style.fontSize = '0.75em';
n@484 2929 title.style.width = "100%";
n@484 2930 title.align = 'center';
n@484 2931 this.object.appendChild(title);
n@484 2932
n@483 2933 this.object.appendChild(this.slider);
n@483 2934 this.object.appendChild(this.valueText);
n@483 2935 }
nicholas@235 2936 // Global Checkers
nicholas@235 2937 // These functions will help enforce the checkers
nicholas@235 2938 this.checkHiddenAnchor = function()
nicholas@235 2939 {
n@453 2940 for (var ao of audioEngineContext.audioObjects)
nicholas@235 2941 {
n@453 2942 if (ao.specification.type == "anchor")
nicholas@235 2943 {
n@454 2944 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
n@453 2945 // Anchor is not set below
n@453 2946 console.log('Anchor node not below marker value');
n@453 2947 alert('Please keep listening');
n@498 2948 this.storeErrorNode('Anchor node not below marker value');
n@453 2949 return false;
n@453 2950 }
nicholas@235 2951 }
nicholas@235 2952 }
nicholas@235 2953 return true;
nicholas@235 2954 };
nicholas@235 2955
nicholas@235 2956 this.checkHiddenReference = function()
nicholas@235 2957 {
n@453 2958 for (var ao of audioEngineContext.audioObjects)
nicholas@235 2959 {
n@453 2960 if (ao.specification.type == "reference")
nicholas@235 2961 {
n@454 2962 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
n@453 2963 // Anchor is not set below
n@498 2964 console.log('Reference node not above marker value');
n@498 2965 this.storeErrorNode('Reference node not above marker value');
n@453 2966 alert('Please keep listening');
n@453 2967 return false;
n@453 2968 }
nicholas@235 2969 }
nicholas@235 2970 }
nicholas@235 2971 return true;
nicholas@235 2972 };
n@366 2973
n@366 2974 this.checkFragmentsFullyPlayed = function ()
n@366 2975 {
n@366 2976 // Checks the entire file has been played back
n@366 2977 // NOTE ! This will return true IF playback is Looped!!!
n@366 2978 if (audioEngineContext.loopPlayback)
n@366 2979 {
n@366 2980 console.log("WARNING - Looped source: Cannot check fragments are fully played");
n@366 2981 return true;
n@366 2982 }
n@366 2983 var check_pass = true;
n@366 2984 var error_obj = [];
n@366 2985 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
n@366 2986 {
n@366 2987 var object = audioEngineContext.audioObjects[i];
nicholas@415 2988 var time = object.buffer.buffer.duration;
n@366 2989 var metric = object.metric;
n@366 2990 var passed = false;
n@366 2991 for (var j=0; j<metric.listenTracker.length; j++)
n@366 2992 {
n@366 2993 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
n@366 2994 var start_time = Number(bt[0].getAttribute('start'));
n@366 2995 var stop_time = Number(bt[0].getAttribute('stop'));
n@366 2996 var delta = stop_time - start_time;
n@366 2997 if (delta >= time)
n@366 2998 {
n@366 2999 passed = true;
n@366 3000 break;
n@366 3001 }
n@366 3002 }
n@366 3003 if (passed == false)
n@366 3004 {
n@366 3005 check_pass = false;
n@598 3006 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
n@598 3007 error_obj.push(object.interfaceDOM.getPresentedId());
n@366 3008 }
n@366 3009 }
n@366 3010 if (check_pass == false)
n@366 3011 {
nicholas@415 3012 var str_start = "You have not completely listened to fragments ";
n@366 3013 for (var i=0; i<error_obj.length; i++)
n@366 3014 {
n@366 3015 str_start += error_obj[i];
n@366 3016 if (i != error_obj.length-1)
n@366 3017 {
n@366 3018 str_start += ', ';
n@366 3019 }
n@366 3020 }
n@366 3021 str_start += ". Please keep listening";
n@366 3022 console.log("[ALERT]: "+str_start);
n@498 3023 this.storeErrorNode("[ALERT]: "+str_start);
n@366 3024 alert(str_start);
n@366 3025 }
n@366 3026 };
nicholas@421 3027 this.checkAllMoved = function()
nicholas@421 3028 {
nicholas@421 3029 var str = "You have not moved ";
nicholas@421 3030 var failed = [];
n@469 3031 for (var ao of audioEngineContext.audioObjects)
nicholas@421 3032 {
n@469 3033 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
nicholas@421 3034 {
n@469 3035 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@421 3036 }
nicholas@421 3037 }
nicholas@421 3038 if (failed.length == 0)
nicholas@421 3039 {
nicholas@421 3040 return true;
nicholas@421 3041 } else if (failed.length == 1)
nicholas@421 3042 {
nicholas@421 3043 str += 'track '+failed[0];
nicholas@421 3044 } else {
nicholas@421 3045 str += 'tracks ';
nicholas@421 3046 for (var i=0; i<failed.length-1; i++)
nicholas@421 3047 {
nicholas@421 3048 str += failed[i]+', ';
nicholas@421 3049 }
nicholas@421 3050 str += 'and '+failed[i];
nicholas@421 3051 }
nicholas@421 3052 str +='.';
nicholas@421 3053 alert(str);
nicholas@421 3054 console.log(str);
n@498 3055 this.storeErrorNode(str);
nicholas@421 3056 return false;
nicholas@421 3057 };
nicholas@421 3058 this.checkAllPlayed = function()
nicholas@421 3059 {
nicholas@421 3060 var str = "You have not played ";
nicholas@421 3061 var failed = [];
n@469 3062 for (var ao of audioEngineContext.audioObjects)
nicholas@421 3063 {
n@469 3064 if(ao.metric.wasListenedTo == false)
nicholas@421 3065 {
n@469 3066 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@421 3067 }
nicholas@421 3068 }
nicholas@421 3069 if (failed.length == 0)
nicholas@421 3070 {
nicholas@421 3071 return true;
nicholas@421 3072 } else if (failed.length == 1)
nicholas@421 3073 {
nicholas@421 3074 str += 'track '+failed[0];
nicholas@421 3075 } else {
nicholas@421 3076 str += 'tracks ';
nicholas@421 3077 for (var i=0; i<failed.length-1; i++)
nicholas@421 3078 {
nicholas@421 3079 str += failed[i]+', ';
nicholas@421 3080 }
nicholas@421 3081 str += 'and '+failed[i];
nicholas@421 3082 }
nicholas@421 3083 str +='.';
nicholas@421 3084 alert(str);
nicholas@421 3085 console.log(str);
n@498 3086 this.storeErrorNode(str);
nicholas@421 3087 return false;
nicholas@421 3088 };
n@498 3089
n@498 3090 this.storeErrorNode = function(errorMessage)
n@498 3091 {
n@498 3092 var time = audioEngineContext.timer.getTestTime();
n@498 3093 var node = storage.document.createElement('error');
n@498 3094 node.setAttribute('time',time);
n@498 3095 node.textContent = errorMessage;
n@498 3096 testState.currentStore.XMLDOM.appendChild(node);
n@498 3097 };
n@453 3098 }
n@453 3099
n@453 3100 function Storage()
n@453 3101 {
n@453 3102 // Holds results in XML format until ready for collection
n@453 3103 this.globalPreTest = null;
n@453 3104 this.globalPostTest = null;
n@453 3105 this.testPages = [];
n@453 3106 this.document = document.implementation.createDocument(null,"waetresult");
n@551 3107 this.root = this.document.childNodes[0];
n@453 3108 this.state = 0;
n@453 3109
n@584 3110 this.initialise = function(sessionKey)
n@453 3111 {
n@584 3112 if (sessionKey == undefined) {
n@584 3113 // We need to get the sessionKey
n@584 3114 this.SessionKey.generateKey();
n@589 3115 var projectDocument = specification.projectXML;
n@589 3116 projectDocument.setAttribute('file-name',url);
n@589 3117 this.root.appendChild(projectDocument);
n@589 3118 this.root.appendChild(returnDateNode());
n@589 3119 this.root.appendChild(interfaceContext.returnNavigator());
n@584 3120 } else {
n@584 3121 this.SessionKey.key = sessionKey;
n@584 3122 }
n@589 3123 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
n@589 3124 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
n@453 3125 };
n@584 3126
n@584 3127 this.SessionKey = {
n@584 3128 key: null,
n@584 3129 request: new XMLHttpRequest(),
n@584 3130 parent: this,
n@584 3131 handleEvent: function() {
n@584 3132 var parse = new DOMParser();
n@584 3133 var xml = parse.parseFromString(this.request.response,"text/xml");
n@584 3134 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
n@584 3135 this.key = xml.getAllElementsByTagName("key")[0].textContent;
n@589 3136 this.parent.root.setAttribute("key",this.key);
n@589 3137 this.parent.root.setAttribute("state","empty");
n@584 3138 } else {
n@584 3139 this.generateKey();
n@584 3140 }
n@584 3141 },
n@584 3142 generateKey: function() {
n@584 3143 var temp_key = randomString(32);
n@584 3144 this.request.open("GET","keygen.php?key="+temp_key,true);
n@584 3145 this.request.addEventListener("load",this);
n@584 3146 this.request.send();
n@587 3147 },
n@589 3148 update: function() {
n@589 3149 this.parent.root.setAttribute("state","update");
n@589 3150 var xmlhttp = new XMLHttpRequest();
n@589 3151 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
n@589 3152 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
n@589 3153 xmlhttp.onerror = function(){
n@589 3154 console.log('Error updating file to server!');
n@589 3155 };
n@589 3156 var hold = document.createElement("div");
n@589 3157 var clone = this.parent.root.cloneNode(true);
n@589 3158 hold.appendChild(clone);
n@589 3159 xmlhttp.onload = function() {
n@589 3160 if (this.status >= 300) {
n@589 3161 console.log("WARNING - Could not update at this time");
n@589 3162 } else {
n@589 3163 var parser = new DOMParser();
n@589 3164 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
n@589 3165 var response = xmlDoc.getElementsByTagName('response')[0];
n@589 3166 if (response.getAttribute("state") == "OK") {
n@589 3167 var file = response.getElementsByTagName("file")[0];
n@589 3168 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
n@589 3169 } else {
n@589 3170 var message = response.getElementsByTagName("message");
n@589 3171 console.log("Intermediate save: Error! "+message.textContent);
n@589 3172 }
n@587 3173 }
n@587 3174 }
n@589 3175 xmlhttp.send([hold.innerHTML]);
n@584 3176 }
n@584 3177 }
n@453 3178
n@453 3179 this.createTestPageStore = function(specification)
n@453 3180 {
n@453 3181 var store = new this.pageNode(this,specification);
n@453 3182 this.testPages.push(store);
n@453 3183 return this.testPages[this.testPages.length-1];
n@453 3184 };
n@453 3185
n@453 3186 this.surveyNode = function(parent,root,specification)
n@453 3187 {
n@453 3188 this.specification = specification;
n@453 3189 this.parent = parent;
n@453 3190 this.XMLDOM = this.parent.document.createElement('survey');
n@453 3191 this.XMLDOM.setAttribute('location',this.specification.location);
n@453 3192 for (var optNode of this.specification.options)
n@453 3193 {
n@453 3194 if (optNode.type != 'statement')
n@453 3195 {
n@453 3196 var node = this.parent.document.createElement('surveyresult');
n@453 3197 node.id = optNode.id;
n@453 3198 node.setAttribute('type',optNode.type);
n@453 3199 this.XMLDOM.appendChild(node);
n@453 3200 }
n@453 3201 }
n@453 3202 root.appendChild(this.XMLDOM);
n@453 3203
n@453 3204 this.postResult = function(node)
n@453 3205 {
n@453 3206 // From popup: node is the popupOption node containing both spec. and results
n@453 3207 // ID is the position
n@453 3208 if (node.specification.type == 'statement'){return;}
n@453 3209 var surveyresult = this.parent.document.getElementById(node.specification.id);
n@453 3210 switch(node.specification.type)
n@453 3211 {
n@453 3212 case "number":
n@453 3213 case "question":
n@453 3214 var child = this.parent.document.createElement('response');
n@453 3215 child.textContent = node.response;
n@453 3216 surveyresult.appendChild(child);
n@453 3217 break;
n@453 3218 case "radio":
n@453 3219 var child = this.parent.document.createElement('response');
n@453 3220 child.setAttribute('name',node.response.name);
n@453 3221 child.textContent = node.response.text;
n@453 3222 surveyresult.appendChild(child);
n@453 3223 break;
n@453 3224 case "checkbox":
n@453 3225 for (var i=0; i<node.response.length; i++)
n@453 3226 {
n@453 3227 var checkNode = this.parent.document.createElement('response');
n@476 3228 checkNode.setAttribute('name',node.response[i].name);
n@476 3229 checkNode.setAttribute('checked',node.response[i].checked);
n@455 3230 surveyresult.appendChild(checkNode);
n@453 3231 }
n@453 3232 break;
n@453 3233 }
n@453 3234 };
n@453 3235 };
n@453 3236
n@453 3237 this.pageNode = function(parent,specification)
n@453 3238 {
n@453 3239 // Create one store per test page
n@453 3240 this.specification = specification;
n@453 3241 this.parent = parent;
n@453 3242 this.XMLDOM = this.parent.document.createElement('page');
n@453 3243 this.XMLDOM.setAttribute('id',specification.id);
n@453 3244 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
n@474 3245 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
n@474 3246 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
n@453 3247
n@453 3248 // Add any page metrics
n@453 3249 var page_metric = this.parent.document.createElement('metric');
n@453 3250 this.XMLDOM.appendChild(page_metric);
n@453 3251
n@453 3252 // Add the audioelement
n@453 3253 for (var element of this.specification.audioElements)
n@453 3254 {
n@453 3255 var aeNode = this.parent.document.createElement('audioelement');
n@453 3256 aeNode.id = element.id;
n@453 3257 aeNode.setAttribute('type',element.type);
n@453 3258 aeNode.setAttribute('url', element.url);
n@453 3259 aeNode.setAttribute('gain', element.gain);
n@453 3260 if (element.type == 'anchor' || element.type == 'reference')
n@453 3261 {
n@453 3262 if (element.marker > 0)
n@453 3263 {
n@453 3264 aeNode.setAttribute('marker',element.marker);
n@453 3265 }
n@453 3266 }
n@453 3267 var ae_metric = this.parent.document.createElement('metric');
n@453 3268 aeNode.appendChild(ae_metric);
n@453 3269 this.XMLDOM.appendChild(aeNode);
n@453 3270 }
n@453 3271
n@453 3272 this.parent.root.appendChild(this.XMLDOM);
n@453 3273 };
n@589 3274 this.update = function() {
n@589 3275 this.SessionKey.update();
n@589 3276 }
n@453 3277 this.finish = function()
n@453 3278 {
n@453 3279 if (this.state == 0)
n@453 3280 {
n@589 3281 this.update();
n@453 3282 }
n@453 3283 this.state = 1;
n@453 3284 return this.root;
n@453 3285 };
n@453 3286 }