annotate core.js @ 551:5cd12e23b195 Dev_main

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