annotate core.js @ 607:328f24798462 multiple-tests-concatenation

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