annotate js/core.js @ 2647:83ae42eab960

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