annotate core.js @ 589:055f9763fada Dev_main

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