annotate core.js @ 597:5a3b74e95d4a Dev_main

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