annotate js/core.js @ 2312:483bd6573747

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