annotate core.js @ 553:bdcc5fc7a6bf Dev_main

Fix Safari error (event.currentTarget.owner) line 1365
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Tue, 23 Feb 2016 11:56:11 +0000
parents 5cd12e23b195
children 37f07218f744
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@553 1365 if (event.currentTarget != null) {
n@553 1366 event.currentTarget.owner.stop(audioContext.currentTime+1);
n@553 1367 }
n@202 1368 };
n@202 1369 if (this.bufferNode.loop == false) {
n@202 1370 this.metric.startListening(audioEngineContext.timer.getTestTime());
n@489 1371 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
n@489 1372 this.interfaceDOM.startPlayback();
n@489 1373 } else {
n@489 1374 this.outputGain.gain.setValueAtTime(0.0,startTime);
n@489 1375 }
n@202 1376 this.bufferNode.start(startTime);
n@527 1377 this.bufferNode.playbackStartTime = startTime;
nicholas@110 1378 }
n@16 1379 };
nicholas@1 1380
n@489 1381 this.stop = function(stopTime) {
n@489 1382 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
n@97 1383 if (this.bufferNode != undefined)
n@97 1384 {
n@203 1385 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
n@489 1386 this.bufferNode.stop(stopTime);
n@97 1387 this.bufferNode = undefined;
n@97 1388 }
n@489 1389 this.outputGain.gain.value = 0.0;
n@489 1390 this.interfaceDOM.stopPlayback();
n@16 1391 };
n@164 1392
n@164 1393 this.getCurrentPosition = function() {
n@164 1394 var time = audioEngineContext.timer.getTestTime();
n@164 1395 if (this.bufferNode != undefined) {
n@527 1396 return (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
n@164 1397 } else {
n@164 1398 return 0;
n@164 1399 }
n@164 1400 };
n@183 1401
n@183 1402 this.exportXMLDOM = function() {
n@453 1403 var file = storage.document.createElement('file');
nicholas@387 1404 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
nicholas@387 1405 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
nicholas@387 1406 file.setAttribute('sampleCount',this.buffer.buffer.length);
nicholas@387 1407 file.setAttribute('duration',this.buffer.buffer.duration);
n@453 1408 this.storeDOM.appendChild(file);
n@453 1409 if (this.specification.type != 'outside-reference') {
n@383 1410 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
n@469 1411 if (interfaceXML != null)
n@469 1412 {
n@469 1413 if (interfaceXML.length == undefined) {
n@469 1414 this.storeDOM.appendChild(interfaceXML);
n@469 1415 } else {
n@469 1416 for (var i=0; i<interfaceXML.length; i++)
n@469 1417 {
n@469 1418 this.storeDOM.appendChild(interfaceXML[i]);
n@469 1419 }
n@383 1420 }
n@383 1421 }
n@459 1422 if (this.commentDOM != null) {
n@459 1423 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
n@459 1424 }
nicholas@236 1425 }
n@453 1426 var nodes = this.metric.exportXMLDOM();
n@453 1427 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
n@453 1428 for (var i=0; i<nodes.length; i++)
n@453 1429 {
n@453 1430 mroot.appendChild(nodes[i]);
n@453 1431 }
n@183 1432 };
n@49 1433 }
n@49 1434
n@49 1435 function timer()
n@49 1436 {
n@49 1437 /* Timer object used in audioEngine to keep track of session timings
n@49 1438 * Uses the timer of the web audio API, so sample resolution
n@49 1439 */
n@49 1440 this.testStarted = false;
n@49 1441 this.testStartTime = 0;
n@49 1442 this.testDuration = 0;
n@49 1443 this.minimumTestTime = 0; // No minimum test time
n@49 1444 this.startTest = function()
n@49 1445 {
n@49 1446 if (this.testStarted == false)
n@49 1447 {
n@49 1448 this.testStartTime = audioContext.currentTime;
n@49 1449 this.testStarted = true;
n@49 1450 this.updateTestTime();
n@52 1451 audioEngineContext.metric.initialiseTest();
n@49 1452 }
n@49 1453 };
n@49 1454 this.stopTest = function()
n@49 1455 {
n@49 1456 if (this.testStarted)
n@49 1457 {
n@49 1458 this.testDuration = this.getTestTime();
n@49 1459 this.testStarted = false;
n@49 1460 } else {
n@49 1461 console.log('ERR: Test tried to end before beginning');
n@49 1462 }
n@49 1463 };
n@49 1464 this.updateTestTime = function()
n@49 1465 {
n@49 1466 if (this.testStarted)
n@49 1467 {
n@49 1468 this.testDuration = audioContext.currentTime - this.testStartTime;
n@49 1469 }
n@49 1470 };
n@49 1471 this.getTestTime = function()
n@49 1472 {
n@49 1473 this.updateTestTime();
n@49 1474 return this.testDuration;
n@49 1475 };
n@49 1476 }
n@49 1477
n@377 1478 function sessionMetrics(engine,specification)
n@49 1479 {
n@49 1480 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
n@49 1481 */
n@49 1482 this.engine = engine;
n@49 1483 this.lastClicked = -1;
n@49 1484 this.data = -1;
n@113 1485 this.reset = function() {
n@113 1486 this.lastClicked = -1;
n@113 1487 this.data = -1;
n@113 1488 };
n@377 1489
n@377 1490 this.enableElementInitialPosition = false;
n@377 1491 this.enableElementListenTracker = false;
n@377 1492 this.enableElementTimer = false;
n@377 1493 this.enableElementTracker = false;
n@377 1494 this.enableFlagListenedTo = false;
n@377 1495 this.enableFlagMoved = false;
n@377 1496 this.enableTestTimer = false;
n@377 1497 // Obtain the metrics enabled
n@453 1498 for (var i=0; i<specification.metrics.enabled.length; i++)
n@377 1499 {
n@453 1500 var node = specification.metrics.enabled[i];
n@453 1501 switch(node)
n@377 1502 {
n@377 1503 case 'testTimer':
n@377 1504 this.enableTestTimer = true;
n@377 1505 break;
n@377 1506 case 'elementTimer':
n@377 1507 this.enableElementTimer = true;
n@377 1508 break;
n@377 1509 case 'elementTracker':
n@377 1510 this.enableElementTracker = true;
n@377 1511 break;
n@377 1512 case 'elementListenTracker':
n@377 1513 this.enableElementListenTracker = true;
n@377 1514 break;
n@377 1515 case 'elementInitialPosition':
n@377 1516 this.enableElementInitialPosition = true;
n@377 1517 break;
n@377 1518 case 'elementFlagListenedTo':
n@377 1519 this.enableFlagListenedTo = true;
n@377 1520 break;
n@377 1521 case 'elementFlagMoved':
n@377 1522 this.enableFlagMoved = true;
n@377 1523 break;
n@377 1524 case 'elementFlagComments':
n@377 1525 this.enableFlagComments = true;
n@377 1526 break;
n@377 1527 }
n@377 1528 }
n@52 1529 this.initialiseTest = function(){};
n@49 1530 }
n@49 1531
n@139 1532 function metricTracker(caller)
n@49 1533 {
n@49 1534 /* Custom object to track and collect metric data
n@49 1535 * Used only inside the audioObjects object.
n@49 1536 */
n@49 1537
n@49 1538 this.listenedTimer = 0;
n@49 1539 this.listenStart = 0;
nicholas@110 1540 this.listenHold = false;
n@51 1541 this.initialPosition = -1;
n@49 1542 this.movementTracker = [];
n@164 1543 this.listenTracker =[];
n@49 1544 this.wasListenedTo = false;
n@49 1545 this.wasMoved = false;
n@49 1546 this.hasComments = false;
n@139 1547 this.parent = caller;
n@49 1548
n@453 1549 this.initialise = function(position)
n@49 1550 {
n@51 1551 if (this.initialPosition == -1) {
n@51 1552 this.initialPosition = position;
n@454 1553 this.moved(0,position);
n@51 1554 }
n@49 1555 };
n@49 1556
n@49 1557 this.moved = function(time,position)
n@49 1558 {
n@454 1559 if (time > 0) {this.wasMoved = true;}
n@49 1560 this.movementTracker[this.movementTracker.length] = [time, position];
n@49 1561 };
n@49 1562
nicholas@132 1563 this.startListening = function(time)
n@49 1564 {
nicholas@110 1565 if (this.listenHold == false)
n@49 1566 {
n@49 1567 this.wasListenedTo = true;
n@49 1568 this.listenStart = time;
nicholas@110 1569 this.listenHold = true;
n@164 1570
n@164 1571 var evnt = document.createElement('event');
n@164 1572 var testTime = document.createElement('testTime');
n@164 1573 testTime.setAttribute('start',time);
n@164 1574 var bufferTime = document.createElement('bufferTime');
n@164 1575 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
n@164 1576 evnt.appendChild(testTime);
n@164 1577 evnt.appendChild(bufferTime);
n@164 1578 this.listenTracker.push(evnt);
n@164 1579
n@139 1580 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
n@139 1581 }
n@139 1582 };
nicholas@132 1583
n@203 1584 this.stopListening = function(time,bufferStopTime)
nicholas@132 1585 {
nicholas@132 1586 if (this.listenHold == true)
nicholas@132 1587 {
n@164 1588 var diff = time - this.listenStart;
n@164 1589 this.listenedTimer += (diff);
n@49 1590 this.listenStart = 0;
nicholas@110 1591 this.listenHold = false;
n@164 1592
n@164 1593 var evnt = this.listenTracker[this.listenTracker.length-1];
n@164 1594 var testTime = evnt.getElementsByTagName('testTime')[0];
n@164 1595 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
n@164 1596 testTime.setAttribute('stop',time);
n@203 1597 if (bufferStopTime == undefined) {
n@203 1598 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
n@203 1599 } else {
n@203 1600 bufferTime.setAttribute('stop',bufferStopTime);
n@203 1601 }
n@164 1602 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
n@49 1603 }
n@49 1604 };
n@177 1605
n@177 1606 this.exportXMLDOM = function() {
n@453 1607 var storeDOM = [];
n@177 1608 if (audioEngineContext.metric.enableElementTimer) {
n@453 1609 var mElementTimer = storage.document.createElement('metricresult');
n@177 1610 mElementTimer.setAttribute('name','enableElementTimer');
n@177 1611 mElementTimer.textContent = this.listenedTimer;
n@453 1612 storeDOM.push(mElementTimer);
n@177 1613 }
n@177 1614 if (audioEngineContext.metric.enableElementTracker) {
n@453 1615 var elementTrackerFull = storage.document.createElement('metricResult');
n@177 1616 elementTrackerFull.setAttribute('name','elementTrackerFull');
n@177 1617 for (var k=0; k<this.movementTracker.length; k++)
n@177 1618 {
n@453 1619 var timePos = storage.document.createElement('timePos');
n@177 1620 timePos.id = k;
n@453 1621 var time = storage.document.createElement('time');
n@177 1622 time.textContent = this.movementTracker[k][0];
n@177 1623 var position = document.createElement('position');
n@177 1624 position.textContent = this.movementTracker[k][1];
n@177 1625 timePos.appendChild(time);
n@177 1626 timePos.appendChild(position);
n@177 1627 elementTrackerFull.appendChild(timePos);
n@177 1628 }
n@453 1629 storeDOM.push(elementTrackerFull);
n@177 1630 }
n@177 1631 if (audioEngineContext.metric.enableElementListenTracker) {
n@453 1632 var elementListenTracker = storage.document.createElement('metricResult');
n@177 1633 elementListenTracker.setAttribute('name','elementListenTracker');
n@177 1634 for (var k=0; k<this.listenTracker.length; k++) {
n@177 1635 elementListenTracker.appendChild(this.listenTracker[k]);
n@177 1636 }
n@453 1637 storeDOM.push(elementListenTracker);
n@177 1638 }
n@177 1639 if (audioEngineContext.metric.enableElementInitialPosition) {
n@453 1640 var elementInitial = storage.document.createElement('metricResult');
n@177 1641 elementInitial.setAttribute('name','elementInitialPosition');
n@177 1642 elementInitial.textContent = this.initialPosition;
n@453 1643 storeDOM.push(elementInitial);
n@177 1644 }
n@177 1645 if (audioEngineContext.metric.enableFlagListenedTo) {
n@453 1646 var flagListenedTo = storage.document.createElement('metricResult');
n@177 1647 flagListenedTo.setAttribute('name','elementFlagListenedTo');
n@177 1648 flagListenedTo.textContent = this.wasListenedTo;
n@453 1649 storeDOM.push(flagListenedTo);
n@177 1650 }
n@177 1651 if (audioEngineContext.metric.enableFlagMoved) {
n@453 1652 var flagMoved = storage.document.createElement('metricResult');
n@177 1653 flagMoved.setAttribute('name','elementFlagMoved');
n@177 1654 flagMoved.textContent = this.wasMoved;
n@453 1655 storeDOM.push(flagMoved);
n@177 1656 }
n@177 1657 if (audioEngineContext.metric.enableFlagComments) {
n@453 1658 var flagComments = storage.document.createElement('metricResult');
n@177 1659 flagComments.setAttribute('name','elementFlagComments');
n@177 1660 if (this.parent.commentDOM == null)
n@177 1661 {flag.textContent = 'false';}
n@177 1662 else if (this.parent.commentDOM.textContent.length == 0)
n@177 1663 {flag.textContent = 'false';}
n@177 1664 else
n@177 1665 {flag.textContet = 'true';}
n@453 1666 storeDOM.push(flagComments);
n@177 1667 }
n@453 1668 return storeDOM;
n@177 1669 };
n@54 1670 }
n@54 1671
n@54 1672 function randomiseOrder(input)
n@54 1673 {
n@54 1674 // This takes an array of information and randomises the order
n@54 1675 var N = input.length;
b@207 1676
b@207 1677 var inputSequence = []; // For safety purposes: keep track of randomisation
b@207 1678 for (var counter = 0; counter < N; ++counter)
b@207 1679 inputSequence.push(counter) // Fill array
b@207 1680 var inputSequenceClone = inputSequence.slice(0);
b@207 1681
n@54 1682 var holdArr = [];
b@207 1683 var outputSequence = [];
n@54 1684 for (var n=0; n<N; n++)
n@54 1685 {
n@54 1686 // First pick a random number
n@54 1687 var r = Math.random();
n@54 1688 // Multiply and floor by the number of elements left
n@54 1689 r = Math.floor(r*input.length);
n@54 1690 // Pick out that element and delete from the array
n@54 1691 holdArr.push(input.splice(r,1)[0]);
b@207 1692 // Do the same with sequence
b@207 1693 outputSequence.push(inputSequence.splice(r,1)[0]);
n@54 1694 }
b@207 1695 console.log(inputSequenceClone.toString()); // print original array to console
b@207 1696 console.log(outputSequence.toString()); // print randomised array to console
n@54 1697 return holdArr;
n@125 1698 }
n@125 1699
n@125 1700 function returnDateNode()
n@125 1701 {
n@125 1702 // Create an XML Node for the Date and Time a test was conducted
n@125 1703 // Structure is
n@125 1704 // <datetime>
n@125 1705 // <date year="##" month="##" day="##">DD/MM/YY</date>
n@125 1706 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
n@125 1707 // </datetime>
n@125 1708 var dateTime = new Date();
n@125 1709 var year = document.createAttribute('year');
n@125 1710 var month = document.createAttribute('month');
n@125 1711 var day = document.createAttribute('day');
n@125 1712 var hour = document.createAttribute('hour');
n@125 1713 var minute = document.createAttribute('minute');
n@125 1714 var secs = document.createAttribute('secs');
n@125 1715
n@125 1716 year.nodeValue = dateTime.getFullYear();
n@125 1717 month.nodeValue = dateTime.getMonth()+1;
n@125 1718 day.nodeValue = dateTime.getDate();
n@125 1719 hour.nodeValue = dateTime.getHours();
n@125 1720 minute.nodeValue = dateTime.getMinutes();
n@125 1721 secs.nodeValue = dateTime.getSeconds();
n@125 1722
n@125 1723 var hold = document.createElement("datetime");
n@125 1724 var date = document.createElement("date");
n@125 1725 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
n@125 1726 var time = document.createElement("time");
n@125 1727 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
n@125 1728
n@125 1729 date.setAttributeNode(year);
n@125 1730 date.setAttributeNode(month);
n@125 1731 date.setAttributeNode(day);
n@125 1732 time.setAttributeNode(hour);
n@125 1733 time.setAttributeNode(minute);
n@125 1734 time.setAttributeNode(secs);
n@125 1735
n@125 1736 hold.appendChild(date);
n@125 1737 hold.appendChild(time);
n@377 1738 return hold;
n@125 1739
nicholas@135 1740 }
nicholas@135 1741
n@180 1742 function Specification() {
n@180 1743 // Handles the decoding of the project specification XML into a simple JavaScript Object.
n@180 1744
n@453 1745 this.interface = null;
n@504 1746 this.projectReturn = "null";
n@453 1747 this.randomiseOrder = null;
n@453 1748 this.testPages = null;
n@453 1749 this.pages = [];
n@453 1750 this.metrics = null;
n@453 1751 this.interfaces = null;
n@453 1752 this.loudness = null;
n@453 1753 this.errors = [];
n@453 1754 this.schema = null;
n@380 1755
n@453 1756 this.processAttribute = function(attribute,schema)
n@453 1757 {
n@453 1758 // attribute is the string returned from getAttribute on the XML
n@453 1759 // schema is the <xs:attribute> node
n@453 1760 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
n@453 1761 {
n@477 1762 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
n@453 1763 }
n@453 1764 var defaultOpt = schema.getAttribute('default');
n@453 1765 if (attribute == null) {
n@453 1766 attribute = defaultOpt;
n@453 1767 }
n@453 1768 var dataType = schema.getAttribute('type');
n@453 1769 if (typeof dataType == "string") { dataType = dataType.substr(3);}
n@453 1770 else {dataType = "string";}
n@453 1771 if (attribute == null)
n@453 1772 {
n@453 1773 return attribute;
n@453 1774 }
n@453 1775 switch(dataType)
n@453 1776 {
n@453 1777 case "boolean":
n@453 1778 if (attribute == 'true'){attribute = true;}else{attribute=false;}
n@453 1779 break;
n@453 1780 case "negativeInteger":
n@453 1781 case "positiveInteger":
n@453 1782 case "nonNegativeInteger":
n@453 1783 case "nonPositiveInteger":
n@453 1784 case "integer":
n@453 1785 case "decimal":
n@453 1786 case "short":
n@453 1787 attribute = Number(attribute);
n@453 1788 break;
n@453 1789 case "string":
n@453 1790 default:
n@453 1791 attribute = String(attribute);
n@453 1792 break;
n@453 1793 }
n@453 1794 return attribute;
n@453 1795 };
n@180 1796
n@374 1797 this.decode = function(projectXML) {
n@453 1798 this.errors = [];
n@180 1799 // projectXML - DOM Parsed document
nicholas@240 1800 this.projectXML = projectXML.childNodes[0];
n@180 1801 var setupNode = projectXML.getElementsByTagName('setup')[0];
n@477 1802 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@453 1803 // First decode the attributes
n@477 1804 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@453 1805 for (var i in attributes)
n@297 1806 {
n@453 1807 if (isNaN(Number(i)) == true){break;}
n@453 1808 var attributeName = attributes[i].getAttribute('name');
n@453 1809 var projectAttr = setupNode.getAttribute(attributeName);
n@453 1810 projectAttr = this.processAttribute(projectAttr,attributes[i]);
n@453 1811 switch(typeof projectAttr)
n@410 1812 {
n@453 1813 case "number":
n@453 1814 case "boolean":
n@453 1815 eval('this.'+attributeName+' = '+projectAttr);
n@453 1816 break;
n@453 1817 case "string":
n@453 1818 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 1819 break;
n@410 1820 }
n@453 1821
n@374 1822 }
n@374 1823
n@501 1824 this.metrics = new this.metricNode();
n@180 1825
n@453 1826 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
n@453 1827
n@453 1828 // Now process the survey node options
n@453 1829 var survey = setupNode.getElementsByTagName('survey');
n@453 1830 for (var i in survey) {
n@453 1831 if (isNaN(Number(i)) == true){break;}
n@453 1832 var location = survey[i].getAttribute('location');
n@453 1833 if (location == 'pre' || location == 'before')
n@453 1834 {
n@453 1835 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@453 1836 else {
n@453 1837 this.preTest = new this.surveyNode();
n@501 1838 this.preTest.decode(this,survey[i]);
n@453 1839 }
n@453 1840 } else if (location == 'post' || location == 'after') {
n@453 1841 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@453 1842 else {
n@453 1843 this.postTest = new this.surveyNode();
n@501 1844 this.postTest.decode(this,survey[i]);
n@453 1845 }
n@180 1846 }
n@180 1847 }
n@180 1848
n@453 1849 var interfaceNode = setupNode.getElementsByTagName('interface');
n@453 1850 if (interfaceNode.length > 1)
n@453 1851 {
n@453 1852 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
n@453 1853 }
n@453 1854 this.interfaces = new this.interfaceNode();
n@453 1855 if (interfaceNode.length != 0)
n@453 1856 {
n@453 1857 interfaceNode = interfaceNode[0];
n@477 1858 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
nicholas@213 1859 }
nicholas@213 1860
n@453 1861 // Page tags
n@453 1862 var pageTags = projectXML.getElementsByTagName('page');
n@477 1863 var pageSchema = this.schema.getAllElementsByName('page')[0];
n@453 1864 for (var i=0; i<pageTags.length; i++)
n@297 1865 {
n@453 1866 var node = new this.page();
n@453 1867 node.decode(this,pageTags[i],pageSchema);
n@453 1868 this.pages.push(node);
n@297 1869 }
n@180 1870 };
n@180 1871
n@374 1872 this.encode = function()
n@374 1873 {
n@503 1874 var RootDocument = document.implementation.createDocument(null,"waet");
n@503 1875 var root = RootDocument.children[0];
n@503 1876 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
n@503 1877 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
n@453 1878 // Build setup node
n@503 1879 var setup = RootDocument.createElement("setup");
n@503 1880 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@503 1881 // First decode the attributes
n@503 1882 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@503 1883 for (var i=0; i<attributes.length; i++)
n@503 1884 {
n@503 1885 var name = attributes[i].getAttribute("name");
n@503 1886 if (name == undefined) {
n@503 1887 name = attributes[i].getAttribute("ref");
n@503 1888 }
n@503 1889 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 1890 {
n@503 1891 eval("setup.setAttribute('"+name+"',this."+name+")");
n@503 1892 }
n@503 1893 }
n@503 1894 root.appendChild(setup);
n@503 1895 // Survey node
n@503 1896 setup.appendChild(this.preTest.encode(RootDocument));
n@503 1897 setup.appendChild(this.postTest.encode(RootDocument));
n@503 1898 setup.appendChild(this.metrics.encode(RootDocument));
n@503 1899 setup.appendChild(this.interfaces.encode(RootDocument));
n@503 1900 for (var page of this.pages)
n@503 1901 {
n@503 1902 root.appendChild(page.encode(RootDocument));
n@503 1903 }
n@503 1904 return RootDocument;
n@374 1905 };
n@374 1906
n@453 1907 this.surveyNode = function() {
n@453 1908 this.location = null;
n@180 1909 this.options = [];
n@501 1910 this.schema = specification.schema.getAllElementsByName('survey')[0];
n@180 1911
n@374 1912 this.OptionNode = function() {
n@374 1913 this.type = undefined;
n@501 1914 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
n@374 1915 this.id = undefined;
n@374 1916 this.mandatory = undefined;
n@374 1917 this.statement = undefined;
n@374 1918 this.boxsize = undefined;
n@374 1919 this.options = [];
n@374 1920 this.min = undefined;
n@374 1921 this.max = undefined;
n@374 1922 this.step = undefined;
n@374 1923
n@501 1924 this.decode = function(parent,child)
n@374 1925 {
n@501 1926 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 1927 for (var i in attributeMap){
n@453 1928 if(isNaN(Number(i)) == true){break;}
n@453 1929 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 1930 var projectAttr = child.getAttribute(attributeName);
n@453 1931 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
n@453 1932 switch(typeof projectAttr)
n@453 1933 {
n@453 1934 case "number":
n@453 1935 case "boolean":
n@453 1936 eval('this.'+attributeName+' = '+projectAttr);
n@453 1937 break;
n@453 1938 case "string":
n@453 1939 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 1940 break;
n@374 1941 }
n@453 1942 }
n@453 1943 this.statement = child.getElementsByTagName('statement')[0].textContent;
n@453 1944 if (this.type == "checkbox" || this.type == "radio") {
n@453 1945 var children = child.getElementsByTagName('option');
n@453 1946 if (children.length == null) {
n@374 1947 console.log('Malformed' +child.nodeName+ 'entry');
n@374 1948 this.statement = 'Malformed' +child.nodeName+ 'entry';
n@374 1949 this.type = 'statement';
n@374 1950 } else {
n@374 1951 this.options = [];
n@453 1952 for (var i in children)
n@453 1953 {
n@453 1954 if (isNaN(Number(i))==true){break;}
n@453 1955 this.options.push({
n@453 1956 name: children[i].getAttribute('name'),
n@453 1957 text: children[i].textContent
n@453 1958 });
n@374 1959 }
n@374 1960 }
n@191 1961 }
n@374 1962 };
n@374 1963
n@503 1964 this.exportXML = function(doc)
n@374 1965 {
n@544 1966 var node = doc.createElement('surveyentry');
n@453 1967 node.setAttribute('type',this.type);
n@503 1968 var statement = doc.createElement('statement');
n@453 1969 statement.textContent = this.statement;
n@453 1970 node.appendChild(statement);
n@374 1971 switch(this.type)
n@374 1972 {
n@374 1973 case "statement":
n@374 1974 break;
n@374 1975 case "question":
n@544 1976 node.id = this.id;
n@544 1977 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
n@544 1978 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
n@544 1979 break;
n@544 1980 case "number":
n@544 1981 node.id = this.id;
n@544 1982 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
n@544 1983 if (this.min != undefined) {node.setAttribute("min", this.min);}
n@544 1984 if (this.max != undefined) {node.setAttribute("max", this.max);}
n@544 1985 break;
n@374 1986 case "checkbox":
n@374 1987 case "radio":
n@374 1988 node.id = this.id;
n@374 1989 for (var i=0; i<this.options.length; i++)
n@374 1990 {
n@374 1991 var option = this.options[i];
n@503 1992 var optionNode = doc.createElement("option");
n@374 1993 optionNode.setAttribute("name",option.name);
n@374 1994 optionNode.textContent = option.text;
n@374 1995 node.appendChild(optionNode);
n@374 1996 }
n@374 1997 break;
nicholas@188 1998 }
n@374 1999 return node;
n@374 2000 };
n@374 2001 };
n@501 2002 this.decode = function(parent,xml) {
n@453 2003 this.location = xml.getAttribute('location');
n@453 2004 if (this.location == 'before'){this.location = 'pre';}
n@453 2005 else if (this.location == 'after'){this.location = 'post';}
n@453 2006 for (var i in xml.children)
n@453 2007 {
n@453 2008 if(isNaN(Number(i))==true){break;}
n@374 2009 var node = new this.OptionNode();
n@501 2010 node.decode(parent,xml.children[i]);
n@374 2011 this.options.push(node);
n@453 2012 }
n@453 2013 };
n@503 2014 this.encode = function(doc) {
n@503 2015 var node = doc.createElement('survey');
n@453 2016 node.setAttribute('location',this.location);
n@453 2017 for (var i=0; i<this.options.length; i++)
n@453 2018 {
n@503 2019 node.appendChild(this.options[i].exportXML(doc));
n@453 2020 }
n@453 2021 return node;
n@453 2022 };
n@453 2023 };
n@453 2024
n@453 2025 this.interfaceNode = function()
n@453 2026 {
n@453 2027 this.title = null;
n@453 2028 this.name = null;
n@453 2029 this.options = [];
n@453 2030 this.scales = [];
n@501 2031 this.schema = specification.schema.getAllElementsByName('interface')[1];
n@453 2032
n@501 2033 this.decode = function(parent,xml) {
n@453 2034 this.name = xml.getAttribute('name');
n@453 2035 var titleNode = xml.getElementsByTagName('title');
n@453 2036 if (titleNode.length == 1)
n@453 2037 {
n@453 2038 this.title = titleNode[0].textContent;
n@453 2039 }
n@453 2040 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
n@453 2041 // Extract interfaceoption node schema
n@501 2042 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
n@477 2043 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
n@453 2044 for (var i=0; i<interfaceOptionNodes.length; i++)
n@453 2045 {
n@453 2046 var ioNode = interfaceOptionNodes[i];
n@453 2047 var option = {};
n@453 2048 for (var j=0; j<attributeMap.length; j++)
n@453 2049 {
n@453 2050 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
n@453 2051 var projectAttr = ioNode.getAttribute(attributeName);
n@453 2052 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
n@453 2053 switch(typeof projectAttr)
n@453 2054 {
n@453 2055 case "number":
n@453 2056 case "boolean":
n@453 2057 eval('option.'+attributeName+' = '+projectAttr);
n@453 2058 break;
n@453 2059 case "string":
n@453 2060 eval('option.'+attributeName+' = "'+projectAttr+'"');
n@453 2061 break;
n@453 2062 }
n@453 2063 }
n@453 2064 this.options.push(option);
n@453 2065 }
n@453 2066
n@453 2067 // Now the scales nodes
n@453 2068 var scaleParent = xml.getElementsByTagName('scales');
n@453 2069 if (scaleParent.length == 1) {
n@453 2070 scaleParent = scaleParent[0];
n@453 2071 for (var i=0; i<scaleParent.children.length; i++) {
n@453 2072 var child = scaleParent.children[i];
n@453 2073 this.scales.push({
n@453 2074 text: child.textContent,
n@453 2075 position: Number(child.getAttribute('position'))
n@453 2076 });
n@374 2077 }
n@180 2078 }
n@180 2079 };
n@453 2080
n@503 2081 this.encode = function(doc) {
n@503 2082 var node = doc.createElement("interface");
n@503 2083 if (typeof name == "string")
n@503 2084 node.setAttribute("name",this.name);
n@503 2085 for (var option of this.options)
n@503 2086 {
n@503 2087 var child = doc.createElement("interfaceoption");
n@503 2088 child.setAttribute("type",option.type);
n@503 2089 child.setAttribute("name",option.name);
n@503 2090 node.appendChild(child);
n@503 2091 }
n@503 2092 if (this.scales.length != 0) {
n@503 2093 var scales = doc.createElement("scales");
n@503 2094 for (var scale of this.scales)
n@503 2095 {
n@503 2096 var child = doc.createElement("scalelabel");
n@503 2097 child.setAttribute("position",scale.position);
n@503 2098 child.textContent = scale.text;
n@503 2099 scales.appendChild(child);
n@503 2100 }
n@503 2101 node.appendChild(scales);
n@503 2102 }
n@503 2103 return node;
n@453 2104 };
n@180 2105 };
n@180 2106
n@501 2107 this.metricNode = function() {
n@501 2108 this.enabled = [];
n@501 2109 this.decode = function(parent, xml) {
n@501 2110 var children = xml.getElementsByTagName('metricenable');
n@501 2111 for (var i in children) {
n@501 2112 if (isNaN(Number(i)) == true){break;}
n@501 2113 this.enabled.push(children[i].textContent);
n@501 2114 }
n@501 2115 }
n@503 2116 this.encode = function(doc) {
n@503 2117 var node = doc.createElement('metric');
n@501 2118 for (var i in this.enabled)
n@501 2119 {
n@501 2120 if (isNaN(Number(i)) == true){break;}
n@503 2121 var child = doc.createElement('metricenable');
n@501 2122 child.textContent = this.enabled[i];
n@501 2123 node.appendChild(child);
n@501 2124 }
n@501 2125 return node;
n@501 2126 }
n@501 2127 }
n@501 2128
n@453 2129 this.page = function() {
n@374 2130 this.presentedId = undefined;
n@374 2131 this.id = undefined;
n@374 2132 this.hostURL = undefined;
n@374 2133 this.randomiseOrder = undefined;
n@374 2134 this.loop = undefined;
n@453 2135 this.showElementComments = undefined;
n@374 2136 this.outsideReference = null;
n@410 2137 this.loudness = null;
n@453 2138 this.preTest = null;
n@453 2139 this.postTest = null;
n@374 2140 this.interfaces = [];
n@374 2141 this.commentBoxPrefix = "Comment on track";
n@374 2142 this.audioElements = [];
n@374 2143 this.commentQuestions = [];
n@501 2144 this.schema = specification.schema.getAllElementsByName("page")[0];
n@374 2145
n@501 2146 this.decode = function(parent,xml)
n@374 2147 {
n@477 2148 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2149 for (var i=0; i<attributeMap.length; i++)
n@410 2150 {
n@453 2151 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2152 var projectAttr = xml.getAttribute(attributeName);
n@453 2153 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
n@453 2154 switch(typeof projectAttr)
nicholas@417 2155 {
n@453 2156 case "number":
n@453 2157 case "boolean":
n@453 2158 eval('this.'+attributeName+' = '+projectAttr);
n@453 2159 break;
n@453 2160 case "string":
n@453 2161 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2162 break;
n@374 2163 }
n@374 2164 }
n@374 2165
n@453 2166 // Get the Comment Box Prefix
n@453 2167 var CBP = xml.getElementsByTagName('commentboxprefix');
n@453 2168 if (CBP.length != 0) {
n@453 2169 this.commentBoxPrefix = CBP[0].textContent;
n@427 2170 }
n@427 2171
n@453 2172 // Now decode the interfaces
n@453 2173 var interfaceNode = xml.getElementsByTagName('interface');
n@453 2174 for (var i=0; i<interfaceNode.length; i++)
n@453 2175 {
n@453 2176 var node = new parent.interfaceNode();
n@477 2177 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
n@453 2178 this.interfaces.push(node);
n@453 2179 }
n@380 2180
n@453 2181 // Now process the survey node options
n@453 2182 var survey = xml.getElementsByTagName('survey');
n@477 2183 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
n@453 2184 for (var i in survey) {
n@453 2185 if (isNaN(Number(i)) == true){break;}
n@453 2186 var location = survey[i].getAttribute('location');
n@453 2187 if (location == 'pre' || location == 'before')
n@453 2188 {
n@453 2189 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@453 2190 else {
n@453 2191 this.preTest = new parent.surveyNode();
n@453 2192 this.preTest.decode(parent,survey[i],surveySchema);
n@453 2193 }
n@453 2194 } else if (location == 'post' || location == 'after') {
n@453 2195 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@453 2196 else {
n@453 2197 this.postTest = new parent.surveyNode();
n@453 2198 this.postTest.decode(parent,survey[i],surveySchema);
n@453 2199 }
n@453 2200 }
n@453 2201 }
n@453 2202
n@453 2203 // Now process the audioelement tags
n@453 2204 var audioElements = xml.getElementsByTagName('audioelement');
n@453 2205 for (var i=0; i<audioElements.length; i++)
n@453 2206 {
n@453 2207 var node = new this.audioElementNode();
n@501 2208 node.decode(this,audioElements[i]);
n@453 2209 this.audioElements.push(node);
n@453 2210 }
n@453 2211
n@453 2212 // Now decode the commentquestions
n@453 2213 var commentQuestions = xml.getElementsByTagName('commentquestion');
n@453 2214 for (var i=0; i<commentQuestions.length; i++)
n@453 2215 {
n@374 2216 var node = new this.commentQuestionNode();
n@501 2217 node.decode(parent,commentQuestions[i]);
n@374 2218 this.commentQuestions.push(node);
n@180 2219 }
n@180 2220 };
n@180 2221
n@374 2222 this.encode = function(root)
n@374 2223 {
n@503 2224 var AHNode = root.createElement("page");
n@503 2225 // First decode the attributes
n@503 2226 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@503 2227 for (var i=0; i<attributes.length; i++)
n@503 2228 {
n@503 2229 var name = attributes[i].getAttribute("name");
n@503 2230 if (name == undefined) {
n@503 2231 name = attributes[i].getAttribute("ref");
n@503 2232 }
n@503 2233 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 2234 {
n@503 2235 eval("AHNode.setAttribute('"+name+"',this."+name+")");
n@503 2236 }
n@503 2237 }
n@410 2238 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
n@503 2239 // <commentboxprefix>
n@503 2240 var commentboxprefix = root.createElement("commentboxprefix");
n@503 2241 commentboxprefix.textContent = this.commentBoxPrefix;
n@503 2242 AHNode.appendChild(commentboxprefix);
n@503 2243
n@374 2244 for (var i=0; i<this.interfaces.length; i++)
n@324 2245 {
n@374 2246 AHNode.appendChild(this.interfaces[i].encode(root));
n@374 2247 }
n@374 2248
n@374 2249 for (var i=0; i<this.audioElements.length; i++) {
n@374 2250 AHNode.appendChild(this.audioElements[i].encode(root));
n@374 2251 }
n@374 2252 // Create <CommentQuestion>
n@374 2253 for (var i=0; i<this.commentQuestions.length; i++)
n@374 2254 {
n@503 2255 AHNode.appendChild(this.commentQuestions[i].encode(root));
n@374 2256 }
n@374 2257
n@503 2258 AHNode.appendChild(this.preTest.encode(root));
n@503 2259 AHNode.appendChild(this.postTest.encode(root));
n@374 2260 return AHNode;
n@374 2261 };
n@374 2262
n@453 2263 this.commentQuestionNode = function() {
n@453 2264 this.id = null;
n@453 2265 this.type = undefined;
n@374 2266 this.options = [];
n@453 2267 this.statement = undefined;
n@501 2268 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
n@501 2269 this.decode = function(parent,xml)
n@374 2270 {
n@453 2271 this.id = xml.id;
n@453 2272 this.type = xml.getAttribute('type');
n@453 2273 this.statement = xml.getElementsByTagName('statement')[0].textContent;
n@453 2274 var optNodes = xml.getElementsByTagName('option');
n@453 2275 for (var i=0; i<optNodes.length; i++)
n@453 2276 {
n@453 2277 var optNode = optNodes[i];
n@453 2278 this.options.push({
n@453 2279 name: optNode.getAttribute('name'),
n@453 2280 text: optNode.textContent
n@453 2281 });
n@374 2282 }
n@374 2283 };
n@453 2284
n@374 2285 this.encode = function(root)
n@374 2286 {
n@503 2287 var node = root.createElement("commentquestion");
n@503 2288 node.id = this.id;
n@503 2289 node.setAttribute("type",this.type);
n@503 2290 var statement = root.createElement("statement");
n@503 2291 statement.textContent = this.statement;
n@503 2292 node.appendChild(statement);
n@503 2293 for (var option of this.options)
n@503 2294 {
n@503 2295 var child = root.createElement("option");
n@503 2296 child.setAttribute("name",option.name);
n@503 2297 child.textContent = option.text;
n@503 2298 node.appendChild(child);
n@503 2299 }
n@503 2300 return node;
n@374 2301 };
n@374 2302 };
n@374 2303
n@374 2304 this.audioElementNode = function() {
n@374 2305 this.url = null;
n@374 2306 this.id = null;
n@374 2307 this.parent = null;
n@453 2308 this.type = null;
n@525 2309 this.marker = null;
n@374 2310 this.enforce = false;
n@400 2311 this.gain = 1.0;
n@501 2312 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
n@453 2313 this.parent = null;
n@501 2314 this.decode = function(parent,xml)
n@374 2315 {
n@374 2316 this.parent = parent;
n@477 2317 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2318 for (var i=0; i<attributeMap.length; i++)
n@400 2319 {
n@453 2320 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2321 var projectAttr = xml.getAttribute(attributeName);
n@453 2322 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
n@453 2323 switch(typeof projectAttr)
n@374 2324 {
n@453 2325 case "number":
n@453 2326 case "boolean":
n@453 2327 eval('this.'+attributeName+' = '+projectAttr);
n@453 2328 break;
n@453 2329 case "string":
n@453 2330 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2331 break;
n@324 2332 }
n@324 2333 }
n@453 2334
n@374 2335 };
n@374 2336 this.encode = function(root)
n@374 2337 {
n@503 2338 var AENode = root.createElement("audioelement");
n@503 2339 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@503 2340 for (var i=0; i<attributes.length; i++)
n@503 2341 {
n@503 2342 var name = attributes[i].getAttribute("name");
n@503 2343 if (name == undefined) {
n@503 2344 name = attributes[i].getAttribute("ref");
n@503 2345 }
n@503 2346 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 2347 {
n@503 2348 eval("AENode.setAttribute('"+name+"',this."+name+")");
n@503 2349 }
n@503 2350 }
n@374 2351 return AENode;
n@374 2352 };
n@180 2353 };
n@180 2354 };
n@180 2355 }
n@374 2356
n@182 2357 function Interface(specificationObject) {
n@180 2358 // This handles the bindings between the interface and the audioEngineContext;
n@182 2359 this.specification = specificationObject;
n@182 2360 this.insertPoint = document.getElementById("topLevelBody");
n@180 2361
n@453 2362 this.newPage = function(audioHolderObject,store)
n@375 2363 {
n@500 2364 audioEngineContext.newTestPage(audioHolderObject,store);
n@550 2365 interfaceContext.commentBoxes.deleteCommentBoxes();
n@375 2366 interfaceContext.deleteCommentQuestions();
n@453 2367 loadTest(audioHolderObject,store);
n@375 2368 };
n@375 2369
n@182 2370 // Bounded by interface!!
n@182 2371 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
n@182 2372 // For example, APE returns the slider position normalised in a <value> tag.
n@182 2373 this.interfaceObjects = [];
n@182 2374 this.interfaceObject = function(){};
n@182 2375
n@302 2376 this.resizeWindow = function(event)
n@302 2377 {
n@395 2378 popup.resize(event);
n@302 2379 for(var i=0; i<this.commentBoxes.length; i++)
n@302 2380 {this.commentBoxes[i].resize();}
n@302 2381 for(var i=0; i<this.commentQuestions.length; i++)
n@302 2382 {this.commentQuestions[i].resize();}
n@302 2383 try
n@302 2384 {
n@302 2385 resizeWindow(event);
n@302 2386 }
n@302 2387 catch(err)
n@302 2388 {
n@302 2389 console.log("Warning - Interface does not have Resize option");
n@302 2390 console.log(err);
n@302 2391 }
n@302 2392 };
n@302 2393
n@356 2394 this.returnNavigator = function()
n@356 2395 {
n@491 2396 var node = storage.document.createElement("navigator");
n@491 2397 var platform = storage.document.createElement("platform");
n@356 2398 platform.textContent = navigator.platform;
n@491 2399 var vendor = storage.document.createElement("vendor");
n@356 2400 vendor.textContent = navigator.vendor;
n@491 2401 var userAgent = storage.document.createElement("uagent");
n@356 2402 userAgent.textContent = navigator.userAgent;
n@491 2403 var screen = storage.document.createElement("window");
n@491 2404 screen.setAttribute('innerWidth',window.innerWidth);
n@491 2405 screen.setAttribute('innerHeight',window.innerHeight);
n@356 2406 node.appendChild(platform);
n@356 2407 node.appendChild(vendor);
n@356 2408 node.appendChild(userAgent);
n@491 2409 node.appendChild(screen);
n@356 2410 return node;
n@356 2411 };
n@356 2412
n@550 2413 this.commentBoxes = new function() {
n@550 2414 this.boxes = [];
n@550 2415 this.injectPoint = null;
n@550 2416 this.elementCommentBox = function(audioObject) {
n@550 2417 var element = audioObject.specification;
n@550 2418 this.audioObject = audioObject;
n@550 2419 this.id = audioObject.id;
n@550 2420 var audioHolderObject = audioObject.specification.parent;
n@550 2421 // Create document objects to hold the comment boxes
n@550 2422 this.trackComment = document.createElement('div');
n@550 2423 this.trackComment.className = 'comment-div';
n@550 2424 this.trackComment.id = 'comment-div-'+audioObject.id;
n@550 2425 // Create a string next to each comment asking for a comment
n@550 2426 this.trackString = document.createElement('span');
n@550 2427 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
n@550 2428 // Create the HTML5 comment box 'textarea'
n@550 2429 this.trackCommentBox = document.createElement('textarea');
n@550 2430 this.trackCommentBox.rows = '4';
n@550 2431 this.trackCommentBox.cols = '100';
n@550 2432 this.trackCommentBox.name = 'trackComment'+audioObject.id;
n@550 2433 this.trackCommentBox.className = 'trackComment';
n@550 2434 var br = document.createElement('br');
n@550 2435 // Add to the holder.
n@550 2436 this.trackComment.appendChild(this.trackString);
n@550 2437 this.trackComment.appendChild(br);
n@550 2438 this.trackComment.appendChild(this.trackCommentBox);
n@550 2439
n@550 2440 this.exportXMLDOM = function() {
n@550 2441 var root = document.createElement('comment');
n@550 2442 var question = document.createElement('question');
n@550 2443 question.textContent = this.trackString.textContent;
n@550 2444 var response = document.createElement('response');
n@550 2445 response.textContent = this.trackCommentBox.value;
n@550 2446 console.log("Comment frag-"+this.id+": "+response.textContent);
n@550 2447 root.appendChild(question);
n@550 2448 root.appendChild(response);
n@550 2449 return root;
n@550 2450 };
n@550 2451 this.resize = function()
n@550 2452 {
n@550 2453 var boxwidth = (window.innerWidth-100)/2;
n@550 2454 if (boxwidth >= 600)
n@550 2455 {
n@550 2456 boxwidth = 600;
n@550 2457 }
n@550 2458 else if (boxwidth < 400)
n@550 2459 {
n@550 2460 boxwidth = 400;
n@550 2461 }
n@550 2462 this.trackComment.style.width = boxwidth+"px";
n@550 2463 this.trackCommentBox.style.width = boxwidth-6+"px";
n@550 2464 };
n@550 2465 this.resize();
n@550 2466 };
n@550 2467 this.createCommentBox = function(audioObject) {
n@550 2468 var node = new this.elementCommentBox(audioObject);
n@550 2469 this.boxes.push(node);
n@550 2470 audioObject.commentDOM = node;
n@550 2471 return node;
n@550 2472 };
n@550 2473 this.sortCommentBoxes = function() {
n@550 2474 this.boxes.sort(function(a,b){return a.id - b.id;});
n@550 2475 };
n@550 2476
n@550 2477 this.showCommentBoxes = function(inject, sort) {
n@550 2478 this.injectPoint = inject;
n@550 2479 if (sort) {this.sortCommentBoxes();}
n@550 2480 for (var box of this.boxes) {
n@550 2481 inject.appendChild(box.trackComment);
n@550 2482 }
n@550 2483 };
n@550 2484
n@550 2485 this.deleteCommentBoxes = function() {
n@550 2486 if (this.injectPoint != null) {
n@550 2487 for (var box of this.boxes) {
n@550 2488 this.injectPoint.removeChild(box.trackComment);
n@550 2489 }
n@550 2490 this.injectPoint = null;
n@550 2491 }
n@550 2492 this.boxes = [];
n@550 2493 };
n@550 2494 }
n@182 2495
n@193 2496 this.commentQuestions = [];
n@193 2497
n@193 2498 this.commentBox = function(commentQuestion) {
n@193 2499 this.specification = commentQuestion;
n@193 2500 // Create document objects to hold the comment boxes
n@193 2501 this.holder = document.createElement('div');
n@193 2502 this.holder.className = 'comment-div';
n@193 2503 // Create a string next to each comment asking for a comment
n@193 2504 this.string = document.createElement('span');
n@453 2505 this.string.innerHTML = commentQuestion.statement;
n@193 2506 // Create the HTML5 comment box 'textarea'
n@193 2507 this.textArea = document.createElement('textarea');
n@193 2508 this.textArea.rows = '4';
n@193 2509 this.textArea.cols = '100';
n@193 2510 this.textArea.className = 'trackComment';
n@193 2511 var br = document.createElement('br');
n@193 2512 // Add to the holder.
n@193 2513 this.holder.appendChild(this.string);
n@193 2514 this.holder.appendChild(br);
n@193 2515 this.holder.appendChild(this.textArea);
n@193 2516
n@520 2517 this.exportXMLDOM = function(storePoint) {
n@520 2518 var root = storePoint.parent.document.createElement('comment');
n@193 2519 root.id = this.specification.id;
n@193 2520 root.setAttribute('type',this.specification.type);
b@254 2521 console.log("Question: "+this.string.textContent);
b@254 2522 console.log("Response: "+root.textContent);
n@520 2523 var question = storePoint.parent.document.createElement('question');
n@520 2524 question.textContent = this.string.textContent;
n@520 2525 var response = storePoint.parent.document.createElement('response');
n@520 2526 response.textContent = this.textArea.value;
n@520 2527 root.appendChild(question);
n@520 2528 root.appendChild(response);
n@520 2529 storePoint.XMLDOM.appendChild(root);
n@193 2530 return root;
n@193 2531 };
n@302 2532 this.resize = function()
n@302 2533 {
n@302 2534 var boxwidth = (window.innerWidth-100)/2;
n@302 2535 if (boxwidth >= 600)
n@302 2536 {
n@302 2537 boxwidth = 600;
n@302 2538 }
n@302 2539 else if (boxwidth < 400)
n@302 2540 {
n@302 2541 boxwidth = 400;
n@302 2542 }
n@302 2543 this.holder.style.width = boxwidth+"px";
n@302 2544 this.textArea.style.width = boxwidth-6+"px";
n@302 2545 };
n@302 2546 this.resize();
n@193 2547 };
n@193 2548
n@193 2549 this.radioBox = function(commentQuestion) {
n@193 2550 this.specification = commentQuestion;
n@193 2551 // Create document objects to hold the comment boxes
n@193 2552 this.holder = document.createElement('div');
n@193 2553 this.holder.className = 'comment-div';
n@193 2554 // Create a string next to each comment asking for a comment
n@193 2555 this.string = document.createElement('span');
n@193 2556 this.string.innerHTML = commentQuestion.statement;
n@193 2557 var br = document.createElement('br');
n@193 2558 // Add to the holder.
n@193 2559 this.holder.appendChild(this.string);
n@193 2560 this.holder.appendChild(br);
n@193 2561 this.options = [];
n@193 2562 this.inputs = document.createElement('div');
n@193 2563 this.span = document.createElement('div');
n@193 2564 this.inputs.align = 'center';
n@193 2565 this.inputs.style.marginLeft = '12px';
n@193 2566 this.span.style.marginLeft = '12px';
n@193 2567 this.span.align = 'center';
n@193 2568 this.span.style.marginTop = '15px';
n@193 2569
n@193 2570 var optCount = commentQuestion.options.length;
n@453 2571 for (var optNode of commentQuestion.options)
n@193 2572 {
n@193 2573 var div = document.createElement('div');
n@301 2574 div.style.width = '80px';
n@193 2575 div.style.float = 'left';
n@193 2576 var input = document.createElement('input');
n@193 2577 input.type = 'radio';
n@193 2578 input.name = commentQuestion.id;
n@453 2579 input.setAttribute('setvalue',optNode.name);
n@193 2580 input.className = 'comment-radio';
n@193 2581 div.appendChild(input);
n@193 2582 this.inputs.appendChild(div);
n@193 2583
n@193 2584
n@193 2585 div = document.createElement('div');
n@301 2586 div.style.width = '80px';
n@193 2587 div.style.float = 'left';
n@193 2588 div.align = 'center';
n@193 2589 var span = document.createElement('span');
n@453 2590 span.textContent = optNode.text;
n@193 2591 span.className = 'comment-radio-span';
n@193 2592 div.appendChild(span);
n@193 2593 this.span.appendChild(div);
n@193 2594 this.options.push(input);
n@193 2595 }
n@193 2596 this.holder.appendChild(this.span);
n@193 2597 this.holder.appendChild(this.inputs);
n@193 2598
n@520 2599 this.exportXMLDOM = function(storePoint) {
n@520 2600 var root = storePoint.parent.document.createElement('comment');
n@193 2601 root.id = this.specification.id;
n@193 2602 root.setAttribute('type',this.specification.type);
n@193 2603 var question = document.createElement('question');
n@193 2604 question.textContent = this.string.textContent;
n@193 2605 var response = document.createElement('response');
n@193 2606 var i=0;
n@193 2607 while(this.options[i].checked == false) {
n@193 2608 i++;
n@193 2609 if (i >= this.options.length) {
n@193 2610 break;
n@193 2611 }
n@193 2612 }
n@193 2613 if (i >= this.options.length) {
n@193 2614 response.textContent = 'null';
n@193 2615 } else {
n@193 2616 response.textContent = this.options[i].getAttribute('setvalue');
n@193 2617 response.setAttribute('number',i);
n@193 2618 }
n@195 2619 console.log('Comment: '+question.textContent);
n@195 2620 console.log('Response: '+response.textContent);
n@193 2621 root.appendChild(question);
n@193 2622 root.appendChild(response);
n@520 2623 storePoint.XMLDOM.appendChild(root);
n@193 2624 return root;
n@193 2625 };
n@302 2626 this.resize = function()
n@302 2627 {
n@302 2628 var boxwidth = (window.innerWidth-100)/2;
n@302 2629 if (boxwidth >= 600)
n@302 2630 {
n@302 2631 boxwidth = 600;
n@302 2632 }
n@302 2633 else if (boxwidth < 400)
n@302 2634 {
n@302 2635 boxwidth = 400;
n@302 2636 }
n@302 2637 this.holder.style.width = boxwidth+"px";
n@302 2638 var text = this.holder.children[2];
n@302 2639 var options = this.holder.children[3];
n@302 2640 var optCount = options.children.length;
n@302 2641 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@302 2642 var options = options.firstChild;
n@302 2643 var text = text.firstChild;
n@302 2644 options.style.marginRight = spanMargin;
n@302 2645 options.style.marginLeft = spanMargin;
n@302 2646 text.style.marginRight = spanMargin;
n@302 2647 text.style.marginLeft = spanMargin;
n@302 2648 while(options.nextSibling != undefined)
n@302 2649 {
n@302 2650 options = options.nextSibling;
n@302 2651 text = text.nextSibling;
n@302 2652 options.style.marginRight = spanMargin;
n@302 2653 options.style.marginLeft = spanMargin;
n@302 2654 text.style.marginRight = spanMargin;
n@302 2655 text.style.marginLeft = spanMargin;
n@302 2656 }
n@302 2657 };
n@302 2658 this.resize();
n@193 2659 };
n@193 2660
n@195 2661 this.checkboxBox = function(commentQuestion) {
n@195 2662 this.specification = commentQuestion;
n@195 2663 // Create document objects to hold the comment boxes
n@195 2664 this.holder = document.createElement('div');
n@195 2665 this.holder.className = 'comment-div';
n@195 2666 // Create a string next to each comment asking for a comment
n@195 2667 this.string = document.createElement('span');
n@195 2668 this.string.innerHTML = commentQuestion.statement;
n@195 2669 var br = document.createElement('br');
n@195 2670 // Add to the holder.
n@195 2671 this.holder.appendChild(this.string);
n@195 2672 this.holder.appendChild(br);
n@195 2673 this.options = [];
n@195 2674 this.inputs = document.createElement('div');
n@195 2675 this.span = document.createElement('div');
n@195 2676 this.inputs.align = 'center';
n@195 2677 this.inputs.style.marginLeft = '12px';
n@195 2678 this.span.style.marginLeft = '12px';
n@195 2679 this.span.align = 'center';
n@195 2680 this.span.style.marginTop = '15px';
n@195 2681
n@195 2682 var optCount = commentQuestion.options.length;
n@195 2683 for (var i=0; i<optCount; i++)
n@195 2684 {
n@195 2685 var div = document.createElement('div');
n@301 2686 div.style.width = '80px';
n@195 2687 div.style.float = 'left';
n@195 2688 var input = document.createElement('input');
n@195 2689 input.type = 'checkbox';
n@195 2690 input.name = commentQuestion.id;
n@195 2691 input.setAttribute('setvalue',commentQuestion.options[i].name);
n@195 2692 input.className = 'comment-radio';
n@195 2693 div.appendChild(input);
n@195 2694 this.inputs.appendChild(div);
n@195 2695
n@195 2696
n@195 2697 div = document.createElement('div');
n@301 2698 div.style.width = '80px';
n@195 2699 div.style.float = 'left';
n@195 2700 div.align = 'center';
n@195 2701 var span = document.createElement('span');
n@195 2702 span.textContent = commentQuestion.options[i].text;
n@195 2703 span.className = 'comment-radio-span';
n@195 2704 div.appendChild(span);
n@195 2705 this.span.appendChild(div);
n@195 2706 this.options.push(input);
n@195 2707 }
n@195 2708 this.holder.appendChild(this.span);
n@195 2709 this.holder.appendChild(this.inputs);
n@195 2710
n@520 2711 this.exportXMLDOM = function(storePoint) {
n@520 2712 var root = storePoint.parent.document.createElement('comment');
n@195 2713 root.id = this.specification.id;
n@195 2714 root.setAttribute('type',this.specification.type);
n@195 2715 var question = document.createElement('question');
n@195 2716 question.textContent = this.string.textContent;
n@195 2717 root.appendChild(question);
n@195 2718 console.log('Comment: '+question.textContent);
n@195 2719 for (var i=0; i<this.options.length; i++) {
n@195 2720 var response = document.createElement('response');
n@195 2721 response.textContent = this.options[i].checked;
n@195 2722 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
n@195 2723 root.appendChild(response);
n@195 2724 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
n@195 2725 }
n@520 2726 storePoint.XMLDOM.appendChild(root);
n@195 2727 return root;
n@195 2728 };
n@302 2729 this.resize = function()
n@302 2730 {
n@302 2731 var boxwidth = (window.innerWidth-100)/2;
n@302 2732 if (boxwidth >= 600)
n@302 2733 {
n@302 2734 boxwidth = 600;
n@302 2735 }
n@302 2736 else if (boxwidth < 400)
n@302 2737 {
n@302 2738 boxwidth = 400;
n@302 2739 }
n@302 2740 this.holder.style.width = boxwidth+"px";
n@302 2741 var text = this.holder.children[2];
n@302 2742 var options = this.holder.children[3];
n@302 2743 var optCount = options.children.length;
n@302 2744 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@302 2745 var options = options.firstChild;
n@302 2746 var text = text.firstChild;
n@302 2747 options.style.marginRight = spanMargin;
n@302 2748 options.style.marginLeft = spanMargin;
n@302 2749 text.style.marginRight = spanMargin;
n@302 2750 text.style.marginLeft = spanMargin;
n@302 2751 while(options.nextSibling != undefined)
n@302 2752 {
n@302 2753 options = options.nextSibling;
n@302 2754 text = text.nextSibling;
n@302 2755 options.style.marginRight = spanMargin;
n@302 2756 options.style.marginLeft = spanMargin;
n@302 2757 text.style.marginRight = spanMargin;
n@302 2758 text.style.marginLeft = spanMargin;
n@302 2759 }
n@302 2760 };
n@302 2761 this.resize();
n@195 2762 };
nicholas@211 2763
n@193 2764 this.createCommentQuestion = function(element) {
n@193 2765 var node;
n@453 2766 if (element.type == 'question') {
n@193 2767 node = new this.commentBox(element);
n@193 2768 } else if (element.type == 'radio') {
n@193 2769 node = new this.radioBox(element);
n@195 2770 } else if (element.type == 'checkbox') {
n@195 2771 node = new this.checkboxBox(element);
n@193 2772 }
n@193 2773 this.commentQuestions.push(node);
n@193 2774 return node;
n@193 2775 };
n@201 2776
nicholas@237 2777 this.deleteCommentQuestions = function()
nicholas@237 2778 {
nicholas@237 2779 this.commentQuestions = [];
nicholas@237 2780 };
nicholas@237 2781
n@201 2782 this.playhead = new function()
n@201 2783 {
n@201 2784 this.object = document.createElement('div');
n@201 2785 this.object.className = 'playhead';
n@201 2786 this.object.align = 'left';
n@201 2787 var curTime = document.createElement('div');
n@201 2788 curTime.style.width = '50px';
n@201 2789 this.curTimeSpan = document.createElement('span');
n@201 2790 this.curTimeSpan.textContent = '00:00';
n@201 2791 curTime.appendChild(this.curTimeSpan);
n@201 2792 this.object.appendChild(curTime);
n@201 2793 this.scrubberTrack = document.createElement('div');
n@201 2794 this.scrubberTrack.className = 'playhead-scrub-track';
n@201 2795
n@201 2796 this.scrubberHead = document.createElement('div');
n@201 2797 this.scrubberHead.id = 'playhead-scrubber';
n@201 2798 this.scrubberTrack.appendChild(this.scrubberHead);
n@201 2799 this.object.appendChild(this.scrubberTrack);
n@201 2800
n@201 2801 this.timePerPixel = 0;
n@201 2802 this.maxTime = 0;
n@201 2803
n@204 2804 this.playbackObject;
n@204 2805
n@204 2806 this.setTimePerPixel = function(audioObject) {
n@201 2807 //maxTime must be in seconds
n@204 2808 this.playbackObject = audioObject;
n@379 2809 this.maxTime = audioObject.buffer.buffer.duration;
n@201 2810 var width = 490; //500 - 10, 5 each side of the tracker head
n@204 2811 this.timePerPixel = this.maxTime/490;
n@204 2812 if (this.maxTime < 60) {
n@201 2813 this.curTimeSpan.textContent = '0.00';
n@201 2814 } else {
n@201 2815 this.curTimeSpan.textContent = '00:00';
n@201 2816 }
n@201 2817 };
n@201 2818
n@204 2819 this.update = function() {
n@201 2820 // Update the playhead position, startPlay must be called
n@201 2821 if (this.timePerPixel > 0) {
n@204 2822 var time = this.playbackObject.getCurrentPosition();
n@498 2823 if (time > 0 && time < this.maxTime) {
nicholas@267 2824 var width = 490;
nicholas@267 2825 var pix = Math.floor(time/this.timePerPixel);
nicholas@267 2826 this.scrubberHead.style.left = pix+'px';
nicholas@267 2827 if (this.maxTime > 60.0) {
nicholas@267 2828 var secs = time%60;
nicholas@267 2829 var mins = Math.floor((time-secs)/60);
nicholas@267 2830 secs = secs.toString();
nicholas@267 2831 secs = secs.substr(0,2);
nicholas@267 2832 mins = mins.toString();
nicholas@267 2833 this.curTimeSpan.textContent = mins+':'+secs;
nicholas@267 2834 } else {
nicholas@267 2835 time = time.toString();
nicholas@267 2836 this.curTimeSpan.textContent = time.substr(0,4);
nicholas@267 2837 }
n@201 2838 } else {
nicholas@267 2839 this.scrubberHead.style.left = '0px';
nicholas@267 2840 if (this.maxTime < 60) {
nicholas@267 2841 this.curTimeSpan.textContent = '0.00';
nicholas@267 2842 } else {
nicholas@267 2843 this.curTimeSpan.textContent = '00:00';
nicholas@267 2844 }
n@201 2845 }
n@201 2846 }
n@201 2847 };
n@204 2848
n@204 2849 this.interval = undefined;
n@204 2850
n@204 2851 this.start = function() {
n@204 2852 if (this.playbackObject != undefined && this.interval == undefined) {
nicholas@267 2853 if (this.maxTime < 60) {
nicholas@267 2854 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nicholas@267 2855 } else {
nicholas@267 2856 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nicholas@267 2857 }
n@204 2858 }
n@204 2859 };
n@204 2860 this.stop = function() {
n@204 2861 clearInterval(this.interval);
n@204 2862 this.interval = undefined;
n@527 2863 this.scrubberHead.style.left = '0px';
nicholas@267 2864 if (this.maxTime < 60) {
nicholas@267 2865 this.curTimeSpan.textContent = '0.00';
nicholas@267 2866 } else {
nicholas@267 2867 this.curTimeSpan.textContent = '00:00';
nicholas@267 2868 }
n@204 2869 };
n@201 2870 };
n@483 2871
n@483 2872 this.volume = new function()
n@483 2873 {
n@483 2874 // An in-built volume module which can be viewed on page
n@483 2875 // Includes trackers on page-by-page data
n@483 2876 // Volume does NOT reset to 0dB on each page load
n@483 2877 this.valueLin = 1.0;
n@483 2878 this.valueDB = 0.0;
n@483 2879 this.object = document.createElement('div');
n@483 2880 this.object.id = 'master-volume-holder';
n@483 2881 this.slider = document.createElement('input');
n@483 2882 this.slider.id = 'master-volume-control';
n@483 2883 this.slider.type = 'range';
n@483 2884 this.valueText = document.createElement('span');
n@483 2885 this.valueText.id = 'master-volume-feedback';
n@483 2886 this.valueText.textContent = '0dB';
n@483 2887
n@483 2888 this.slider.min = -60;
n@483 2889 this.slider.max = 12;
n@483 2890 this.slider.value = 0;
n@483 2891 this.slider.step = 1;
n@483 2892 this.slider.onmousemove = function(event)
n@483 2893 {
n@483 2894 interfaceContext.volume.valueDB = event.currentTarget.value;
n@483 2895 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
n@483 2896 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
n@483 2897 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
n@483 2898 }
n@483 2899 this.slider.onmouseup = function(event)
n@483 2900 {
n@526 2901 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
n@483 2902 if (storePoint.length == 0)
n@483 2903 {
n@483 2904 storePoint = storage.document.createElement('metricresult');
n@483 2905 storePoint.setAttribute('name','volumeTracker');
n@526 2906 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
n@483 2907 }
n@483 2908 else {
n@483 2909 storePoint = storePoint[0];
n@483 2910 }
n@483 2911 var node = storage.document.createElement('movement');
n@483 2912 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
n@483 2913 node.setAttribute('volume',interfaceContext.volume.valueDB);
n@483 2914 node.setAttribute('format','dBFS');
n@483 2915 storePoint.appendChild(node);
n@483 2916 }
n@483 2917
n@484 2918 var title = document.createElement('div');
n@484 2919 title.innerHTML = '<span>Master Volume Control</span>';
n@484 2920 title.style.fontSize = '0.75em';
n@484 2921 title.style.width = "100%";
n@484 2922 title.align = 'center';
n@484 2923 this.object.appendChild(title);
n@484 2924
n@483 2925 this.object.appendChild(this.slider);
n@483 2926 this.object.appendChild(this.valueText);
n@483 2927 }
nicholas@235 2928 // Global Checkers
nicholas@235 2929 // These functions will help enforce the checkers
nicholas@235 2930 this.checkHiddenAnchor = function()
nicholas@235 2931 {
n@453 2932 for (var ao of audioEngineContext.audioObjects)
nicholas@235 2933 {
n@453 2934 if (ao.specification.type == "anchor")
nicholas@235 2935 {
n@454 2936 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
n@453 2937 // Anchor is not set below
n@453 2938 console.log('Anchor node not below marker value');
n@453 2939 alert('Please keep listening');
n@498 2940 this.storeErrorNode('Anchor node not below marker value');
n@453 2941 return false;
n@453 2942 }
nicholas@235 2943 }
nicholas@235 2944 }
nicholas@235 2945 return true;
nicholas@235 2946 };
nicholas@235 2947
nicholas@235 2948 this.checkHiddenReference = function()
nicholas@235 2949 {
n@453 2950 for (var ao of audioEngineContext.audioObjects)
nicholas@235 2951 {
n@453 2952 if (ao.specification.type == "reference")
nicholas@235 2953 {
n@454 2954 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
n@453 2955 // Anchor is not set below
n@498 2956 console.log('Reference node not above marker value');
n@498 2957 this.storeErrorNode('Reference node not above marker value');
n@453 2958 alert('Please keep listening');
n@453 2959 return false;
n@453 2960 }
nicholas@235 2961 }
nicholas@235 2962 }
nicholas@235 2963 return true;
nicholas@235 2964 };
n@366 2965
n@366 2966 this.checkFragmentsFullyPlayed = function ()
n@366 2967 {
n@366 2968 // Checks the entire file has been played back
n@366 2969 // NOTE ! This will return true IF playback is Looped!!!
n@366 2970 if (audioEngineContext.loopPlayback)
n@366 2971 {
n@366 2972 console.log("WARNING - Looped source: Cannot check fragments are fully played");
n@366 2973 return true;
n@366 2974 }
n@366 2975 var check_pass = true;
n@366 2976 var error_obj = [];
n@366 2977 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
n@366 2978 {
n@366 2979 var object = audioEngineContext.audioObjects[i];
nicholas@415 2980 var time = object.buffer.buffer.duration;
n@366 2981 var metric = object.metric;
n@366 2982 var passed = false;
n@366 2983 for (var j=0; j<metric.listenTracker.length; j++)
n@366 2984 {
n@366 2985 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
n@366 2986 var start_time = Number(bt[0].getAttribute('start'));
n@366 2987 var stop_time = Number(bt[0].getAttribute('stop'));
n@366 2988 var delta = stop_time - start_time;
n@366 2989 if (delta >= time)
n@366 2990 {
n@366 2991 passed = true;
n@366 2992 break;
n@366 2993 }
n@366 2994 }
n@366 2995 if (passed == false)
n@366 2996 {
n@366 2997 check_pass = false;
n@469 2998 console.log("Continue listening to track-"+audioEngineContext.audioObjects.interfaceDOM.getPresentedId());
n@469 2999 error_obj.push(audioEngineContext.audioObjects.interfaceDOM.getPresentedId());
n@366 3000 }
n@366 3001 }
n@366 3002 if (check_pass == false)
n@366 3003 {
nicholas@415 3004 var str_start = "You have not completely listened to fragments ";
n@366 3005 for (var i=0; i<error_obj.length; i++)
n@366 3006 {
n@366 3007 str_start += error_obj[i];
n@366 3008 if (i != error_obj.length-1)
n@366 3009 {
n@366 3010 str_start += ', ';
n@366 3011 }
n@366 3012 }
n@366 3013 str_start += ". Please keep listening";
n@366 3014 console.log("[ALERT]: "+str_start);
n@498 3015 this.storeErrorNode("[ALERT]: "+str_start);
n@366 3016 alert(str_start);
n@366 3017 }
n@366 3018 };
nicholas@421 3019 this.checkAllMoved = function()
nicholas@421 3020 {
nicholas@421 3021 var str = "You have not moved ";
nicholas@421 3022 var failed = [];
n@469 3023 for (var ao of audioEngineContext.audioObjects)
nicholas@421 3024 {
n@469 3025 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
nicholas@421 3026 {
n@469 3027 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@421 3028 }
nicholas@421 3029 }
nicholas@421 3030 if (failed.length == 0)
nicholas@421 3031 {
nicholas@421 3032 return true;
nicholas@421 3033 } else if (failed.length == 1)
nicholas@421 3034 {
nicholas@421 3035 str += 'track '+failed[0];
nicholas@421 3036 } else {
nicholas@421 3037 str += 'tracks ';
nicholas@421 3038 for (var i=0; i<failed.length-1; i++)
nicholas@421 3039 {
nicholas@421 3040 str += failed[i]+', ';
nicholas@421 3041 }
nicholas@421 3042 str += 'and '+failed[i];
nicholas@421 3043 }
nicholas@421 3044 str +='.';
nicholas@421 3045 alert(str);
nicholas@421 3046 console.log(str);
n@498 3047 this.storeErrorNode(str);
nicholas@421 3048 return false;
nicholas@421 3049 };
nicholas@421 3050 this.checkAllPlayed = function()
nicholas@421 3051 {
nicholas@421 3052 var str = "You have not played ";
nicholas@421 3053 var failed = [];
n@469 3054 for (var ao of audioEngineContext.audioObjects)
nicholas@421 3055 {
n@469 3056 if(ao.metric.wasListenedTo == false)
nicholas@421 3057 {
n@469 3058 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@421 3059 }
nicholas@421 3060 }
nicholas@421 3061 if (failed.length == 0)
nicholas@421 3062 {
nicholas@421 3063 return true;
nicholas@421 3064 } else if (failed.length == 1)
nicholas@421 3065 {
nicholas@421 3066 str += 'track '+failed[0];
nicholas@421 3067 } else {
nicholas@421 3068 str += 'tracks ';
nicholas@421 3069 for (var i=0; i<failed.length-1; i++)
nicholas@421 3070 {
nicholas@421 3071 str += failed[i]+', ';
nicholas@421 3072 }
nicholas@421 3073 str += 'and '+failed[i];
nicholas@421 3074 }
nicholas@421 3075 str +='.';
nicholas@421 3076 alert(str);
nicholas@421 3077 console.log(str);
n@498 3078 this.storeErrorNode(str);
nicholas@421 3079 return false;
nicholas@421 3080 };
n@498 3081
n@498 3082 this.storeErrorNode = function(errorMessage)
n@498 3083 {
n@498 3084 var time = audioEngineContext.timer.getTestTime();
n@498 3085 var node = storage.document.createElement('error');
n@498 3086 node.setAttribute('time',time);
n@498 3087 node.textContent = errorMessage;
n@498 3088 testState.currentStore.XMLDOM.appendChild(node);
n@498 3089 };
n@453 3090 }
n@453 3091
n@453 3092 function Storage()
n@453 3093 {
n@453 3094 // Holds results in XML format until ready for collection
n@453 3095 this.globalPreTest = null;
n@453 3096 this.globalPostTest = null;
n@453 3097 this.testPages = [];
n@453 3098 this.document = document.implementation.createDocument(null,"waetresult");
n@551 3099 this.root = this.document.childNodes[0];
n@453 3100 this.state = 0;
n@453 3101
n@453 3102 this.initialise = function()
n@453 3103 {
n@471 3104 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
n@471 3105 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
n@453 3106 };
n@453 3107
n@453 3108 this.createTestPageStore = function(specification)
n@453 3109 {
n@453 3110 var store = new this.pageNode(this,specification);
n@453 3111 this.testPages.push(store);
n@453 3112 return this.testPages[this.testPages.length-1];
n@453 3113 };
n@453 3114
n@453 3115 this.surveyNode = function(parent,root,specification)
n@453 3116 {
n@453 3117 this.specification = specification;
n@453 3118 this.parent = parent;
n@453 3119 this.XMLDOM = this.parent.document.createElement('survey');
n@453 3120 this.XMLDOM.setAttribute('location',this.specification.location);
n@453 3121 for (var optNode of this.specification.options)
n@453 3122 {
n@453 3123 if (optNode.type != 'statement')
n@453 3124 {
n@453 3125 var node = this.parent.document.createElement('surveyresult');
n@453 3126 node.id = optNode.id;
n@453 3127 node.setAttribute('type',optNode.type);
n@453 3128 this.XMLDOM.appendChild(node);
n@453 3129 }
n@453 3130 }
n@453 3131 root.appendChild(this.XMLDOM);
n@453 3132
n@453 3133 this.postResult = function(node)
n@453 3134 {
n@453 3135 // From popup: node is the popupOption node containing both spec. and results
n@453 3136 // ID is the position
n@453 3137 if (node.specification.type == 'statement'){return;}
n@453 3138 var surveyresult = this.parent.document.getElementById(node.specification.id);
n@453 3139 switch(node.specification.type)
n@453 3140 {
n@453 3141 case "number":
n@453 3142 case "question":
n@453 3143 var child = this.parent.document.createElement('response');
n@453 3144 child.textContent = node.response;
n@453 3145 surveyresult.appendChild(child);
n@453 3146 break;
n@453 3147 case "radio":
n@453 3148 var child = this.parent.document.createElement('response');
n@453 3149 child.setAttribute('name',node.response.name);
n@453 3150 child.textContent = node.response.text;
n@453 3151 surveyresult.appendChild(child);
n@453 3152 break;
n@453 3153 case "checkbox":
n@453 3154 for (var i=0; i<node.response.length; i++)
n@453 3155 {
n@453 3156 var checkNode = this.parent.document.createElement('response');
n@476 3157 checkNode.setAttribute('name',node.response[i].name);
n@476 3158 checkNode.setAttribute('checked',node.response[i].checked);
n@455 3159 surveyresult.appendChild(checkNode);
n@453 3160 }
n@453 3161 break;
n@453 3162 }
n@453 3163 };
n@453 3164 };
n@453 3165
n@453 3166 this.pageNode = function(parent,specification)
n@453 3167 {
n@453 3168 // Create one store per test page
n@453 3169 this.specification = specification;
n@453 3170 this.parent = parent;
n@453 3171 this.XMLDOM = this.parent.document.createElement('page');
n@453 3172 this.XMLDOM.setAttribute('id',specification.id);
n@453 3173 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
n@474 3174 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
n@474 3175 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
n@453 3176
n@453 3177 // Add any page metrics
n@453 3178 var page_metric = this.parent.document.createElement('metric');
n@453 3179 this.XMLDOM.appendChild(page_metric);
n@453 3180
n@453 3181 // Add the audioelement
n@453 3182 for (var element of this.specification.audioElements)
n@453 3183 {
n@453 3184 var aeNode = this.parent.document.createElement('audioelement');
n@453 3185 aeNode.id = element.id;
n@453 3186 aeNode.setAttribute('type',element.type);
n@453 3187 aeNode.setAttribute('url', element.url);
n@453 3188 aeNode.setAttribute('gain', element.gain);
n@453 3189 if (element.type == 'anchor' || element.type == 'reference')
n@453 3190 {
n@453 3191 if (element.marker > 0)
n@453 3192 {
n@453 3193 aeNode.setAttribute('marker',element.marker);
n@453 3194 }
n@453 3195 }
n@453 3196 var ae_metric = this.parent.document.createElement('metric');
n@453 3197 aeNode.appendChild(ae_metric);
n@453 3198 this.XMLDOM.appendChild(aeNode);
n@453 3199 }
n@453 3200
n@453 3201 this.parent.root.appendChild(this.XMLDOM);
n@453 3202 };
n@453 3203 this.finish = function()
n@453 3204 {
n@453 3205 if (this.state == 0)
n@453 3206 {
n@453 3207 var projectDocument = specification.projectXML;
n@453 3208 projectDocument.setAttribute('file-name',url);
n@453 3209 this.root.appendChild(projectDocument);
n@453 3210 this.root.appendChild(returnDateNode());
n@453 3211 this.root.appendChild(interfaceContext.returnNavigator());
n@453 3212 }
n@453 3213 this.state = 1;
n@453 3214 return this.root;
n@453 3215 };
n@453 3216 }