annotate core.js @ 544:e0f95b33ba31 Dev_main

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