annotate js/core.js @ 2615:8b8f3f3ce68e

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