annotate js/core.js @ 2674:b9efbbe0d829

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