annotate js/core.js @ 2617:cce1fb4009f2

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