annotate js/core.js @ 2331:ee5e2db32f7c

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