annotate js/core.js @ 2616:ac08e4a3a94b

#101.Mostly done. Loads up alternatives if one of them fails, until end of URL lists
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Tue, 15 Nov 2016 15:31:53 +0000
parents 8b8f3f3ce68e
children cce1fb4009f2
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 this.urls = [];
nicholas@2498 1492 this.buffer = null;
nicholas@2498 1493 this.users = [];
nicholas@2224 1494 this.progress = 0;
nicholas@2224 1495 this.status = 0;
nicholas@2498 1496 this.ready = function () {
nicholas@2498 1497 if (this.status >= 2) {
nicholas@2224 1498 this.status = 3;
nicholas@2224 1499 }
nicholas@2498 1500 for (var i = 0; i < this.users.length; i++) {
nicholas@2498 1501 this.users[i].state = 1;
nicholas@2498 1502 if (this.users[i].interfaceDOM != null) {
nicholas@2498 1503 this.users[i].bufferLoaded(this);
nicholas@2498 1504 }
nicholas@2498 1505 }
nicholas@2498 1506 };
nicholas@2615 1507 this.getMedia = function (urls) {
nicholas@2615 1508 var self = this;
nicholas@2616 1509 var currentUrlIndex = 0;
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@2616 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
nicholas@2615 1597 this.progress = 0;
nicholas@2224 1598 this.status = 1;
nicholas@2615 1599 get(this.urls[currentUrlIndex]).then(processAudio.bind(self)).catch(getNextURL.bind(self));
nicholas@2498 1600 };
nicholas@2498 1601
nicholas@2498 1602 this.registerAudioObject = function (audioObject) {
nicholas@2224 1603 // Called by an audioObject to register to the buffer for use
nicholas@2224 1604 // First check if already in the register pool
nicholas@2498 1605 for (var objects of this.users) {
nicholas@2498 1606 if (audioObject.id == objects.id) {
nicholas@2498 1607 return 0;
nicholas@2498 1608 }
nicholas@2224 1609 }
nicholas@2224 1610 this.users.push(audioObject);
nicholas@2498 1611 if (this.status == 3 || this.status == -1) {
nicholas@2224 1612 // The buffer is already ready, trigger bufferLoaded
nicholas@2224 1613 audioObject.bufferLoaded(this);
nicholas@2224 1614 }
nicholas@2224 1615 };
nicholas@2498 1616
nicholas@2498 1617 this.copyBuffer = function (preSilenceTime, postSilenceTime) {
nicholas@2224 1618 // Copies the entire bufferObj.
nicholas@2498 1619 if (preSilenceTime == undefined) {
nicholas@2498 1620 preSilenceTime = 0;
nicholas@2498 1621 }
nicholas@2498 1622 if (postSilenceTime == undefined) {
nicholas@2498 1623 postSilenceTime = 0;
nicholas@2498 1624 }
nicholas@2498 1625 var preSilenceSamples = secondsToSamples(preSilenceTime, this.buffer.sampleRate);
nicholas@2498 1626 var postSilenceSamples = secondsToSamples(postSilenceTime, this.buffer.sampleRate);
nicholas@2498 1627 var newLength = this.buffer.length + preSilenceSamples + postSilenceSamples;
nicholas@2460 1628 var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
nicholas@2224 1629 // Now we can use some efficient background copy schemes if we are just padding the end
nicholas@2460 1630 if (preSilenceSamples == 0 && typeof copybuffer.copyToChannel == "function") {
nicholas@2498 1631 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
nicholas@2498 1632 copybuffer.copyToChannel(this.buffer.getChannelData(c), c);
nicholas@2224 1633 }
nicholas@2224 1634 } else {
nicholas@2498 1635 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
nicholas@2224 1636 var src = this.buffer.getChannelData(c);
nicholas@2460 1637 var dst = copybuffer.getChannelData(c);
nicholas@2498 1638 for (var n = 0; n < src.length; n++)
nicholas@2498 1639 dst[n + preSilenceSamples] = src[n];
nicholas@2224 1640 }
nicholas@2224 1641 }
nicholas@2224 1642 // Copy in the rest of the buffer information
nicholas@2460 1643 copybuffer.lufs = this.buffer.lufs;
nicholas@2460 1644 copybuffer.playbackGain = this.buffer.playbackGain;
nicholas@2460 1645 return copybuffer;
nicholas@2460 1646 }
nicholas@2498 1647
nicholas@2498 1648 this.cropBuffer = function (startTime, stopTime) {
nicholas@2460 1649 // Copy and return the cropped buffer
nicholas@2498 1650 var start_sample = Math.floor(startTime * this.buffer.sampleRate);
nicholas@2498 1651 var stop_sample = Math.floor(stopTime * this.buffer.sampleRate);
nicholas@2460 1652 var newLength = stop_sample - start_sample;
nicholas@2460 1653 var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
nicholas@2460 1654 // Now we can use some efficient background copy schemes if we are just padding the end
nicholas@2498 1655 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
nicholas@2460 1656 var buffer = this.buffer.getChannelData(c);
nicholas@2498 1657 var sub_frame = buffer.subarray(start_sample, stop_sample);
nicholas@2460 1658 if (typeof copybuffer.copyToChannel == "function") {
nicholas@2498 1659 copybuffer.copyToChannel(sub_frame, c);
nicholas@2460 1660 } else {
nicholas@2460 1661 var dst = copybuffer.getChannelData(c);
nicholas@2498 1662 for (var n = 0; n < newLength; n++)
nicholas@2505 1663 dst[n] = buffer[n + start_sample];
nicholas@2460 1664 }
nicholas@2460 1665 }
nicholas@2460 1666 return copybuffer;
nicholas@2224 1667 }
nicholas@2498 1668 };
nicholas@2498 1669
nicholas@2498 1670 this.loadPageData = function (page) {
nicholas@2224 1671 // Load the URL from pages
nicholas@2224 1672 for (var element of page.audioElements) {
nicholas@2224 1673 var URL = page.hostURL + element.url;
nicholas@2224 1674 var buffer = null;
nicholas@2224 1675 for (var buffObj of this.buffers) {
nicholas@2224 1676 if (URL == buffObj.url) {
nicholas@2224 1677 buffer = buffObj;
nicholas@2224 1678 break;
nicholas@2224 1679 }
nicholas@2224 1680 }
nicholas@2224 1681 if (buffer == null) {
nicholas@2224 1682 buffer = new this.bufferObj();
nicholas@2615 1683 var urls = [URL];
nicholas@2615 1684 element.alternatives.forEach(function (e) {
nicholas@2615 1685 urls.push(e.url);
nicholas@2615 1686 });
nicholas@2615 1687 buffer.getMedia(urls);
nicholas@2224 1688 this.buffers.push(buffer);
nicholas@2224 1689 }
nicholas@2224 1690 }
nicholas@2224 1691 };
nicholas@2498 1692
nicholas@2498 1693 this.play = function (id) {
nicholas@2498 1694 // Start the timer and set the audioEngine state to playing (1)
nicholas@2498 1695 if (this.status == 0) {
nicholas@2498 1696 // Check if all audioObjects are ready
nicholas@2498 1697 this.bufferReady(id);
nicholas@2498 1698 } else {
nicholas@2498 1699 this.status = 1;
nicholas@2498 1700 }
nicholas@2498 1701 if (this.status == 1) {
nicholas@2498 1702 this.timer.startTest();
nicholas@2498 1703 if (id == undefined) {
nicholas@2498 1704 id = -1;
nicholas@2498 1705 console.error('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
nicholas@2498 1706 return;
nicholas@2498 1707 } else {
nicholas@2498 1708 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
nicholas@2498 1709 }
nicholas@2528 1710 var setTime = audioContext.currentTime;
nicholas@2498 1711 if (this.synchPlayback && this.loopPlayback) {
nicholas@2351 1712 // Traditional looped playback
nicholas@2498 1713 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1714 this.audioObjects[i].play(audioContext.currentTime);
nicholas@2498 1715 if (id == i) {
nicholas@2498 1716 this.audioObjects[i].loopStart(setTime);
nicholas@2498 1717 } else {
nicholas@2528 1718 this.audioObjects[i].loopStop(setTime + specification.crossFade);
nicholas@2498 1719 }
nicholas@2498 1720 }
nicholas@2351 1721 } else {
nicholas@2498 1722 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1723 if (i != id) {
nicholas@2528 1724 this.audioObjects[i].stop(setTime + specification.crossFade);
nicholas@2498 1725 } else if (i == id) {
nicholas@2498 1726 this.audioObjects[id].play(setTime);
nicholas@2498 1727 }
nicholas@2498 1728 }
nicholas@2498 1729 }
nicholas@2498 1730 interfaceContext.playhead.start();
nicholas@2498 1731 }
nicholas@2498 1732 };
nicholas@2224 1733
nicholas@2498 1734 this.stop = function () {
nicholas@2498 1735 // Send stop and reset command to all playback buffers
nicholas@2498 1736 if (this.status == 1) {
nicholas@2498 1737 var setTime = audioContext.currentTime + 0.1;
nicholas@2498 1738 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1739 this.audioObjects[i].stop(setTime);
nicholas@2498 1740 }
nicholas@2498 1741 interfaceContext.playhead.stop();
nicholas@2498 1742 }
nicholas@2498 1743 };
nicholas@2498 1744
nicholas@2498 1745 this.newTrack = function (element) {
nicholas@2498 1746 // Pull data from given URL into new audio buffer
nicholas@2498 1747 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
nicholas@2498 1748
nicholas@2498 1749 // Create the audioObject with ID of the new track length;
nicholas@2498 1750 audioObjectId = this.audioObjects.length;
nicholas@2498 1751 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
nicholas@2498 1752
nicholas@2498 1753 // Check if audioObject buffer is currently stored by full URL
nicholas@2498 1754 var URL = testState.currentStateMap.hostURL + element.url;
nicholas@2498 1755 var buffer = null;
nicholas@2498 1756 for (var i = 0; i < this.buffers.length; i++) {
nicholas@2615 1757 if (this.buffers[i].urls.includes(URL)) {
nicholas@2498 1758 buffer = this.buffers[i];
nicholas@2498 1759 break;
nicholas@2498 1760 }
nicholas@2498 1761 }
nicholas@2498 1762 if (buffer == null) {
nicholas@2498 1763 console.log("[WARN]: Buffer was not loaded in pre-test! " + URL);
nicholas@2498 1764 buffer = new this.bufferObj();
nicholas@2224 1765 this.buffers.push(buffer);
nicholas@2498 1766 buffer.getMedia(URL);
nicholas@2498 1767 }
nicholas@2498 1768 this.audioObjects[audioObjectId].specification = element;
nicholas@2498 1769 this.audioObjects[audioObjectId].url = URL;
nicholas@2498 1770 // Obtain store node
nicholas@2498 1771 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
nicholas@2498 1772 for (var i = 0; i < aeNodes.length; i++) {
nicholas@2498 1773 if (aeNodes[i].getAttribute("ref") == element.id) {
nicholas@2498 1774 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
nicholas@2498 1775 break;
nicholas@2498 1776 }
nicholas@2498 1777 }
nicholas@2224 1778 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
nicholas@2498 1779 return this.audioObjects[audioObjectId];
nicholas@2498 1780 };
nicholas@2498 1781
nicholas@2498 1782 this.newTestPage = function (audioHolderObject, store) {
nicholas@2498 1783 this.pageStore = store;
nicholas@2351 1784 this.pageSpecification = audioHolderObject;
nicholas@2498 1785 this.status = 0;
nicholas@2498 1786 this.audioObjectsReady = false;
nicholas@2498 1787 this.metric.reset();
nicholas@2498 1788 for (var i = 0; i < this.buffers.length; i++) {
nicholas@2498 1789 this.buffers[i].users = [];
nicholas@2498 1790 }
nicholas@2498 1791 this.audioObjects = [];
nicholas@2224 1792 this.timer = new timer();
nicholas@2224 1793 this.loopPlayback = audioHolderObject.loop;
nicholas@2351 1794 this.synchPlayback = audioHolderObject.synchronous;
nicholas@2498 1795 };
nicholas@2498 1796
nicholas@2498 1797 this.checkAllPlayed = function () {
nicholas@2498 1798 arr = [];
nicholas@2498 1799 for (var id = 0; id < this.audioObjects.length; id++) {
nicholas@2498 1800 if (this.audioObjects[id].metric.wasListenedTo == false) {
nicholas@2498 1801 arr.push(this.audioObjects[id].id);
nicholas@2498 1802 }
nicholas@2498 1803 }
nicholas@2498 1804 return arr;
nicholas@2498 1805 };
nicholas@2498 1806
nicholas@2498 1807 this.checkAllReady = function () {
nicholas@2498 1808 var ready = true;
nicholas@2498 1809 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1810 if (this.audioObjects[i].state == 0) {
nicholas@2498 1811 // Track not ready
nicholas@2498 1812 console.log('WAIT -- audioObject ' + i + ' not ready yet!');
nicholas@2498 1813 ready = false;
nicholas@2498 1814 };
nicholas@2498 1815 }
nicholas@2498 1816 return ready;
nicholas@2498 1817 };
nicholas@2498 1818
nicholas@2498 1819 this.setSynchronousLoop = function () {
nicholas@2570 1820 // Pads the signals so they are all exactly the same duration
nicholas@2570 1821 // Get the duration of the longest signal.
nicholas@2570 1822 var duration = 0;
nicholas@2498 1823 var maxId;
nicholas@2498 1824 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2570 1825 if (duration < this.audioObjects[i].buffer.buffer.duration) {
nicholas@2570 1826 duration = this.audioObjects[i].buffer.buffer.duration;
nicholas@2498 1827 maxId = i;
nicholas@2498 1828 }
nicholas@2498 1829 }
nicholas@2498 1830 // Extract the audio and zero-pad
nicholas@2498 1831 for (var ao of this.audioObjects) {
nicholas@2570 1832 if (ao.buffer.buffer.duration !== duration) {
nicholas@2570 1833 ao.buffer.buffer = ao.buffer.copyBuffer(0, duration - ao.buffer.buffer.duration);
nicholas@2500 1834 }
nicholas@2498 1835 }
nicholas@2498 1836 };
nicholas@2498 1837
nicholas@2498 1838 this.bufferReady = function (id) {
nicholas@2498 1839 if (this.checkAllReady()) {
nicholas@2498 1840 if (this.synchPlayback) {
nicholas@2498 1841 this.setSynchronousLoop();
nicholas@2498 1842 }
nicholas@2460 1843 this.status = 1;
nicholas@2460 1844 return true;
nicholas@2460 1845 }
nicholas@2460 1846 return false;
nicholas@2460 1847 }
nicholas@2498 1848
nicholas@2498 1849 this.exportXML = function () {
nicholas@2498 1850
nicholas@2224 1851 };
nicholas@2498 1852
nicholas@2224 1853 }
nicholas@2224 1854
nicholas@2224 1855 function audioObject(id) {
nicholas@2498 1856 // The main buffer object with common control nodes to the AudioEngine
nicholas@2498 1857
nicholas@2498 1858 this.specification;
nicholas@2498 1859 this.id = id;
nicholas@2498 1860 this.state = 0; // 0 - no data, 1 - ready
nicholas@2498 1861 this.url = null; // Hold the URL given for the output back to the results.
nicholas@2498 1862 this.metric = new metricTracker(this);
nicholas@2498 1863 this.storeDOM = null;
nicholas@2498 1864
nicholas@2498 1865 // Bindings for GUI
nicholas@2498 1866 this.interfaceDOM = null;
nicholas@2498 1867 this.commentDOM = null;
nicholas@2498 1868
nicholas@2498 1869 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
nicholas@2498 1870 this.bufferNode = undefined;
nicholas@2498 1871 this.outputGain = audioContext.createGain();
nicholas@2498 1872
nicholas@2498 1873 this.onplayGain = 1.0;
nicholas@2498 1874
nicholas@2498 1875 // Connect buffer to the audio graph
nicholas@2498 1876 this.outputGain.connect(audioEngineContext.outputGain);
nicholas@2508 1877 audioEngineContext.nullBufferSource.connect(this.outputGain);
nicholas@2498 1878
nicholas@2498 1879 // the audiobuffer is not designed for multi-start playback
nicholas@2498 1880 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
nicholas@2498 1881 this.buffer;
nicholas@2498 1882
nicholas@2498 1883 this.bufferLoaded = function (callee) {
nicholas@2498 1884 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
nicholas@2498 1885 // audioObject and trigger the interfaceDOM.enable() function for user feedback
nicholas@2224 1886 if (callee.status == -1) {
nicholas@2224 1887 // ERROR
nicholas@2224 1888 this.state = -1;
nicholas@2498 1889 if (this.interfaceDOM != null) {
nicholas@2498 1890 this.interfaceDOM.error();
nicholas@2498 1891 }
nicholas@2224 1892 this.buffer = callee;
nicholas@2224 1893 return;
nicholas@2224 1894 }
nicholas@2224 1895 this.buffer = callee;
nicholas@2224 1896 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
nicholas@2224 1897 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
nicholas@2460 1898 var startTime = this.specification.startTime;
nicholas@2460 1899 var stopTime = this.specification.stopTime;
nicholas@2460 1900 var copybuffer = new callee.constructor();
nicholas@2500 1901
nicholas@2500 1902 copybuffer.buffer = callee.cropBuffer(startTime || 0, stopTime || callee.buffer.duration);
nicholas@2500 1903 if (preSilenceTime != 0 || postSilenceTime != 0) {
nicholas@2500 1904 copybuffer.buffer = copybuffer.copyBuffer(preSilenceTime, postSilenceTime);
nicholas@2460 1905 }
nicholas@2500 1906
nicholas@2500 1907 copybuffer.lufs = callee.buffer.lufs;
nicholas@2500 1908 this.buffer = copybuffer;
nicholas@2498 1909
nicholas@2498 1910 var targetLUFS = this.specification.parent.loudness || specification.loudness;
nicholas@2498 1911 if (typeof targetLUFS === "number" && isFinite(targetLUFS)) {
nicholas@2498 1912 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
nicholas@2498 1913 } else {
nicholas@2498 1914 this.buffer.buffer.playbackGain = 1.0;
nicholas@2498 1915 }
nicholas@2498 1916 if (this.interfaceDOM != null) {
nicholas@2498 1917 this.interfaceDOM.enable();
nicholas@2498 1918 }
nicholas@2498 1919 this.onplayGain = decibelToLinear(this.specification.gain) * (this.buffer.buffer.playbackGain || 1.0);
nicholas@2498 1920 this.storeDOM.setAttribute('playGain', linearToDecibel(this.onplayGain));
nicholas@2460 1921 this.state = 1;
nicholas@2460 1922 audioEngineContext.bufferReady(id);
nicholas@2498 1923 };
nicholas@2498 1924
nicholas@2498 1925 this.bindInterface = function (interfaceObject) {
nicholas@2498 1926 this.interfaceDOM = interfaceObject;
nicholas@2498 1927 this.metric.initialise(interfaceObject.getValue());
nicholas@2498 1928 if (this.state == 1) {
nicholas@2498 1929 this.interfaceDOM.enable();
nicholas@2498 1930 } else if (this.state == -1) {
nicholas@2224 1931 // ERROR
nicholas@2224 1932 this.interfaceDOM.error();
nicholas@2224 1933 return;
nicholas@2224 1934 }
nicholas@2498 1935 this.storeDOM.setAttribute('presentedId', interfaceObject.getPresentedId());
nicholas@2498 1936 };
nicholas@2498 1937
nicholas@2498 1938 this.loopStart = function (setTime) {
nicholas@2498 1939 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain, setTime);
nicholas@2498 1940 this.metric.startListening(audioEngineContext.timer.getTestTime());
nicholas@2224 1941 this.interfaceDOM.startPlayback();
nicholas@2498 1942 };
nicholas@2498 1943
nicholas@2498 1944 this.loopStop = function (setTime) {
nicholas@2498 1945 if (this.outputGain.gain.value != 0.0) {
nicholas@2498 1946 this.outputGain.gain.linearRampToValueAtTime(0.0, setTime);
nicholas@2498 1947 this.metric.stopListening(audioEngineContext.timer.getTestTime());
nicholas@2498 1948 }
nicholas@2224 1949 this.interfaceDOM.stopPlayback();
nicholas@2498 1950 };
nicholas@2498 1951
nicholas@2498 1952 this.play = function (startTime) {
nicholas@2498 1953 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
nicholas@2498 1954 this.bufferNode = audioContext.createBufferSource();
nicholas@2498 1955 this.bufferNode.owner = this;
nicholas@2498 1956 this.bufferNode.connect(this.outputGain);
nicholas@2498 1957 this.bufferNode.buffer = this.buffer.buffer;
nicholas@2498 1958 this.bufferNode.loop = audioEngineContext.loopPlayback;
nicholas@2498 1959 this.bufferNode.onended = function (event) {
nicholas@2498 1960 // Safari does not like using 'this' to reference the calling object!
nicholas@2498 1961 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
nicholas@2224 1962 if (event.currentTarget != null) {
nicholas@2498 1963 event.currentTarget.owner.stop(audioContext.currentTime + 1);
nicholas@2224 1964 }
nicholas@2498 1965 };
nicholas@2508 1966 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
nicholas@2498 1967 if (!audioEngineContext.loopPlayback || !audioEngineContext.synchPlayback) {
nicholas@2498 1968 this.metric.startListening(audioEngineContext.timer.getTestTime());
nicholas@2529 1969 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain, startTime + specification.crossFade);
nicholas@2224 1970 this.interfaceDOM.startPlayback();
nicholas@2498 1971 } else {
nicholas@2529 1972 this.outputGain.gain.linearRampToValueAtTime(0.0, startTime);
nicholas@2224 1973 }
nicholas@2499 1974 if (audioEngineContext.loopPlayback) {
nicholas@2499 1975 this.bufferNode.loopStart = this.specification.startTime || 0;
nicholas@2499 1976 this.bufferNode.loopEnd = this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration;
nicholas@2499 1977 this.bufferNode.start(startTime);
nicholas@2499 1978 } else {
nicholas@2499 1979 this.bufferNode.start(startTime, this.specification.startTime || 0, this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration);
nicholas@2499 1980 }
nicholas@2224 1981 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
nicholas@2498 1982 }
nicholas@2498 1983 };
nicholas@2498 1984
nicholas@2498 1985 this.stop = function (stopTime) {
nicholas@2224 1986 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
nicholas@2498 1987 if (this.bufferNode != undefined) {
nicholas@2498 1988 this.metric.stopListening(audioEngineContext.timer.getTestTime(), this.getCurrentPosition());
nicholas@2498 1989 this.bufferNode.stop(stopTime);
nicholas@2498 1990 this.bufferNode = undefined;
nicholas@2498 1991 }
nicholas@2529 1992 this.outputGain.gain.linearRampToValueAtTime(0.0, stopTime);
nicholas@2224 1993 this.interfaceDOM.stopPlayback();
nicholas@2498 1994 };
nicholas@2498 1995
nicholas@2498 1996 this.getCurrentPosition = function () {
nicholas@2498 1997 var time = audioEngineContext.timer.getTestTime();
nicholas@2498 1998 if (this.bufferNode != undefined) {
nicholas@2498 1999 var position = (time - this.bufferNode.playbackStartTime) % this.buffer.buffer.duration;
nicholas@2498 2000 if (isNaN(position)) {
nicholas@2498 2001 return 0;
nicholas@2498 2002 }
nicholas@2224 2003 return position;
nicholas@2498 2004 } else {
nicholas@2498 2005 return 0;
nicholas@2498 2006 }
nicholas@2498 2007 };
nicholas@2498 2008
nicholas@2498 2009 this.exportXMLDOM = function () {
nicholas@2498 2010 var file = storage.document.createElement('file');
nicholas@2498 2011 file.setAttribute('sampleRate', this.buffer.buffer.sampleRate);
nicholas@2498 2012 file.setAttribute('channels', this.buffer.buffer.numberOfChannels);
nicholas@2498 2013 file.setAttribute('sampleCount', this.buffer.buffer.length);
nicholas@2498 2014 file.setAttribute('duration', this.buffer.buffer.duration);
nicholas@2498 2015 this.storeDOM.appendChild(file);
nicholas@2498 2016 if (this.specification.type != 'outside-reference') {
nicholas@2498 2017 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
nicholas@2498 2018 if (interfaceXML != null) {
nicholas@2498 2019 if (interfaceXML.length == undefined) {
nicholas@2498 2020 this.storeDOM.appendChild(interfaceXML);
nicholas@2498 2021 } else {
nicholas@2498 2022 for (var i = 0; i < interfaceXML.length; i++) {
nicholas@2498 2023 this.storeDOM.appendChild(interfaceXML[i]);
nicholas@2498 2024 }
nicholas@2498 2025 }
nicholas@2498 2026 }
nicholas@2498 2027 if (this.commentDOM != null) {
nicholas@2498 2028 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
nicholas@2498 2029 }
nicholas@2498 2030 }
nicholas@2498 2031 var nodes = this.metric.exportXMLDOM();
nicholas@2498 2032 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
nicholas@2498 2033 for (var i = 0; i < nodes.length; i++) {
nicholas@2498 2034 mroot.appendChild(nodes[i]);
nicholas@2498 2035 }
nicholas@2498 2036 };
nicholas@2224 2037 }
nicholas@2224 2038
nicholas@2498 2039 function timer() {
nicholas@2498 2040 /* Timer object used in audioEngine to keep track of session timings
nicholas@2498 2041 * Uses the timer of the web audio API, so sample resolution
nicholas@2498 2042 */
nicholas@2498 2043 this.testStarted = false;
nicholas@2498 2044 this.testStartTime = 0;
nicholas@2498 2045 this.testDuration = 0;
nicholas@2498 2046 this.minimumTestTime = 0; // No minimum test time
nicholas@2498 2047 this.startTest = function () {
nicholas@2498 2048 if (this.testStarted == false) {
nicholas@2498 2049 this.testStartTime = audioContext.currentTime;
nicholas@2498 2050 this.testStarted = true;
nicholas@2498 2051 this.updateTestTime();
nicholas@2498 2052 audioEngineContext.metric.initialiseTest();
nicholas@2498 2053 }
nicholas@2498 2054 };
nicholas@2498 2055 this.stopTest = function () {
nicholas@2498 2056 if (this.testStarted) {
nicholas@2498 2057 this.testDuration = this.getTestTime();
nicholas@2498 2058 this.testStarted = false;
nicholas@2498 2059 } else {
nicholas@2498 2060 console.log('ERR: Test tried to end before beginning');
nicholas@2498 2061 }
nicholas@2498 2062 };
nicholas@2498 2063 this.updateTestTime = function () {
nicholas@2498 2064 if (this.testStarted) {
nicholas@2498 2065 this.testDuration = audioContext.currentTime - this.testStartTime;
nicholas@2498 2066 }
nicholas@2498 2067 };
nicholas@2498 2068 this.getTestTime = function () {
nicholas@2498 2069 this.updateTestTime();
nicholas@2498 2070 return this.testDuration;
nicholas@2498 2071 };
nicholas@2224 2072 }
nicholas@2224 2073
nicholas@2498 2074 function sessionMetrics(engine, specification) {
nicholas@2498 2075 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
nicholas@2498 2076 */
nicholas@2498 2077 this.engine = engine;
nicholas@2498 2078 this.lastClicked = -1;
nicholas@2498 2079 this.data = -1;
nicholas@2498 2080 this.reset = function () {
nicholas@2498 2081 this.lastClicked = -1;
nicholas@2498 2082 this.data = -1;
nicholas@2498 2083 };
nicholas@2498 2084
nicholas@2498 2085 this.enableElementInitialPosition = false;
nicholas@2498 2086 this.enableElementListenTracker = false;
nicholas@2498 2087 this.enableElementTimer = false;
nicholas@2498 2088 this.enableElementTracker = false;
nicholas@2498 2089 this.enableFlagListenedTo = false;
nicholas@2498 2090 this.enableFlagMoved = false;
nicholas@2498 2091 this.enableTestTimer = false;
nicholas@2498 2092 // Obtain the metrics enabled
nicholas@2498 2093 for (var i = 0; i < specification.metrics.enabled.length; i++) {
nicholas@2498 2094 var node = specification.metrics.enabled[i];
nicholas@2498 2095 switch (node) {
nicholas@2498 2096 case 'testTimer':
nicholas@2498 2097 this.enableTestTimer = true;
nicholas@2498 2098 break;
nicholas@2498 2099 case 'elementTimer':
nicholas@2498 2100 this.enableElementTimer = true;
nicholas@2498 2101 break;
nicholas@2498 2102 case 'elementTracker':
nicholas@2498 2103 this.enableElementTracker = true;
nicholas@2498 2104 break;
nicholas@2498 2105 case 'elementListenTracker':
nicholas@2498 2106 this.enableElementListenTracker = true;
nicholas@2498 2107 break;
nicholas@2498 2108 case 'elementInitialPosition':
nicholas@2498 2109 this.enableElementInitialPosition = true;
nicholas@2498 2110 break;
nicholas@2498 2111 case 'elementFlagListenedTo':
nicholas@2498 2112 this.enableFlagListenedTo = true;
nicholas@2498 2113 break;
nicholas@2498 2114 case 'elementFlagMoved':
nicholas@2498 2115 this.enableFlagMoved = true;
nicholas@2498 2116 break;
nicholas@2498 2117 case 'elementFlagComments':
nicholas@2498 2118 this.enableFlagComments = true;
nicholas@2498 2119 break;
nicholas@2498 2120 }
nicholas@2498 2121 }
nicholas@2498 2122 this.initialiseTest = function () {};
nicholas@2224 2123 }
nicholas@2224 2124
nicholas@2498 2125 function metricTracker(caller) {
nicholas@2498 2126 /* Custom object to track and collect metric data
nicholas@2498 2127 * Used only inside the audioObjects object.
nicholas@2498 2128 */
nicholas@2498 2129
nicholas@2498 2130 this.listenedTimer = 0;
nicholas@2498 2131 this.listenStart = 0;
nicholas@2498 2132 this.listenHold = false;
nicholas@2498 2133 this.initialPosition = -1;
nicholas@2498 2134 this.movementTracker = [];
nicholas@2498 2135 this.listenTracker = [];
nicholas@2498 2136 this.wasListenedTo = false;
nicholas@2498 2137 this.wasMoved = false;
nicholas@2498 2138 this.hasComments = false;
nicholas@2498 2139 this.parent = caller;
nicholas@2498 2140
nicholas@2498 2141 this.initialise = function (position) {
nicholas@2498 2142 if (this.initialPosition == -1) {
nicholas@2498 2143 this.initialPosition = position;
nicholas@2498 2144 this.moved(0, position);
nicholas@2498 2145 }
nicholas@2498 2146 };
nicholas@2498 2147
nicholas@2498 2148 this.moved = function (time, position) {
nicholas@2498 2149 if (time > 0) {
nicholas@2498 2150 this.wasMoved = true;
nicholas@2498 2151 }
nicholas@2498 2152 this.movementTracker[this.movementTracker.length] = [time, position];
nicholas@2498 2153 };
nicholas@2498 2154
nicholas@2498 2155 this.startListening = function (time) {
nicholas@2498 2156 if (this.listenHold == false) {
nicholas@2498 2157 this.wasListenedTo = true;
nicholas@2498 2158 this.listenStart = time;
nicholas@2498 2159 this.listenHold = true;
nicholas@2498 2160
nicholas@2498 2161 var evnt = document.createElement('event');
nicholas@2498 2162 var testTime = document.createElement('testTime');
nicholas@2498 2163 testTime.setAttribute('start', time);
nicholas@2498 2164 var bufferTime = document.createElement('bufferTime');
nicholas@2498 2165 bufferTime.setAttribute('start', this.parent.getCurrentPosition());
nicholas@2498 2166 evnt.appendChild(testTime);
nicholas@2498 2167 evnt.appendChild(bufferTime);
nicholas@2498 2168 this.listenTracker.push(evnt);
nicholas@2498 2169
nicholas@2498 2170 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
nicholas@2498 2171 }
nicholas@2498 2172 };
nicholas@2498 2173
nicholas@2498 2174 this.stopListening = function (time, bufferStopTime) {
nicholas@2498 2175 if (this.listenHold == true) {
nicholas@2498 2176 var diff = time - this.listenStart;
nicholas@2498 2177 this.listenedTimer += (diff);
nicholas@2498 2178 this.listenStart = 0;
nicholas@2498 2179 this.listenHold = false;
nicholas@2498 2180
nicholas@2498 2181 var evnt = this.listenTracker[this.listenTracker.length - 1];
nicholas@2498 2182 var testTime = evnt.getElementsByTagName('testTime')[0];
nicholas@2498 2183 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
nicholas@2498 2184 testTime.setAttribute('stop', time);
nicholas@2498 2185 if (bufferStopTime == undefined) {
nicholas@2498 2186 bufferTime.setAttribute('stop', this.parent.getCurrentPosition());
nicholas@2498 2187 } else {
nicholas@2498 2188 bufferTime.setAttribute('stop', bufferStopTime);
nicholas@2498 2189 }
nicholas@2498 2190 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
nicholas@2498 2191 }
nicholas@2498 2192 };
nicholas@2498 2193
nicholas@2498 2194 this.exportXMLDOM = function () {
nicholas@2498 2195 var storeDOM = [];
nicholas@2498 2196 if (audioEngineContext.metric.enableElementTimer) {
nicholas@2498 2197 var mElementTimer = storage.document.createElement('metricresult');
nicholas@2498 2198 mElementTimer.setAttribute('name', 'enableElementTimer');
nicholas@2498 2199 mElementTimer.textContent = this.listenedTimer;
nicholas@2498 2200 storeDOM.push(mElementTimer);
nicholas@2498 2201 }
nicholas@2498 2202 if (audioEngineContext.metric.enableElementTracker) {
nicholas@2498 2203 var elementTrackerFull = storage.document.createElement('metricresult');
nicholas@2498 2204 elementTrackerFull.setAttribute('name', 'elementTrackerFull');
nicholas@2498 2205 for (var k = 0; k < this.movementTracker.length; k++) {
nicholas@2498 2206 var timePos = storage.document.createElement('movement');
nicholas@2498 2207 timePos.setAttribute("time", this.movementTracker[k][0]);
nicholas@2498 2208 timePos.setAttribute("value", this.movementTracker[k][1]);
nicholas@2498 2209 elementTrackerFull.appendChild(timePos);
nicholas@2498 2210 }
nicholas@2498 2211 storeDOM.push(elementTrackerFull);
nicholas@2498 2212 }
nicholas@2498 2213 if (audioEngineContext.metric.enableElementListenTracker) {
nicholas@2498 2214 var elementListenTracker = storage.document.createElement('metricresult');
nicholas@2498 2215 elementListenTracker.setAttribute('name', 'elementListenTracker');
nicholas@2498 2216 for (var k = 0; k < this.listenTracker.length; k++) {
nicholas@2498 2217 elementListenTracker.appendChild(this.listenTracker[k]);
nicholas@2498 2218 }
nicholas@2498 2219 storeDOM.push(elementListenTracker);
nicholas@2498 2220 }
nicholas@2498 2221 if (audioEngineContext.metric.enableElementInitialPosition) {
nicholas@2498 2222 var elementInitial = storage.document.createElement('metricresult');
nicholas@2498 2223 elementInitial.setAttribute('name', 'elementInitialPosition');
nicholas@2498 2224 elementInitial.textContent = this.initialPosition;
nicholas@2498 2225 storeDOM.push(elementInitial);
nicholas@2498 2226 }
nicholas@2498 2227 if (audioEngineContext.metric.enableFlagListenedTo) {
nicholas@2498 2228 var flagListenedTo = storage.document.createElement('metricresult');
nicholas@2498 2229 flagListenedTo.setAttribute('name', 'elementFlagListenedTo');
nicholas@2498 2230 flagListenedTo.textContent = this.wasListenedTo;
nicholas@2498 2231 storeDOM.push(flagListenedTo);
nicholas@2498 2232 }
nicholas@2498 2233 if (audioEngineContext.metric.enableFlagMoved) {
nicholas@2498 2234 var flagMoved = storage.document.createElement('metricresult');
nicholas@2498 2235 flagMoved.setAttribute('name', 'elementFlagMoved');
nicholas@2498 2236 flagMoved.textContent = this.wasMoved;
nicholas@2498 2237 storeDOM.push(flagMoved);
nicholas@2498 2238 }
nicholas@2498 2239 if (audioEngineContext.metric.enableFlagComments) {
nicholas@2498 2240 var flagComments = storage.document.createElement('metricresult');
nicholas@2498 2241 flagComments.setAttribute('name', 'elementFlagComments');
nicholas@2498 2242 if (this.parent.commentDOM == null) {
nicholas@2498 2243 flag.textContent = 'false';
nicholas@2498 2244 } else if (this.parent.commentDOM.textContent.length == 0) {
nicholas@2498 2245 flag.textContent = 'false';
nicholas@2498 2246 } else {
nicholas@2498 2247 flag.textContet = 'true';
nicholas@2498 2248 }
nicholas@2498 2249 storeDOM.push(flagComments);
nicholas@2498 2250 }
nicholas@2498 2251 return storeDOM;
nicholas@2498 2252 };
nicholas@2224 2253 }
nicholas@2498 2254
nicholas@2224 2255 function Interface(specificationObject) {
nicholas@2498 2256 // This handles the bindings between the interface and the audioEngineContext;
nicholas@2498 2257 this.specification = specificationObject;
nicholas@2498 2258 this.insertPoint = document.getElementById("topLevelBody");
nicholas@2498 2259
nicholas@2498 2260 this.newPage = function (audioHolderObject, store) {
nicholas@2498 2261 audioEngineContext.newTestPage(audioHolderObject, store);
nicholas@2498 2262 interfaceContext.commentBoxes.deleteCommentBoxes();
nicholas@2498 2263 interfaceContext.deleteCommentQuestions();
nicholas@2498 2264 loadTest(audioHolderObject, store);
nicholas@2498 2265 };
nicholas@2498 2266
nicholas@2498 2267 // Bounded by interface!!
nicholas@2498 2268 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
nicholas@2498 2269 // For example, APE returns the slider position normalised in a <value> tag.
nicholas@2498 2270 this.interfaceObjects = [];
nicholas@2498 2271 this.interfaceObject = function () {};
nicholas@2498 2272
nicholas@2498 2273 this.resizeWindow = function (event) {
nicholas@2498 2274 popup.resize(event);
nicholas@2352 2275 this.volume.resize();
nicholas@2360 2276 this.lightbox.resize();
nicholas@2498 2277 for (var i = 0; i < this.commentBoxes.length; i++) {
nicholas@2498 2278 this.commentBoxes[i].resize();
nicholas@2498 2279 }
nicholas@2498 2280 for (var i = 0; i < this.commentQuestions.length; i++) {
nicholas@2498 2281 this.commentQuestions[i].resize();
nicholas@2498 2282 }
nicholas@2498 2283 try {
nicholas@2498 2284 resizeWindow(event);
nicholas@2498 2285 } catch (err) {
nicholas@2498 2286 console.log("Warning - Interface does not have Resize option");
nicholas@2498 2287 console.log(err);
nicholas@2498 2288 }
nicholas@2498 2289 };
nicholas@2498 2290
nicholas@2498 2291 this.returnNavigator = function () {
nicholas@2498 2292 var node = storage.document.createElement("navigator");
nicholas@2498 2293 var platform = storage.document.createElement("platform");
nicholas@2498 2294 platform.textContent = navigator.platform;
nicholas@2498 2295 var vendor = storage.document.createElement("vendor");
nicholas@2498 2296 vendor.textContent = navigator.vendor;
nicholas@2498 2297 var userAgent = storage.document.createElement("uagent");
nicholas@2498 2298 userAgent.textContent = navigator.userAgent;
nicholas@2224 2299 var screen = storage.document.createElement("window");
nicholas@2498 2300 screen.setAttribute('innerWidth', window.innerWidth);
nicholas@2498 2301 screen.setAttribute('innerHeight', window.innerHeight);
nicholas@2498 2302 node.appendChild(platform);
nicholas@2498 2303 node.appendChild(vendor);
nicholas@2498 2304 node.appendChild(userAgent);
nicholas@2224 2305 node.appendChild(screen);
nicholas@2498 2306 return node;
nicholas@2498 2307 };
nicholas@2498 2308
nicholas@2498 2309 this.returnDateNode = function () {
nicholas@2224 2310 // Create an XML Node for the Date and Time a test was conducted
nicholas@2224 2311 // Structure is
nicholas@2224 2312 // <datetime>
nicholas@2224 2313 // <date year="##" month="##" day="##">DD/MM/YY</date>
nicholas@2224 2314 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
nicholas@2224 2315 // </datetime>
nicholas@2224 2316 var dateTime = new Date();
nicholas@2224 2317 var hold = storage.document.createElement("datetime");
nicholas@2224 2318 var date = storage.document.createElement("date");
nicholas@2224 2319 var time = storage.document.createElement("time");
nicholas@2498 2320 date.setAttribute('year', dateTime.getFullYear());
nicholas@2498 2321 date.setAttribute('month', dateTime.getMonth() + 1);
nicholas@2498 2322 date.setAttribute('day', dateTime.getDate());
nicholas@2498 2323 time.setAttribute('hour', dateTime.getHours());
nicholas@2498 2324 time.setAttribute('minute', dateTime.getMinutes());
nicholas@2498 2325 time.setAttribute('secs', dateTime.getSeconds());
nicholas@2498 2326
nicholas@2224 2327 hold.appendChild(date);
nicholas@2224 2328 hold.appendChild(time);
nicholas@2224 2329 return hold;
nicholas@2224 2330
nicholas@2224 2331 }
nicholas@2498 2332
nicholas@2360 2333 this.lightbox = {
nicholas@2360 2334 parent: this,
nicholas@2360 2335 root: document.createElement("div"),
nicholas@2360 2336 content: document.createElement("div"),
nicholas@2360 2337 accept: document.createElement("button"),
nicholas@2360 2338 blanker: document.createElement("div"),
nicholas@2498 2339 post: function (type, message) {
nicholas@2498 2340 switch (type) {
nicholas@2360 2341 case "Error":
nicholas@2360 2342 this.content.className = "lightbox-error";
nicholas@2360 2343 break;
nicholas@2360 2344 case "Warning":
nicholas@2360 2345 this.content.className = "lightbox-warning";
nicholas@2360 2346 break;
nicholas@2360 2347 default:
nicholas@2360 2348 this.content.className = "lightbox-message";
nicholas@2360 2349 break;
nicholas@2360 2350 }
nicholas@2360 2351 var msg = document.createElement("p");
nicholas@2360 2352 msg.textContent = message;
nicholas@2360 2353 this.content.appendChild(msg);
nicholas@2360 2354 this.show();
nicholas@2360 2355 },
nicholas@2498 2356 show: function () {
nicholas@2360 2357 this.root.style.visibility = "visible";
nicholas@2360 2358 this.blanker.style.visibility = "visible";
nicholas@2360 2359 },
nicholas@2498 2360 clear: function () {
nicholas@2360 2361 this.root.style.visibility = "";
nicholas@2360 2362 this.blanker.style.visibility = "";
nicholas@2360 2363 this.content.textContent = "";
nicholas@2360 2364 },
nicholas@2498 2365 handleEvent: function (event) {
nicholas@2360 2366 if (event.currentTarget == this.accept) {
nicholas@2360 2367 this.clear();
nicholas@2360 2368 }
nicholas@2360 2369 },
nicholas@2498 2370 resize: function (event) {
nicholas@2498 2371 this.root.style.left = (window.innerWidth / 2) - 250 + 'px';
nicholas@2360 2372 }
nicholas@2360 2373 }
nicholas@2498 2374
nicholas@2360 2375 this.lightbox.root.appendChild(this.lightbox.content);
nicholas@2360 2376 this.lightbox.root.appendChild(this.lightbox.accept);
nicholas@2360 2377 this.lightbox.root.className = "popupHolder";
nicholas@2360 2378 this.lightbox.root.id = "lightbox-root";
nicholas@2360 2379 this.lightbox.accept.className = "popupButton";
nicholas@2360 2380 this.lightbox.accept.style.bottom = "10px";
nicholas@2360 2381 this.lightbox.accept.textContent = "OK";
nicholas@2360 2382 this.lightbox.accept.style.left = "237.5px";
nicholas@2498 2383 this.lightbox.accept.addEventListener("click", this.lightbox);
nicholas@2360 2384 this.lightbox.blanker.className = "testHalt";
nicholas@2360 2385 this.lightbox.blanker.id = "lightbox-blanker";
nicholas@2360 2386 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
nicholas@2360 2387 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
nicholas@2498 2388
nicholas@2498 2389 this.commentBoxes = new function () {
nicholas@2224 2390 this.boxes = [];
nicholas@2224 2391 this.injectPoint = null;
nicholas@2498 2392 this.elementCommentBox = function (audioObject) {
nicholas@2224 2393 var element = audioObject.specification;
nicholas@2224 2394 this.audioObject = audioObject;
nicholas@2224 2395 this.id = audioObject.id;
nicholas@2224 2396 var audioHolderObject = audioObject.specification.parent;
nicholas@2224 2397 // Create document objects to hold the comment boxes
nicholas@2224 2398 this.trackComment = document.createElement('div');
nicholas@2224 2399 this.trackComment.className = 'comment-div';
nicholas@2498 2400 this.trackComment.id = 'comment-div-' + audioObject.id;
nicholas@2224 2401 // Create a string next to each comment asking for a comment
nicholas@2224 2402 this.trackString = document.createElement('span');
nicholas@2498 2403 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
nicholas@2224 2404 // Create the HTML5 comment box 'textarea'
nicholas@2224 2405 this.trackCommentBox = document.createElement('textarea');
nicholas@2224 2406 this.trackCommentBox.rows = '4';
nicholas@2224 2407 this.trackCommentBox.cols = '100';
nicholas@2498 2408 this.trackCommentBox.name = 'trackComment' + audioObject.id;
nicholas@2224 2409 this.trackCommentBox.className = 'trackComment';
nicholas@2224 2410 var br = document.createElement('br');
nicholas@2224 2411 // Add to the holder.
nicholas@2224 2412 this.trackComment.appendChild(this.trackString);
nicholas@2224 2413 this.trackComment.appendChild(br);
nicholas@2224 2414 this.trackComment.appendChild(this.trackCommentBox);
nicholas@2224 2415
nicholas@2498 2416 this.exportXMLDOM = function () {
nicholas@2224 2417 var root = document.createElement('comment');
nicholas@2224 2418 var question = document.createElement('question');
nicholas@2224 2419 question.textContent = this.trackString.textContent;
nicholas@2224 2420 var response = document.createElement('response');
nicholas@2224 2421 response.textContent = this.trackCommentBox.value;
nicholas@2498 2422 console.log("Comment frag-" + this.id + ": " + response.textContent);
nicholas@2224 2423 root.appendChild(question);
nicholas@2224 2424 root.appendChild(response);
nicholas@2224 2425 return root;
nicholas@2224 2426 };
nicholas@2498 2427 this.resize = function () {
nicholas@2498 2428 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2429 if (boxwidth >= 600) {
nicholas@2224 2430 boxwidth = 600;
nicholas@2498 2431 } else if (boxwidth < 400) {
nicholas@2224 2432 boxwidth = 400;
nicholas@2224 2433 }
nicholas@2498 2434 this.trackComment.style.width = boxwidth + "px";
nicholas@2498 2435 this.trackCommentBox.style.width = boxwidth - 6 + "px";
nicholas@2224 2436 };
nicholas@2224 2437 this.resize();
nicholas@2224 2438 };
nicholas@2498 2439 this.createCommentBox = function (audioObject) {
nicholas@2224 2440 var node = new this.elementCommentBox(audioObject);
nicholas@2224 2441 this.boxes.push(node);
nicholas@2224 2442 audioObject.commentDOM = node;
nicholas@2224 2443 return node;
nicholas@2224 2444 };
nicholas@2498 2445 this.sortCommentBoxes = function () {
nicholas@2498 2446 this.boxes.sort(function (a, b) {
nicholas@2498 2447 return a.id - b.id;
nicholas@2498 2448 });
nicholas@2224 2449 };
nicholas@2224 2450
nicholas@2498 2451 this.showCommentBoxes = function (inject, sort) {
nicholas@2224 2452 this.injectPoint = inject;
nicholas@2498 2453 if (sort) {
nicholas@2498 2454 this.sortCommentBoxes();
nicholas@2498 2455 }
nicholas@2224 2456 for (var box of this.boxes) {
nicholas@2224 2457 inject.appendChild(box.trackComment);
nicholas@2224 2458 }
nicholas@2224 2459 };
nicholas@2224 2460
nicholas@2498 2461 this.deleteCommentBoxes = function () {
nicholas@2224 2462 if (this.injectPoint != null) {
nicholas@2224 2463 for (var box of this.boxes) {
nicholas@2224 2464 this.injectPoint.removeChild(box.trackComment);
nicholas@2224 2465 }
nicholas@2224 2466 this.injectPoint = null;
nicholas@2224 2467 }
nicholas@2224 2468 this.boxes = [];
nicholas@2224 2469 };
nicholas@2224 2470 }
nicholas@2498 2471
nicholas@2498 2472 this.commentQuestions = [];
nicholas@2498 2473
nicholas@2498 2474 this.commentBox = function (commentQuestion) {
nicholas@2498 2475 this.specification = commentQuestion;
nicholas@2498 2476 // Create document objects to hold the comment boxes
nicholas@2498 2477 this.holder = document.createElement('div');
nicholas@2498 2478 this.holder.className = 'comment-div';
nicholas@2498 2479 // Create a string next to each comment asking for a comment
nicholas@2498 2480 this.string = document.createElement('span');
nicholas@2498 2481 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2482 // Create the HTML5 comment box 'textarea'
nicholas@2498 2483 this.textArea = document.createElement('textarea');
nicholas@2498 2484 this.textArea.rows = '4';
nicholas@2498 2485 this.textArea.cols = '100';
nicholas@2498 2486 this.textArea.className = 'trackComment';
nicholas@2498 2487 var br = document.createElement('br');
nicholas@2498 2488 // Add to the holder.
nicholas@2498 2489 this.holder.appendChild(this.string);
nicholas@2498 2490 this.holder.appendChild(br);
nicholas@2498 2491 this.holder.appendChild(this.textArea);
nicholas@2498 2492
nicholas@2498 2493 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2494 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2495 root.id = this.specification.id;
nicholas@2498 2496 root.setAttribute('type', this.specification.type);
nicholas@2498 2497 console.log("Question: " + this.string.textContent);
nicholas@2498 2498 console.log("Response: " + root.textContent);
nicholas@2224 2499 var question = storePoint.parent.document.createElement('question');
nicholas@2224 2500 question.textContent = this.string.textContent;
nicholas@2224 2501 var response = storePoint.parent.document.createElement('response');
nicholas@2224 2502 response.textContent = this.textArea.value;
nicholas@2224 2503 root.appendChild(question);
nicholas@2224 2504 root.appendChild(response);
nicholas@2224 2505 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2506 return root;
nicholas@2498 2507 };
nicholas@2498 2508 this.resize = function () {
nicholas@2498 2509 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2510 if (boxwidth >= 600) {
nicholas@2498 2511 boxwidth = 600;
nicholas@2498 2512 } else if (boxwidth < 400) {
nicholas@2498 2513 boxwidth = 400;
nicholas@2498 2514 }
nicholas@2498 2515 this.holder.style.width = boxwidth + "px";
nicholas@2498 2516 this.textArea.style.width = boxwidth - 6 + "px";
nicholas@2498 2517 };
nicholas@2498 2518 this.resize();
nicholas@2498 2519 };
nicholas@2498 2520
nicholas@2498 2521 this.radioBox = function (commentQuestion) {
nicholas@2498 2522 this.specification = commentQuestion;
nicholas@2498 2523 // Create document objects to hold the comment boxes
nicholas@2498 2524 this.holder = document.createElement('div');
nicholas@2498 2525 this.holder.className = 'comment-div';
nicholas@2498 2526 // Create a string next to each comment asking for a comment
nicholas@2498 2527 this.string = document.createElement('span');
nicholas@2498 2528 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2529 var br = document.createElement('br');
nicholas@2498 2530 // Add to the holder.
nicholas@2498 2531 this.holder.appendChild(this.string);
nicholas@2498 2532 this.holder.appendChild(br);
nicholas@2498 2533 this.options = [];
nicholas@2498 2534 this.inputs = document.createElement('div');
nicholas@2498 2535 this.span = document.createElement('div');
nicholas@2498 2536 this.inputs.align = 'center';
nicholas@2498 2537 this.inputs.style.marginLeft = '12px';
nicholas@2294 2538 this.inputs.className = "comment-radio-inputs-holder";
nicholas@2498 2539 this.span.style.marginLeft = '12px';
nicholas@2498 2540 this.span.align = 'center';
nicholas@2498 2541 this.span.style.marginTop = '15px';
nicholas@2294 2542 this.span.className = "comment-radio-span-holder";
nicholas@2498 2543
nicholas@2498 2544 var optCount = commentQuestion.options.length;
nicholas@2498 2545 for (var optNode of commentQuestion.options) {
nicholas@2498 2546 var div = document.createElement('div');
nicholas@2498 2547 div.style.width = '80px';
nicholas@2498 2548 div.style.float = 'left';
nicholas@2498 2549 var input = document.createElement('input');
nicholas@2498 2550 input.type = 'radio';
nicholas@2498 2551 input.name = commentQuestion.id;
nicholas@2498 2552 input.setAttribute('setvalue', optNode.name);
nicholas@2498 2553 input.className = 'comment-radio';
nicholas@2498 2554 div.appendChild(input);
nicholas@2498 2555 this.inputs.appendChild(div);
nicholas@2498 2556
nicholas@2498 2557
nicholas@2498 2558 div = document.createElement('div');
nicholas@2498 2559 div.style.width = '80px';
nicholas@2498 2560 div.style.float = 'left';
nicholas@2498 2561 div.align = 'center';
nicholas@2498 2562 var span = document.createElement('span');
nicholas@2498 2563 span.textContent = optNode.text;
nicholas@2498 2564 span.className = 'comment-radio-span';
nicholas@2498 2565 div.appendChild(span);
nicholas@2498 2566 this.span.appendChild(div);
nicholas@2498 2567 this.options.push(input);
nicholas@2498 2568 }
nicholas@2498 2569 this.holder.appendChild(this.span);
nicholas@2498 2570 this.holder.appendChild(this.inputs);
nicholas@2498 2571
nicholas@2498 2572 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2573 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2574 root.id = this.specification.id;
nicholas@2498 2575 root.setAttribute('type', this.specification.type);
nicholas@2498 2576 var question = document.createElement('question');
nicholas@2498 2577 question.textContent = this.string.textContent;
nicholas@2498 2578 var response = document.createElement('response');
nicholas@2498 2579 var i = 0;
nicholas@2498 2580 while (this.options[i].checked == false) {
nicholas@2498 2581 i++;
nicholas@2498 2582 if (i >= this.options.length) {
nicholas@2498 2583 break;
nicholas@2498 2584 }
nicholas@2498 2585 }
nicholas@2498 2586 if (i >= this.options.length) {
nicholas@2498 2587 response.textContent = 'null';
nicholas@2498 2588 } else {
nicholas@2498 2589 response.textContent = this.options[i].getAttribute('setvalue');
nicholas@2498 2590 response.setAttribute('number', i);
nicholas@2498 2591 }
nicholas@2498 2592 console.log('Comment: ' + question.textContent);
nicholas@2498 2593 console.log('Response: ' + response.textContent);
nicholas@2498 2594 root.appendChild(question);
nicholas@2498 2595 root.appendChild(response);
nicholas@2224 2596 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2597 return root;
nicholas@2498 2598 };
nicholas@2498 2599 this.resize = function () {
nicholas@2498 2600 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2601 if (boxwidth >= 600) {
nicholas@2498 2602 boxwidth = 600;
nicholas@2498 2603 } else if (boxwidth < 400) {
nicholas@2498 2604 boxwidth = 400;
nicholas@2498 2605 }
nicholas@2498 2606 this.holder.style.width = boxwidth + "px";
nicholas@2498 2607 var text = this.holder.getElementsByClassName("comment-radio-span-holder")[0];
nicholas@2498 2608 var options = this.holder.getElementsByClassName("comment-radio-inputs-holder")[0];
nicholas@2498 2609 var optCount = options.childElementCount;
nicholas@2498 2610 var spanMargin = Math.floor(((boxwidth - 20 - (optCount * 80)) / (optCount)) / 2) + 'px';
nicholas@2498 2611 var options = options.firstChild;
nicholas@2498 2612 var text = text.firstChild;
nicholas@2498 2613 options.style.marginRight = spanMargin;
nicholas@2498 2614 options.style.marginLeft = spanMargin;
nicholas@2498 2615 text.style.marginRight = spanMargin;
nicholas@2498 2616 text.style.marginLeft = spanMargin;
nicholas@2498 2617 while (options.nextSibling != undefined) {
nicholas@2498 2618 options = options.nextSibling;
nicholas@2498 2619 text = text.nextSibling;
nicholas@2498 2620 options.style.marginRight = spanMargin;
nicholas@2498 2621 options.style.marginLeft = spanMargin;
nicholas@2498 2622 text.style.marginRight = spanMargin;
nicholas@2498 2623 text.style.marginLeft = spanMargin;
nicholas@2498 2624 }
nicholas@2498 2625 };
nicholas@2498 2626 this.resize();
nicholas@2498 2627 };
nicholas@2498 2628
nicholas@2498 2629 this.checkboxBox = function (commentQuestion) {
nicholas@2498 2630 this.specification = commentQuestion;
nicholas@2498 2631 // Create document objects to hold the comment boxes
nicholas@2498 2632 this.holder = document.createElement('div');
nicholas@2498 2633 this.holder.className = 'comment-div';
nicholas@2498 2634 // Create a string next to each comment asking for a comment
nicholas@2498 2635 this.string = document.createElement('span');
nicholas@2498 2636 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2637 var br = document.createElement('br');
nicholas@2498 2638 // Add to the holder.
nicholas@2498 2639 this.holder.appendChild(this.string);
nicholas@2498 2640 this.holder.appendChild(br);
nicholas@2498 2641 this.options = [];
nicholas@2498 2642 this.inputs = document.createElement('div');
nicholas@2498 2643 this.span = document.createElement('div');
nicholas@2498 2644 this.inputs.align = 'center';
nicholas@2498 2645 this.inputs.style.marginLeft = '12px';
nicholas@2294 2646 this.inputs.className = "comment-checkbox-inputs-holder";
nicholas@2498 2647 this.span.style.marginLeft = '12px';
nicholas@2498 2648 this.span.align = 'center';
nicholas@2498 2649 this.span.style.marginTop = '15px';
nicholas@2294 2650 this.span.className = "comment-checkbox-span-holder";
nicholas@2498 2651
nicholas@2498 2652 var optCount = commentQuestion.options.length;
nicholas@2498 2653 for (var i = 0; i < optCount; i++) {
nicholas@2498 2654 var div = document.createElement('div');
nicholas@2498 2655 div.style.width = '80px';
nicholas@2498 2656 div.style.float = 'left';
nicholas@2498 2657 var input = document.createElement('input');
nicholas@2498 2658 input.type = 'checkbox';
nicholas@2498 2659 input.name = commentQuestion.id;
nicholas@2498 2660 input.setAttribute('setvalue', commentQuestion.options[i].name);
nicholas@2498 2661 input.className = 'comment-radio';
nicholas@2498 2662 div.appendChild(input);
nicholas@2498 2663 this.inputs.appendChild(div);
nicholas@2498 2664
nicholas@2498 2665
nicholas@2498 2666 div = document.createElement('div');
nicholas@2498 2667 div.style.width = '80px';
nicholas@2498 2668 div.style.float = 'left';
nicholas@2498 2669 div.align = 'center';
nicholas@2498 2670 var span = document.createElement('span');
nicholas@2498 2671 span.textContent = commentQuestion.options[i].text;
nicholas@2498 2672 span.className = 'comment-radio-span';
nicholas@2498 2673 div.appendChild(span);
nicholas@2498 2674 this.span.appendChild(div);
nicholas@2498 2675 this.options.push(input);
nicholas@2498 2676 }
nicholas@2498 2677 this.holder.appendChild(this.span);
nicholas@2498 2678 this.holder.appendChild(this.inputs);
nicholas@2498 2679
nicholas@2498 2680 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2681 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2682 root.id = this.specification.id;
nicholas@2498 2683 root.setAttribute('type', this.specification.type);
nicholas@2498 2684 var question = document.createElement('question');
nicholas@2498 2685 question.textContent = this.string.textContent;
nicholas@2498 2686 root.appendChild(question);
nicholas@2498 2687 console.log('Comment: ' + question.textContent);
nicholas@2498 2688 for (var i = 0; i < this.options.length; i++) {
nicholas@2498 2689 var response = document.createElement('response');
nicholas@2498 2690 response.textContent = this.options[i].checked;
nicholas@2498 2691 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
nicholas@2498 2692 root.appendChild(response);
nicholas@2498 2693 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
nicholas@2498 2694 }
nicholas@2224 2695 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2696 return root;
nicholas@2498 2697 };
nicholas@2498 2698 this.resize = function () {
nicholas@2498 2699 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2700 if (boxwidth >= 600) {
nicholas@2498 2701 boxwidth = 600;
nicholas@2498 2702 } else if (boxwidth < 400) {
nicholas@2498 2703 boxwidth = 400;
nicholas@2498 2704 }
nicholas@2498 2705 this.holder.style.width = boxwidth + "px";
nicholas@2498 2706 var text = this.holder.getElementsByClassName("comment-checkbox-span-holder")[0];
nicholas@2498 2707 var options = this.holder.getElementsByClassName("comment-checkbox-inputs-holder")[0];
nicholas@2498 2708 var optCount = options.childElementCount;
nicholas@2498 2709 var spanMargin = Math.floor(((boxwidth - 20 - (optCount * 80)) / (optCount)) / 2) + 'px';
nicholas@2498 2710 var options = options.firstChild;
nicholas@2498 2711 var text = text.firstChild;
nicholas@2498 2712 options.style.marginRight = spanMargin;
nicholas@2498 2713 options.style.marginLeft = spanMargin;
nicholas@2498 2714 text.style.marginRight = spanMargin;
nicholas@2498 2715 text.style.marginLeft = spanMargin;
nicholas@2498 2716 while (options.nextSibling != undefined) {
nicholas@2498 2717 options = options.nextSibling;
nicholas@2498 2718 text = text.nextSibling;
nicholas@2498 2719 options.style.marginRight = spanMargin;
nicholas@2498 2720 options.style.marginLeft = spanMargin;
nicholas@2498 2721 text.style.marginRight = spanMargin;
nicholas@2498 2722 text.style.marginLeft = spanMargin;
nicholas@2498 2723 }
nicholas@2498 2724 };
nicholas@2498 2725 this.resize();
nicholas@2498 2726 };
nicholas@2498 2727
n@2579 2728 this.sliderBox = function (commentQuestion) {
n@2579 2729 this.specification = commentQuestion;
n@2579 2730 this.holder = document.createElement("div");
n@2579 2731 this.holder.className = 'comment-div';
n@2579 2732 this.string = document.createElement("span");
n@2579 2733 this.string.innerHTML = commentQuestion.statement;
n@2579 2734 this.slider = document.createElement("input");
n@2579 2735 this.slider.type = "range";
n@2579 2736 this.slider.min = commentQuestion.min;
n@2579 2737 this.slider.max = commentQuestion.max;
n@2579 2738 this.slider.step = commentQuestion.step;
n@2579 2739 this.slider.value = commentQuestion.value;
n@2579 2740 var br = document.createElement('br');
n@2579 2741
n@2580 2742 var textHolder = document.createElement("div");
n@2580 2743 textHolder.className = "comment-slider-text-holder";
n@2580 2744
n@2580 2745 this.leftText = document.createElement("span");
n@2580 2746 this.leftText.textContent = commentQuestion.leftText;
n@2580 2747 this.rightText = document.createElement("span");
n@2580 2748 this.rightText.textContent = commentQuestion.rightText;
n@2580 2749 textHolder.appendChild(this.leftText);
n@2580 2750 textHolder.appendChild(this.rightText);
n@2580 2751
n@2579 2752 this.holder.appendChild(this.string);
n@2579 2753 this.holder.appendChild(br);
n@2579 2754 this.holder.appendChild(this.slider);
n@2580 2755 this.holder.appendChild(textHolder);
n@2579 2756
n@2579 2757 this.exportXMLDOM = function (storePoint) {
n@2579 2758 var root = storePoint.parent.document.createElement('comment');
n@2579 2759 root.id = this.specification.id;
n@2579 2760 root.setAttribute('type', this.specification.type);
n@2579 2761 console.log("Question: " + this.string.textContent);
n@2579 2762 console.log("Response: " + this.slider.value);
n@2579 2763 var question = storePoint.parent.document.createElement('question');
n@2579 2764 question.textContent = this.string.textContent;
n@2579 2765 var response = storePoint.parent.document.createElement('response');
n@2579 2766 response.textContent = this.slider.value;
n@2579 2767 root.appendChild(question);
n@2579 2768 root.appendChild(response);
n@2579 2769 storePoint.XMLDOM.appendChild(root);
n@2579 2770 return root;
n@2579 2771 };
n@2579 2772 this.resize = function () {
n@2579 2773 var boxwidth = (window.innerWidth - 100) / 2;
n@2579 2774 if (boxwidth >= 600) {
n@2579 2775 boxwidth = 600;
n@2579 2776 } else if (boxwidth < 400) {
n@2579 2777 boxwidth = 400;
n@2579 2778 }
n@2579 2779 this.holder.style.width = boxwidth + "px";
n@2579 2780 this.slider.style.width = boxwidth - 24 + "px";
n@2579 2781 };
n@2579 2782 this.resize();
n@2579 2783 };
n@2579 2784
nicholas@2498 2785 this.createCommentQuestion = function (element) {
nicholas@2498 2786 var node;
nicholas@2498 2787 if (element.type == 'question') {
nicholas@2498 2788 node = new this.commentBox(element);
nicholas@2498 2789 } else if (element.type == 'radio') {
nicholas@2498 2790 node = new this.radioBox(element);
nicholas@2498 2791 } else if (element.type == 'checkbox') {
nicholas@2498 2792 node = new this.checkboxBox(element);
n@2579 2793 } else if (element.type == 'slider') {
n@2579 2794 node = new this.sliderBox(element);
nicholas@2498 2795 }
nicholas@2498 2796 this.commentQuestions.push(node);
nicholas@2498 2797 return node;
nicholas@2498 2798 };
nicholas@2498 2799
nicholas@2498 2800 this.deleteCommentQuestions = function () {
nicholas@2498 2801 this.commentQuestions = [];
nicholas@2498 2802 };
nicholas@2498 2803
nicholas@2498 2804 this.outsideReferenceDOM = function (audioObject, index, inject) {
nicholas@2224 2805 this.parent = audioObject;
nicholas@2224 2806 this.outsideReferenceHolder = document.createElement('button');
nicholas@2224 2807 this.outsideReferenceHolder.className = 'outside-reference';
nicholas@2498 2808 this.outsideReferenceHolder.setAttribute('track-id', index);
nicholas@2409 2809 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
nicholas@2224 2810 this.outsideReferenceHolder.disabled = true;
nicholas@2224 2811
nicholas@2498 2812 this.outsideReferenceHolder.onclick = function (event) {
nicholas@2224 2813 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
nicholas@2224 2814 };
nicholas@2224 2815 inject.appendChild(this.outsideReferenceHolder);
nicholas@2498 2816 this.enable = function () {
nicholas@2498 2817 if (this.parent.state == 1) {
nicholas@2224 2818 this.outsideReferenceHolder.disabled = false;
nicholas@2224 2819 }
nicholas@2224 2820 };
nicholas@2498 2821 this.updateLoading = function (progress) {
nicholas@2498 2822 if (progress != 100) {
nicholas@2224 2823 progress = String(progress);
nicholas@2224 2824 progress = progress.split('.')[0];
nicholas@2498 2825 this.outsideReferenceHolder.textContent = progress + '%';
nicholas@2224 2826 } else {
nicholas@2409 2827 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
nicholas@2224 2828 }
nicholas@2224 2829 };
nicholas@2498 2830 this.startPlayback = function () {
nicholas@2224 2831 // Called when playback has begun
nicholas@2224 2832 $('.track-slider').removeClass('track-slider-playing');
nicholas@2224 2833 $('.comment-div').removeClass('comment-box-playing');
nicholas@2224 2834 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
nicholas@2224 2835 };
nicholas@2498 2836 this.stopPlayback = function () {
nicholas@2224 2837 // Called when playback has stopped. This gets called even if playback never started!
nicholas@2224 2838 this.outsideReferenceHolder.style.backgroundColor = "";
nicholas@2224 2839 };
nicholas@2498 2840 this.exportXMLDOM = function (audioObject) {
nicholas@2224 2841 return null;
nicholas@2224 2842 };
nicholas@2498 2843 this.getValue = function () {
nicholas@2224 2844 return 0;
nicholas@2224 2845 };
nicholas@2498 2846 this.getPresentedId = function () {
nicholas@2409 2847 return this.parent.specification.label || "Reference";
nicholas@2224 2848 };
nicholas@2498 2849 this.canMove = function () {
nicholas@2224 2850 return false;
nicholas@2224 2851 };
nicholas@2498 2852 this.error = function () {
nicholas@2498 2853 // audioObject has an error!!
nicholas@2224 2854 this.outsideReferenceHolder.textContent = "Error";
nicholas@2224 2855 this.outsideReferenceHolder.style.backgroundColor = "#F00";
nicholas@2224 2856 }
nicholas@2224 2857 }
nicholas@2498 2858
nicholas@2498 2859 this.playhead = new function () {
nicholas@2498 2860 this.object = document.createElement('div');
nicholas@2498 2861 this.object.className = 'playhead';
nicholas@2498 2862 this.object.align = 'left';
nicholas@2498 2863 var curTime = document.createElement('div');
nicholas@2498 2864 curTime.style.width = '50px';
nicholas@2498 2865 this.curTimeSpan = document.createElement('span');
nicholas@2498 2866 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2867 curTime.appendChild(this.curTimeSpan);
nicholas@2498 2868 this.object.appendChild(curTime);
nicholas@2498 2869 this.scrubberTrack = document.createElement('div');
nicholas@2498 2870 this.scrubberTrack.className = 'playhead-scrub-track';
nicholas@2498 2871
nicholas@2498 2872 this.scrubberHead = document.createElement('div');
nicholas@2498 2873 this.scrubberHead.id = 'playhead-scrubber';
nicholas@2498 2874 this.scrubberTrack.appendChild(this.scrubberHead);
nicholas@2498 2875 this.object.appendChild(this.scrubberTrack);
nicholas@2498 2876
nicholas@2498 2877 this.timePerPixel = 0;
nicholas@2498 2878 this.maxTime = 0;
nicholas@2498 2879
nicholas@2498 2880 this.playbackObject;
nicholas@2498 2881
nicholas@2498 2882 this.setTimePerPixel = function (audioObject) {
nicholas@2498 2883 //maxTime must be in seconds
nicholas@2498 2884 this.playbackObject = audioObject;
nicholas@2498 2885 this.maxTime = audioObject.buffer.buffer.duration;
nicholas@2498 2886 var width = 490; //500 - 10, 5 each side of the tracker head
nicholas@2498 2887 this.timePerPixel = this.maxTime / 490;
nicholas@2498 2888 if (this.maxTime < 60) {
nicholas@2498 2889 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2890 } else {
nicholas@2498 2891 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2892 }
nicholas@2498 2893 };
nicholas@2498 2894
nicholas@2498 2895 this.update = function () {
nicholas@2498 2896 // Update the playhead position, startPlay must be called
nicholas@2498 2897 if (this.timePerPixel > 0) {
nicholas@2498 2898 var time = this.playbackObject.getCurrentPosition();
nicholas@2498 2899 if (time > 0 && time < this.maxTime) {
nicholas@2498 2900 var width = 490;
nicholas@2498 2901 var pix = Math.floor(time / this.timePerPixel);
nicholas@2498 2902 this.scrubberHead.style.left = pix + 'px';
nicholas@2498 2903 if (this.maxTime > 60.0) {
nicholas@2498 2904 var secs = time % 60;
nicholas@2498 2905 var mins = Math.floor((time - secs) / 60);
nicholas@2498 2906 secs = secs.toString();
nicholas@2498 2907 secs = secs.substr(0, 2);
nicholas@2498 2908 mins = mins.toString();
nicholas@2498 2909 this.curTimeSpan.textContent = mins + ':' + secs;
nicholas@2498 2910 } else {
nicholas@2498 2911 time = time.toString();
nicholas@2498 2912 this.curTimeSpan.textContent = time.substr(0, 4);
nicholas@2498 2913 }
nicholas@2498 2914 } else {
nicholas@2498 2915 this.scrubberHead.style.left = '0px';
nicholas@2498 2916 if (this.maxTime < 60) {
nicholas@2498 2917 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2918 } else {
nicholas@2498 2919 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2920 }
nicholas@2498 2921 }
nicholas@2498 2922 }
nicholas@2498 2923 };
nicholas@2498 2924
nicholas@2498 2925 this.interval = undefined;
nicholas@2498 2926
nicholas@2498 2927 this.start = function () {
nicholas@2498 2928 if (this.playbackObject != undefined && this.interval == undefined) {
nicholas@2498 2929 if (this.maxTime < 60) {
nicholas@2498 2930 this.interval = setInterval(function () {
nicholas@2498 2931 interfaceContext.playhead.update();
nicholas@2498 2932 }, 10);
nicholas@2498 2933 } else {
nicholas@2498 2934 this.interval = setInterval(function () {
nicholas@2498 2935 interfaceContext.playhead.update();
nicholas@2498 2936 }, 100);
nicholas@2498 2937 }
nicholas@2498 2938 }
nicholas@2498 2939 };
nicholas@2498 2940 this.stop = function () {
nicholas@2498 2941 clearInterval(this.interval);
nicholas@2498 2942 this.interval = undefined;
nicholas@2224 2943 this.scrubberHead.style.left = '0px';
nicholas@2498 2944 if (this.maxTime < 60) {
nicholas@2498 2945 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2946 } else {
nicholas@2498 2947 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2948 }
nicholas@2498 2949 };
nicholas@2498 2950 };
nicholas@2498 2951
nicholas@2498 2952 this.volume = new function () {
nicholas@2224 2953 // An in-built volume module which can be viewed on page
nicholas@2224 2954 // Includes trackers on page-by-page data
nicholas@2224 2955 // Volume does NOT reset to 0dB on each page load
nicholas@2224 2956 this.valueLin = 1.0;
nicholas@2224 2957 this.valueDB = 0.0;
nicholas@2352 2958 this.root = document.createElement('div');
nicholas@2352 2959 this.root.id = 'master-volume-root';
nicholas@2224 2960 this.object = document.createElement('div');
nicholas@2352 2961 this.object.className = 'master-volume-holder-float';
nicholas@2352 2962 this.object.appendChild(this.root);
nicholas@2224 2963 this.slider = document.createElement('input');
nicholas@2224 2964 this.slider.id = 'master-volume-control';
nicholas@2224 2965 this.slider.type = 'range';
nicholas@2224 2966 this.valueText = document.createElement('span');
nicholas@2224 2967 this.valueText.id = 'master-volume-feedback';
nicholas@2224 2968 this.valueText.textContent = '0dB';
nicholas@2498 2969
nicholas@2224 2970 this.slider.min = -60;
nicholas@2224 2971 this.slider.max = 12;
nicholas@2224 2972 this.slider.value = 0;
nicholas@2224 2973 this.slider.step = 1;
nicholas@2498 2974 this.slider.onmousemove = function (event) {
nicholas@2224 2975 interfaceContext.volume.valueDB = event.currentTarget.value;
nicholas@2224 2976 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
nicholas@2498 2977 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB + 'dB';
nicholas@2224 2978 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
nicholas@2224 2979 }
nicholas@2498 2980 this.slider.onmouseup = function (event) {
nicholas@2224 2981 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
nicholas@2498 2982 if (storePoint.length == 0) {
nicholas@2224 2983 storePoint = storage.document.createElement('metricresult');
nicholas@2498 2984 storePoint.setAttribute('name', 'volumeTracker');
nicholas@2224 2985 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
nicholas@2498 2986 } else {
nicholas@2224 2987 storePoint = storePoint[0];
nicholas@2224 2988 }
nicholas@2224 2989 var node = storage.document.createElement('movement');
nicholas@2498 2990 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
nicholas@2498 2991 node.setAttribute('volume', interfaceContext.volume.valueDB);
nicholas@2498 2992 node.setAttribute('format', 'dBFS');
nicholas@2224 2993 storePoint.appendChild(node);
nicholas@2224 2994 }
nicholas@2498 2995
nicholas@2224 2996 var title = document.createElement('div');
nicholas@2224 2997 title.innerHTML = '<span>Master Volume Control</span>';
nicholas@2224 2998 title.style.fontSize = '0.75em';
nicholas@2224 2999 title.style.width = "100%";
nicholas@2224 3000 title.align = 'center';
nicholas@2352 3001 this.root.appendChild(title);
nicholas@2498 3002
nicholas@2352 3003 this.root.appendChild(this.slider);
nicholas@2352 3004 this.root.appendChild(this.valueText);
nicholas@2498 3005
nicholas@2498 3006 this.resize = function (event) {
nicholas@2352 3007 if (window.innerWidth < 1000) {
nicholas@2352 3008 this.object.className = "master-volume-holder-inline"
nicholas@2352 3009 } else {
nicholas@2352 3010 this.object.className = 'master-volume-holder-float';
nicholas@2352 3011 }
nicholas@2352 3012 }
nicholas@2224 3013 }
nicholas@2498 3014
nicholas@2224 3015 this.calibrationModuleObject = null;
nicholas@2498 3016 this.calibrationModule = function () {
nicholas@2224 3017 // This creates an on-page calibration module
nicholas@2224 3018 this.storeDOM = storage.document.createElement("calibration");
nicholas@2224 3019 storage.root.appendChild(this.storeDOM);
nicholas@2224 3020 // The calibration is a fixed state module
nicholas@2224 3021 this.calibrationNodes = [];
nicholas@2224 3022 this.holder = null;
nicholas@2498 3023 this.build = function (inject) {
nicholas@2224 3024 var f0 = 62.5;
nicholas@2224 3025 this.holder = document.createElement("div");
nicholas@2224 3026 this.holder.className = "calibration-holder";
nicholas@2224 3027 this.calibrationNodes = [];
nicholas@2498 3028 while (f0 < 20000) {
nicholas@2224 3029 var obj = {
nicholas@2224 3030 root: document.createElement("div"),
nicholas@2224 3031 input: document.createElement("input"),
nicholas@2224 3032 oscillator: audioContext.createOscillator(),
nicholas@2224 3033 gain: audioContext.createGain(),
nicholas@2224 3034 f: f0,
nicholas@2224 3035 parent: this,
nicholas@2498 3036 handleEvent: function (event) {
nicholas@2498 3037 switch (event.type) {
nicholas@2224 3038 case "mouseenter":
nicholas@2224 3039 this.oscillator.start(0);
nicholas@2224 3040 break;
nicholas@2224 3041 case "mouseleave":
nicholas@2224 3042 this.oscillator.stop(0);
nicholas@2224 3043 this.oscillator = audioContext.createOscillator();
nicholas@2224 3044 this.oscillator.connect(this.gain);
nicholas@2224 3045 this.oscillator.frequency.value = this.f;
nicholas@2224 3046 break;
nicholas@2224 3047 case "mousemove":
nicholas@2498 3048 var value = Math.pow(10, this.input.value / 20);
nicholas@2224 3049 if (this.f == 1000) {
nicholas@2224 3050 audioEngineContext.outputGain.gain.value = value;
nicholas@2224 3051 interfaceContext.volume.slider.value = this.input.value;
nicholas@2224 3052 } else {
nicholas@2224 3053 this.gain.gain.value = value
nicholas@2224 3054 }
nicholas@2224 3055 break;
nicholas@2224 3056 }
nicholas@2224 3057 },
nicholas@2498 3058 disconnect: function () {
nicholas@2224 3059 this.gain.disconnect();
nicholas@2224 3060 }
nicholas@2224 3061 }
nicholas@2224 3062 obj.root.className = "calibration-slider";
nicholas@2224 3063 obj.root.appendChild(obj.input);
nicholas@2224 3064 obj.oscillator.connect(obj.gain);
nicholas@2224 3065 obj.gain.connect(audioEngineContext.outputGain);
nicholas@2498 3066 obj.gain.gain.value = Math.random() * 2;
nicholas@2224 3067 obj.input.value = obj.gain.gain.value;
nicholas@2498 3068 obj.input.setAttribute('orient', 'vertical');
nicholas@2224 3069 obj.input.type = "range";
nicholas@2593 3070 obj.input.min = -12;
nicholas@2593 3071 obj.input.max = 0;
nicholas@2224 3072 obj.input.step = 0.25;
nicholas@2224 3073 if (f0 != 1000) {
nicholas@2498 3074 obj.input.value = (Math.random() * 12) - 6;
nicholas@2224 3075 } else {
nicholas@2224 3076 obj.input.value = 0;
nicholas@2498 3077 obj.root.style.backgroundColor = "rgb(255,125,125)";
nicholas@2224 3078 }
nicholas@2498 3079 obj.input.addEventListener("mousemove", obj);
nicholas@2498 3080 obj.input.addEventListener("mouseenter", obj);
nicholas@2498 3081 obj.input.addEventListener("mouseleave", obj);
nicholas@2498 3082 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
nicholas@2224 3083 obj.oscillator.frequency.value = f0;
nicholas@2224 3084 this.calibrationNodes.push(obj);
nicholas@2224 3085 this.holder.appendChild(obj.root);
nicholas@2224 3086 f0 *= 2;
nicholas@2224 3087 }
nicholas@2224 3088 inject.appendChild(this.holder);
nicholas@2224 3089 }
nicholas@2498 3090 this.collect = function () {
nicholas@2224 3091 for (var obj of this.calibrationNodes) {
nicholas@2224 3092 var node = storage.document.createElement("calibrationresult");
nicholas@2498 3093 node.setAttribute("frequency", obj.f);
nicholas@2498 3094 node.setAttribute("range-min", obj.input.min);
nicholas@2498 3095 node.setAttribute("range-max", obj.input.max);
nicholas@2498 3096 node.setAttribute("gain-lin", obj.gain.gain.value);
nicholas@2224 3097 this.storeDOM.appendChild(node);
nicholas@2224 3098 }
nicholas@2224 3099 }
nicholas@2224 3100 }
nicholas@2498 3101
nicholas@2498 3102
nicholas@2498 3103 // Global Checkers
nicholas@2498 3104 // These functions will help enforce the checkers
nicholas@2498 3105 this.checkHiddenAnchor = function () {
nicholas@2498 3106 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3107 if (ao.specification.type == "anchor") {
nicholas@2498 3108 if (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0) {
nicholas@2498 3109 // Anchor is not set below
nicholas@2498 3110 console.log('Anchor node not below marker value');
nicholas@2498 3111 interfaceContext.lightbox.post("Message", 'Please keep listening');
nicholas@2224 3112 this.storeErrorNode('Anchor node not below marker value');
nicholas@2498 3113 return false;
nicholas@2498 3114 }
nicholas@2498 3115 }
nicholas@2498 3116 }
nicholas@2498 3117 return true;
nicholas@2498 3118 };
nicholas@2498 3119
nicholas@2498 3120 this.checkHiddenReference = function () {
nicholas@2498 3121 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3122 if (ao.specification.type == "reference") {
nicholas@2498 3123 if (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0) {
nicholas@2498 3124 // Anchor is not set below
nicholas@2498 3125 console.log('Reference node not above marker value');
nicholas@2224 3126 this.storeErrorNode('Reference node not above marker value');
nicholas@2498 3127 interfaceContext.lightbox.post("Message", 'Please keep listening');
nicholas@2498 3128 return false;
nicholas@2498 3129 }
nicholas@2498 3130 }
nicholas@2498 3131 }
nicholas@2498 3132 return true;
nicholas@2498 3133 };
nicholas@2498 3134
nicholas@2498 3135 this.checkFragmentsFullyPlayed = function () {
nicholas@2498 3136 // Checks the entire file has been played back
nicholas@2498 3137 // NOTE ! This will return true IF playback is Looped!!!
nicholas@2498 3138 if (audioEngineContext.loopPlayback) {
nicholas@2498 3139 console.log("WARNING - Looped source: Cannot check fragments are fully played");
nicholas@2498 3140 return true;
nicholas@2498 3141 }
nicholas@2498 3142 var check_pass = true;
nicholas@2498 3143 var error_obj = [];
nicholas@2498 3144 for (var i = 0; i < audioEngineContext.audioObjects.length; i++) {
nicholas@2498 3145 var object = audioEngineContext.audioObjects[i];
nicholas@2498 3146 var time = object.buffer.buffer.duration;
nicholas@2498 3147 var metric = object.metric;
nicholas@2498 3148 var passed = false;
nicholas@2498 3149 for (var j = 0; j < metric.listenTracker.length; j++) {
nicholas@2498 3150 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
nicholas@2498 3151 var start_time = Number(bt[0].getAttribute('start'));
nicholas@2498 3152 var stop_time = Number(bt[0].getAttribute('stop'));
nicholas@2498 3153 var delta = stop_time - start_time;
nicholas@2498 3154 if (delta >= time) {
nicholas@2498 3155 passed = true;
nicholas@2498 3156 break;
nicholas@2498 3157 }
nicholas@2498 3158 }
nicholas@2498 3159 if (passed == false) {
nicholas@2498 3160 check_pass = false;
nicholas@2498 3161 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
nicholas@2498 3162 error_obj.push(object.interfaceDOM.getPresentedId());
nicholas@2498 3163 }
nicholas@2498 3164 }
nicholas@2498 3165 if (check_pass == false) {
nicholas@2498 3166 var str_start = "You have not completely listened to fragments ";
nicholas@2498 3167 for (var i = 0; i < error_obj.length; i++) {
nicholas@2498 3168 str_start += error_obj[i];
nicholas@2498 3169 if (i != error_obj.length - 1) {
nicholas@2498 3170 str_start += ', ';
nicholas@2498 3171 }
nicholas@2498 3172 }
nicholas@2498 3173 str_start += ". Please keep listening";
nicholas@2498 3174 console.log("[ALERT]: " + str_start);
nicholas@2498 3175 this.storeErrorNode("[ALERT]: " + str_start);
nicholas@2498 3176 interfaceContext.lightbox.post("Error", str_start);
nicholas@2444 3177 return false;
nicholas@2498 3178 }
nicholas@2444 3179 return true;
nicholas@2498 3180 };
nicholas@2498 3181 this.checkAllMoved = function () {
nicholas@2498 3182 var str = "You have not moved ";
nicholas@2498 3183 var failed = [];
nicholas@2498 3184 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3185 if (ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true) {
nicholas@2498 3186 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@2498 3187 }
nicholas@2498 3188 }
nicholas@2498 3189 if (failed.length == 0) {
nicholas@2498 3190 return true;
nicholas@2498 3191 } else if (failed.length == 1) {
nicholas@2498 3192 str += 'track ' + failed[0];
nicholas@2498 3193 } else {
nicholas@2498 3194 str += 'tracks ';
nicholas@2498 3195 for (var i = 0; i < failed.length - 1; i++) {
nicholas@2498 3196 str += failed[i] + ', ';
nicholas@2498 3197 }
nicholas@2498 3198 str += 'and ' + failed[i];
nicholas@2498 3199 }
nicholas@2498 3200 str += '.';
nicholas@2498 3201 interfaceContext.lightbox.post("Error", str);
nicholas@2498 3202 console.log(str);
nicholas@2224 3203 this.storeErrorNode(str);
nicholas@2498 3204 return false;
nicholas@2498 3205 };
nicholas@2498 3206 this.checkAllPlayed = function () {
nicholas@2498 3207 var str = "You have not played ";
nicholas@2498 3208 var failed = [];
nicholas@2498 3209 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3210 if (ao.metric.wasListenedTo == false) {
nicholas@2498 3211 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@2498 3212 }
nicholas@2498 3213 }
nicholas@2498 3214 if (failed.length == 0) {
nicholas@2498 3215 return true;
nicholas@2498 3216 } else if (failed.length == 1) {
nicholas@2498 3217 str += 'track ' + failed[0];
nicholas@2498 3218 } else {
nicholas@2498 3219 str += 'tracks ';
nicholas@2498 3220 for (var i = 0; i < failed.length - 1; i++) {
nicholas@2498 3221 str += failed[i] + ', ';
nicholas@2498 3222 }
nicholas@2498 3223 str += 'and ' + failed[i];
nicholas@2498 3224 }
nicholas@2498 3225 str += '.';
nicholas@2498 3226 interfaceContext.lightbox.post("Error", str);
nicholas@2498 3227 console.log(str);
nicholas@2224 3228 this.storeErrorNode(str);
nicholas@2498 3229 return false;
nicholas@2498 3230 };
nicholas@2540 3231 this.checkAllCommented = function () {
nicholas@2540 3232 var str = "You have not commented on all the fragments.";
nicholas@2540 3233 var cont = true,
nicholas@2540 3234 boxes = this.commentBoxes.boxes,
nicholas@2540 3235 numBoxes = boxes.length,
nicholas@2540 3236 i;
nicholas@2540 3237 for (i = 0; i < numBoxes; i++) {
nicholas@2540 3238 if (boxes[i].trackCommentBox.value === "") {
nicholas@2540 3239 interfaceContext.lightbox.post("Error", str);
nicholas@2540 3240 console.log(str);
nicholas@2540 3241 this.storeErrorNode(str);
nicholas@2540 3242 return false;
nicholas@2540 3243 }
nicholas@2540 3244 }
nicholas@2540 3245 return true;
nicholas@2540 3246 }
nicholas@2498 3247 this.checkScaleRange = function (min, max) {
nicholas@2310 3248 var page = testState.getCurrentTestPage();
nicholas@2310 3249 var audioObjects = audioEngineContext.audioObjects;
nicholas@2310 3250 var state = true;
nicholas@2310 3251 var str = "Please keep listening. ";
nicholas@2310 3252 var minRanking = Infinity;
nicholas@2310 3253 var maxRanking = -Infinity;
nicholas@2310 3254 for (var ao of audioObjects) {
nicholas@2310 3255 var rank = ao.interfaceDOM.getValue();
nicholas@2498 3256 if (rank < minRanking) {
nicholas@2498 3257 minRanking = rank;
nicholas@2498 3258 }
nicholas@2498 3259 if (rank > maxRanking) {
nicholas@2498 3260 maxRanking = rank;
nicholas@2498 3261 }
nicholas@2310 3262 }
nicholas@2498 3263 if (minRanking * 100 > min) {
nicholas@2498 3264 str += "At least one fragment must be below the " + min + " mark.";
nicholas@2310 3265 state = false;
nicholas@2310 3266 }
nicholas@2498 3267 if (maxRanking * 100 < max) {
nicholas@2498 3268 str += "At least one fragment must be above the " + max + " mark."
nicholas@2310 3269 state = false;
nicholas@2310 3270 }
nicholas@2310 3271 if (!state) {
nicholas@2310 3272 console.log(str);
nicholas@2310 3273 this.storeErrorNode(str);
nicholas@2498 3274 interfaceContext.lightbox.post("Error", str);
nicholas@2310 3275 }
nicholas@2310 3276 return state;
nicholas@2310 3277 }
nicholas@2498 3278
nicholas@2498 3279 this.storeErrorNode = function (errorMessage) {
nicholas@2224 3280 var time = audioEngineContext.timer.getTestTime();
nicholas@2224 3281 var node = storage.document.createElement('error');
nicholas@2498 3282 node.setAttribute('time', time);
nicholas@2224 3283 node.textContent = errorMessage;
nicholas@2224 3284 testState.currentStore.XMLDOM.appendChild(node);
nicholas@2224 3285 };
nicholas@2595 3286
nicholas@2595 3287 this.getLabel = function (labelType, index, labelStart) {
nicholas@2595 3288 /*
nicholas@2595 3289 Get the correct label based on type, index and offset
nicholas@2595 3290 */
nicholas@2595 3291
nicholas@2595 3292 function calculateLabel(labelType, index, offset) {
nicholas@2595 3293 if (labelType == "none") {
nicholas@2595 3294 return "";
nicholas@2595 3295 }
nicholas@2595 3296 switch (labelType) {
nicholas@2595 3297 case "letter":
nicholas@2596 3298 return String.fromCharCode((index + offset) % 26 + 97);
nicholas@2595 3299 case "capital":
nicholas@2607 3300 return String.fromCharCode((index + offset) % 26 + 65);
nicholas@2595 3301 case "number":
nicholas@2595 3302 return String(index + offset);
nicholas@2595 3303 default:
nicholas@2595 3304 return "";
nicholas@2595 3305 }
nicholas@2595 3306 }
nicholas@2595 3307
nicholas@2596 3308 if (typeof labelStart !== "string" || labelStart.length == 0) {
nicholas@2595 3309 labelStart = String.fromCharCode(0);
nicholas@2595 3310 }
nicholas@2595 3311
nicholas@2595 3312 switch (labelType) {
nicholas@2595 3313 case "letter":
nicholas@2595 3314 labelStart = labelStart.charCodeAt(0);
nicholas@2596 3315 if (labelStart < 97 || labelStart > 122) {
nicholas@2595 3316 labelStart = 97;
nicholas@2595 3317 }
nicholas@2595 3318 labelStart -= 97;
nicholas@2595 3319 break;
nicholas@2595 3320 case "capital":
nicholas@2595 3321 labelStart = labelStart.charCodeAt(0);
nicholas@2596 3322 if (labelStart < 65 || labelStart > 90) {
nicholas@2595 3323 labelStart = 65;
nicholas@2595 3324 }
nicholas@2595 3325 labelStart -= 65;
nicholas@2595 3326 break;
nicholas@2595 3327 case "number":
nicholas@2608 3328 labelStart = Number(labelStart);
nicholas@2608 3329 if (!isFinite(labelStart)) {
nicholas@2595 3330 labelStart = 1;
nicholas@2595 3331 }
nicholas@2595 3332 break;
nicholas@2595 3333 case "none":
nicholas@2595 3334 default:
nicholas@2596 3335 labelStart = 0;
nicholas@2595 3336 }
nicholas@2595 3337 if (typeof index == "number") {
nicholas@2595 3338 return calculateLabel(labelType, index, labelStart);
nicholas@2595 3339 } else if (index.length && index.length > 0) {
nicholas@2595 3340 var a = [],
nicholas@2595 3341 l = index.length,
nicholas@2595 3342 i;
nicholas@2595 3343 for (i = 0; i < l; i++) {
nicholas@2595 3344 a[i] = calculateLabel(labelType, index[i], labelStart);
nicholas@2595 3345 }
nicholas@2595 3346 return a;
nicholas@2595 3347 } else {
nicholas@2595 3348 throw ("Invalid arguments");
nicholas@2595 3349 }
nicholas@2595 3350 }
nicholas@2224 3351 }
nicholas@2224 3352
nicholas@2498 3353 function Storage() {
nicholas@2498 3354 // Holds results in XML format until ready for collection
nicholas@2498 3355 this.globalPreTest = null;
nicholas@2498 3356 this.globalPostTest = null;
nicholas@2498 3357 this.testPages = [];
nicholas@2498 3358 this.document = null;
nicholas@2498 3359 this.root = null;
nicholas@2498 3360 this.state = 0;
nicholas@2498 3361
nicholas@2498 3362 this.initialise = function (existingStore) {
nicholas@2224 3363 if (existingStore == undefined) {
nicholas@2224 3364 // We need to get the sessionKey
nicholas@2510 3365 this.SessionKey.requestKey();
nicholas@2498 3366 this.document = document.implementation.createDocument(null, "waetresult", null);
nicholas@2224 3367 this.root = this.document.childNodes[0];
nicholas@2224 3368 var projectDocument = specification.projectXML;
nicholas@2498 3369 projectDocument.setAttribute('file-name', url);
nicholas@2498 3370 projectDocument.setAttribute('url', qualifyURL(url));
nicholas@2224 3371 this.root.appendChild(projectDocument);
nicholas@2224 3372 this.root.appendChild(interfaceContext.returnDateNode());
nicholas@2224 3373 this.root.appendChild(interfaceContext.returnNavigator());
nicholas@2224 3374 } else {
nicholas@2224 3375 this.document = existingStore;
nicholas@2294 3376 this.root = existingStore.firstChild;
nicholas@2224 3377 this.SessionKey.key = this.root.getAttribute("key");
nicholas@2224 3378 }
nicholas@2498 3379 if (specification.preTest != undefined) {
nicholas@2498 3380 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
nicholas@2498 3381 }
nicholas@2498 3382 if (specification.postTest != undefined) {
nicholas@2498 3383 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
nicholas@2498 3384 }
nicholas@2498 3385 };
nicholas@2498 3386
nicholas@2224 3387 this.SessionKey = {
nicholas@2224 3388 key: null,
nicholas@2224 3389 request: new XMLHttpRequest(),
nicholas@2224 3390 parent: this,
nicholas@2498 3391 handleEvent: function () {
nicholas@2224 3392 var parse = new DOMParser();
nicholas@2498 3393 var xml = parse.parseFromString(this.request.response, "text/xml");
nicholas@2376 3394 if (this.request.response.length == 0) {
nicholas@2515 3395 console.error("An unspecified error occured, no server key could be generated");
nicholas@2376 3396 return;
nicholas@2376 3397 }
nicholas@2498 3398 if (xml.getElementsByTagName("state").length > 0) {
nicholas@2498 3399 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
nicholas@2498 3400 this.key = xml.getAllElementsByTagName("key")[0].textContent;
nicholas@2498 3401 this.parent.root.setAttribute("key", this.key);
nicholas@2498 3402 this.parent.root.setAttribute("state", "empty");
nicholas@2516 3403 this.update();
nicholas@2515 3404 return;
nicholas@2514 3405 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
nicholas@2515 3406 this.key = null;
nicholas@2514 3407 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
nicholas@2515 3408 return;
nicholas@2498 3409 }
nicholas@2498 3410 }
nicholas@2515 3411 this.key = null;
nicholas@2515 3412 console.error("An unspecified error occured, no server key could be generated");
nicholas@2224 3413 },
nicholas@2510 3414 requestKey: function () {
nicholas@2510 3415 // For new servers, request a new key from the server
nicholas@2510 3416 var returnURL = "";
nicholas@2510 3417 if (typeof specification.projectReturn == "string") {
nicholas@2510 3418 if (specification.projectReturn.substr(0, 4) == "http") {
nicholas@2510 3419 returnURL = specification.projectReturn;
nicholas@2510 3420 }
nicholas@2510 3421 }
nicholas@2510 3422 this.request.open("GET", returnURL + "php/requestKey.php", true);
nicholas@2510 3423 this.request.addEventListener("load", this);
nicholas@2510 3424 this.request.send();
nicholas@2510 3425 },
nicholas@2498 3426 update: function () {
nicholas@2357 3427 if (this.key == null) {
nicholas@2357 3428 console.log("Cannot save as key == null");
nicholas@2357 3429 return;
nicholas@2357 3430 }
nicholas@2498 3431 this.parent.root.setAttribute("state", "update");
nicholas@2224 3432 var xmlhttp = new XMLHttpRequest();
nicholas@2302 3433 var returnURL = "";
nicholas@2302 3434 if (typeof specification.projectReturn == "string") {
nicholas@2498 3435 if (specification.projectReturn.substr(0, 4) == "http") {
nicholas@2302 3436 returnURL = specification.projectReturn;
nicholas@2302 3437 }
nicholas@2302 3438 }
nicholas@2498 3439 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key);
nicholas@2224 3440 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nicholas@2498 3441 xmlhttp.onerror = function () {
nicholas@2224 3442 console.log('Error updating file to server!');
nicholas@2224 3443 };
nicholas@2224 3444 var hold = document.createElement("div");
nicholas@2224 3445 var clone = this.parent.root.cloneNode(true);
nicholas@2224 3446 hold.appendChild(clone);
nicholas@2498 3447 xmlhttp.onload = function () {
nicholas@2224 3448 if (this.status >= 300) {
nicholas@2224 3449 console.log("WARNING - Could not update at this time");
nicholas@2224 3450 } else {
nicholas@2224 3451 var parser = new DOMParser();
nicholas@2224 3452 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nicholas@2224 3453 var response = xmlDoc.getElementsByTagName('response')[0];
nicholas@2224 3454 if (response.getAttribute("state") == "OK") {
nicholas@2224 3455 var file = response.getElementsByTagName("file")[0];
nicholas@2498 3456 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
nicholas@2224 3457 } else {
nicholas@2224 3458 var message = response.getElementsByTagName("message");
nicholas@2498 3459 console.log("Intermediate save: Error! " + message.textContent);
nicholas@2224 3460 }
nicholas@2224 3461 }
nicholas@2224 3462 }
nicholas@2224 3463 xmlhttp.send([hold.innerHTML]);
nicholas@2224 3464 }
nicholas@2224 3465 }
nicholas@2498 3466
nicholas@2498 3467 this.createTestPageStore = function (specification) {
nicholas@2498 3468 var store = new this.pageNode(this, specification);
nicholas@2498 3469 this.testPages.push(store);
nicholas@2498 3470 return this.testPages[this.testPages.length - 1];
nicholas@2498 3471 };
nicholas@2498 3472
nicholas@2498 3473 this.surveyNode = function (parent, root, specification) {
nicholas@2498 3474 this.specification = specification;
nicholas@2498 3475 this.parent = parent;
nicholas@2224 3476 this.state = "empty";
nicholas@2498 3477 this.XMLDOM = this.parent.document.createElement('survey');
nicholas@2498 3478 this.XMLDOM.setAttribute('location', this.specification.location);
nicholas@2498 3479 this.XMLDOM.setAttribute("state", this.state);
nicholas@2498 3480 for (var optNode of this.specification.options) {
nicholas@2498 3481 if (optNode.type != 'statement') {
nicholas@2498 3482 var node = this.parent.document.createElement('surveyresult');
nicholas@2498 3483 node.setAttribute("ref", optNode.id);
nicholas@2498 3484 node.setAttribute('type', optNode.type);
nicholas@2498 3485 this.XMLDOM.appendChild(node);
nicholas@2498 3486 }
nicholas@2498 3487 }
nicholas@2498 3488 root.appendChild(this.XMLDOM);
nicholas@2498 3489
nicholas@2498 3490 this.postResult = function (node) {
nicholas@2498 3491 // From popup: node is the popupOption node containing both spec. and results
nicholas@2498 3492 // ID is the position
nicholas@2498 3493 if (node.specification.type == 'statement') {
nicholas@2498 3494 return;
nicholas@2498 3495 }
nicholas@2498 3496 var surveyresult = this.XMLDOM.firstChild;
nicholas@2498 3497 while (surveyresult != null) {
nicholas@2498 3498 if (surveyresult.getAttribute("ref") == node.specification.id) {
nicholas@2224 3499 break;
nicholas@2224 3500 }
nicholas@2224 3501 surveyresult = surveyresult.nextElementSibling;
nicholas@2224 3502 }
nicholas@2498 3503 switch (node.specification.type) {
nicholas@2498 3504 case "number":
nicholas@2498 3505 case "question":
n@2583 3506 case "slider":
nicholas@2498 3507 var child = this.parent.document.createElement('response');
nicholas@2498 3508 child.textContent = node.response;
nicholas@2498 3509 surveyresult.appendChild(child);
nicholas@2464 3510 break;
nicholas@2498 3511 case "radio":
nicholas@2498 3512 var child = this.parent.document.createElement('response');
nicholas@2571 3513 if (node.response !== null) {
nicholas@2571 3514 child.setAttribute('name', node.response.name);
nicholas@2571 3515 child.textContent = node.response.text;
nicholas@2571 3516 }
nicholas@2498 3517 surveyresult.appendChild(child);
nicholas@2498 3518 break;
nicholas@2498 3519 case "checkbox":
nicholas@2498 3520 if (node.response == undefined) {
nicholas@2498 3521 surveyresult.appendChild(this.parent.document.createElement('response'));
nicholas@2498 3522 break;
nicholas@2498 3523 }
nicholas@2498 3524 for (var i = 0; i < node.response.length; i++) {
nicholas@2498 3525 var checkNode = this.parent.document.createElement('response');
nicholas@2498 3526 checkNode.setAttribute('name', node.response[i].name);
nicholas@2498 3527 checkNode.setAttribute('checked', node.response[i].checked);
nicholas@2498 3528 surveyresult.appendChild(checkNode);
nicholas@2498 3529 }
nicholas@2498 3530 break;
nicholas@2498 3531 }
nicholas@2498 3532 };
nicholas@2498 3533 this.complete = function () {
nicholas@2498 3534 this.state = "complete";
nicholas@2498 3535 this.XMLDOM.setAttribute("state", this.state);
nicholas@2498 3536 }
nicholas@2498 3537 };
nicholas@2498 3538
nicholas@2498 3539 this.pageNode = function (parent, specification) {
nicholas@2498 3540 // Create one store per test page
nicholas@2498 3541 this.specification = specification;
nicholas@2498 3542 this.parent = parent;
nicholas@2498 3543 this.state = "empty";
nicholas@2498 3544 this.XMLDOM = this.parent.document.createElement('page');
nicholas@2498 3545 this.XMLDOM.setAttribute('ref', specification.id);
nicholas@2498 3546 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
nicholas@2498 3547 this.XMLDOM.setAttribute("state", this.state);
nicholas@2498 3548 if (specification.preTest != undefined) {
nicholas@2498 3549 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
nicholas@2498 3550 }
nicholas@2498 3551 if (specification.postTest != undefined) {
nicholas@2498 3552 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
nicholas@2498 3553 }
nicholas@2498 3554
nicholas@2498 3555 // Add any page metrics
nicholas@2498 3556 var page_metric = this.parent.document.createElement('metric');
nicholas@2498 3557 this.XMLDOM.appendChild(page_metric);
nicholas@2498 3558
nicholas@2498 3559 // Add the audioelement
nicholas@2498 3560 for (var element of this.specification.audioElements) {
nicholas@2498 3561 var aeNode = this.parent.document.createElement('audioelement');
nicholas@2498 3562 aeNode.setAttribute('ref', element.id);
nicholas@2498 3563 if (element.name != undefined) {
nicholas@2498 3564 aeNode.setAttribute('name', element.name)
nicholas@2498 3565 };
nicholas@2498 3566 aeNode.setAttribute('type', element.type);
nicholas@2498 3567 aeNode.setAttribute('url', element.url);
nicholas@2498 3568 aeNode.setAttribute('fqurl', qualifyURL(element.url));
nicholas@2498 3569 aeNode.setAttribute('gain', element.gain);
nicholas@2498 3570 if (element.type == 'anchor' || element.type == 'reference') {
nicholas@2498 3571 if (element.marker > 0) {
nicholas@2498 3572 aeNode.setAttribute('marker', element.marker);
nicholas@2464 3573 }
nicholas@2498 3574 }
nicholas@2498 3575 var ae_metric = this.parent.document.createElement('metric');
nicholas@2498 3576 aeNode.appendChild(ae_metric);
nicholas@2498 3577 this.XMLDOM.appendChild(aeNode);
nicholas@2498 3578 }
nicholas@2498 3579
nicholas@2498 3580 this.parent.root.appendChild(this.XMLDOM);
nicholas@2498 3581
nicholas@2498 3582 this.complete = function () {
nicholas@2224 3583 this.state = "complete";
nicholas@2498 3584 this.XMLDOM.setAttribute("state", "complete");
nicholas@2224 3585 }
nicholas@2498 3586 };
nicholas@2498 3587 this.update = function () {
nicholas@2224 3588 this.SessionKey.update();
nicholas@2224 3589 }
nicholas@2498 3590 this.finish = function () {
nicholas@2498 3591 if (this.state == 0) {
nicholas@2224 3592 this.update();
nicholas@2498 3593 }
nicholas@2498 3594 this.state = 1;
nicholas@2498 3595 this.root.setAttribute("state", "complete");
nicholas@2498 3596 return this.root;
nicholas@2498 3597 };
nicholas@2224 3598 }
nicholas@2384 3599
nicholas@2401 3600 var window_depedancy_callback;
nicholas@2498 3601 window_depedancy_callback = window.setInterval(function () {
nicholas@2401 3602 if (check_dependancies()) {
nicholas@2401 3603 window.clearInterval(window_depedancy_callback);
nicholas@2401 3604 onload();
nicholas@2401 3605 } else {
nicholas@2401 3606 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
nicholas@2401 3607 }
nicholas@2498 3608 }, 100);