annotate js/core.js @ 2723:2e1cafe93c78

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