annotate core.js @ 2202:61c8e13f1e2e

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