annotate core.js @ 591:537f391079ec Dev_main

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