annotate js/core.js @ 2510:8536e978ab6f

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