annotate core.js @ 644:e465cdd2ca38 Dev_main

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