annotate core.js @ 643:f9e9d94c9b9a Dev_main

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