annotate js/core.js @ 2735:9d31ddedae2b

Minor corrections to the processing of conditional elements.
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Fri, 21 Apr 2017 11:21:51 +0100
parents f2bb15d5f5c9
children dfc2959fb7c6 2fc62a50d593
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@2733 392 var xmlDoc = storage.finish();
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@2735 599 if (jumpID !== null) {
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@2735 831 processConditional.call(this, node, node.response.name);
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@2730 876 if (node.specification.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@2730 885 if (enteredNumber < node.specification.min && node.specification.min !== null) {
nicholas@2730 886 interfaceContext.lightbox.post("Error", 'Number is below the minimum value of ' + node.specification.min);
nicholas@2708 887 return false;
nicholas@2708 888 }
nicholas@2730 889 if (enteredNumber > node.specification.max && node.specification.max !== null) {
nicholas@2730 890 interfaceContext.lightbox.post("Error", 'Number is above the maximum value of ' + node.specification.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@2725 2457 this.highlight = function (state) {
nicholas@2725 2458 if (state === true) {
nicholas@2725 2459 $(this.trackComment).addClass("comment-box-playing");
nicholas@2725 2460 } else {
nicholas@2725 2461 $(this.trackComment).removeClass("comment-box-playing");
nicholas@2725 2462 }
nicholas@2725 2463 };
nicholas@2224 2464 };
nicholas@2712 2465 commentBoxes.createCommentBox = function (audioObject) {
nicholas@2224 2466 var node = new this.elementCommentBox(audioObject);
nicholas@2224 2467 this.boxes.push(node);
nicholas@2224 2468 audioObject.commentDOM = node;
nicholas@2224 2469 return node;
nicholas@2224 2470 };
nicholas@2712 2471 commentBoxes.sortCommentBoxes = function () {
nicholas@2498 2472 this.boxes.sort(function (a, b) {
nicholas@2498 2473 return a.id - b.id;
nicholas@2498 2474 });
nicholas@2224 2475 };
nicholas@2224 2476
nicholas@2712 2477 commentBoxes.showCommentBoxes = function (inject, sort) {
nicholas@2224 2478 this.injectPoint = inject;
nicholas@2498 2479 if (sort) {
nicholas@2498 2480 this.sortCommentBoxes();
nicholas@2498 2481 }
nicholas@2708 2482 this.boxes.forEach(function (box) {
nicholas@2224 2483 inject.appendChild(box.trackComment);
nicholas@2708 2484 });
nicholas@2224 2485 };
nicholas@2224 2486
nicholas@2712 2487 commentBoxes.deleteCommentBoxes = function () {
nicholas@2708 2488 if (this.injectPoint !== null) {
nicholas@2708 2489 this.boxes.forEach(function (box) {
nicholas@2224 2490 this.injectPoint.removeChild(box.trackComment);
nicholas@2708 2491 }, this);
nicholas@2224 2492 this.injectPoint = null;
nicholas@2224 2493 }
nicholas@2224 2494 this.boxes = [];
nicholas@2224 2495 };
nicholas@2725 2496 commentBoxes.highlightById = function (id) {
nicholas@2725 2497 if (id === undefined || typeof id !== "number" || id >= this.boxes.length) {
nicholas@2725 2498 console.log("Error - Invalid id");
nicholas@2725 2499 id = -1;
nicholas@2725 2500 }
nicholas@2725 2501 this.boxes.forEach(function (a) {
nicholas@2725 2502 if (a.id === id) {
nicholas@2725 2503 a.highlight(true);
nicholas@2725 2504 } else {
nicholas@2725 2505 a.highlight(false);
nicholas@2725 2506 }
nicholas@2725 2507 });
nicholas@2725 2508 };
nicholas@2712 2509 return commentBoxes;
nicholas@2712 2510 })();
nicholas@2498 2511
nicholas@2498 2512 this.commentQuestions = [];
nicholas@2498 2513
nicholas@2498 2514 this.commentBox = function (commentQuestion) {
nicholas@2498 2515 this.specification = commentQuestion;
nicholas@2498 2516 // Create document objects to hold the comment boxes
nicholas@2498 2517 this.holder = document.createElement('div');
nicholas@2498 2518 this.holder.className = 'comment-div';
nicholas@2498 2519 // Create a string next to each comment asking for a comment
nicholas@2498 2520 this.string = document.createElement('span');
nicholas@2498 2521 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2522 // Create the HTML5 comment box 'textarea'
nicholas@2498 2523 this.textArea = document.createElement('textarea');
nicholas@2498 2524 this.textArea.rows = '4';
nicholas@2498 2525 this.textArea.cols = '100';
nicholas@2498 2526 this.textArea.className = 'trackComment';
nicholas@2498 2527 var br = document.createElement('br');
nicholas@2498 2528 // Add to the holder.
nicholas@2498 2529 this.holder.appendChild(this.string);
nicholas@2498 2530 this.holder.appendChild(br);
nicholas@2498 2531 this.holder.appendChild(this.textArea);
nicholas@2498 2532
nicholas@2498 2533 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2534 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2535 root.id = this.specification.id;
nicholas@2498 2536 root.setAttribute('type', this.specification.type);
nicholas@2498 2537 console.log("Question: " + this.string.textContent);
nicholas@2498 2538 console.log("Response: " + root.textContent);
nicholas@2224 2539 var question = storePoint.parent.document.createElement('question');
nicholas@2224 2540 question.textContent = this.string.textContent;
nicholas@2224 2541 var response = storePoint.parent.document.createElement('response');
nicholas@2224 2542 response.textContent = this.textArea.value;
nicholas@2224 2543 root.appendChild(question);
nicholas@2224 2544 root.appendChild(response);
nicholas@2224 2545 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2546 return root;
nicholas@2498 2547 };
nicholas@2498 2548 this.resize = function () {
nicholas@2498 2549 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2550 if (boxwidth >= 600) {
nicholas@2498 2551 boxwidth = 600;
nicholas@2498 2552 } else if (boxwidth < 400) {
nicholas@2498 2553 boxwidth = 400;
nicholas@2498 2554 }
nicholas@2498 2555 this.holder.style.width = boxwidth + "px";
nicholas@2498 2556 this.textArea.style.width = boxwidth - 6 + "px";
nicholas@2498 2557 };
nicholas@2498 2558 this.resize();
nicholas@2498 2559 };
nicholas@2498 2560
nicholas@2498 2561 this.radioBox = function (commentQuestion) {
nicholas@2498 2562 this.specification = commentQuestion;
nicholas@2498 2563 // Create document objects to hold the comment boxes
nicholas@2498 2564 this.holder = document.createElement('div');
nicholas@2498 2565 this.holder.className = 'comment-div';
nicholas@2498 2566 // Create a string next to each comment asking for a comment
nicholas@2498 2567 this.string = document.createElement('span');
nicholas@2498 2568 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2569 // Add to the holder.
nicholas@2498 2570 this.holder.appendChild(this.string);
nicholas@2498 2571 this.options = [];
nicholas@2498 2572 this.inputs = document.createElement('div');
nicholas@2711 2573 this.inputs.className = "comment-checkbox-inputs-holder";
nicholas@2498 2574
nicholas@2498 2575 var optCount = commentQuestion.options.length;
nicholas@2711 2576 for (var i = 0; i < optCount; i++) {
nicholas@2498 2577 var div = document.createElement('div');
nicholas@2711 2578 div.className = "comment-checkbox-inputs-flex";
nicholas@2722 2579
nicholas@2711 2580 var span = document.createElement('span');
nicholas@2711 2581 span.textContent = commentQuestion.options[i].text;
nicholas@2711 2582 span.className = 'comment-radio-span';
nicholas@2711 2583 div.appendChild(span);
nicholas@2722 2584
nicholas@2498 2585 var input = document.createElement('input');
nicholas@2498 2586 input.type = 'radio';
nicholas@2498 2587 input.name = commentQuestion.id;
nicholas@2711 2588 input.setAttribute('setvalue', commentQuestion.options[i].name);
nicholas@2498 2589 input.className = 'comment-radio';
nicholas@2498 2590 div.appendChild(input);
nicholas@2722 2591
nicholas@2498 2592 this.inputs.appendChild(div);
nicholas@2498 2593 this.options.push(input);
nicholas@2498 2594 }
nicholas@2498 2595 this.holder.appendChild(this.inputs);
nicholas@2498 2596
nicholas@2498 2597 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2598 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2599 root.id = this.specification.id;
nicholas@2498 2600 root.setAttribute('type', this.specification.type);
nicholas@2498 2601 var question = document.createElement('question');
nicholas@2498 2602 question.textContent = this.string.textContent;
nicholas@2498 2603 var response = document.createElement('response');
nicholas@2498 2604 var i = 0;
nicholas@2708 2605 while (this.options[i].checked === false) {
nicholas@2498 2606 i++;
nicholas@2498 2607 if (i >= this.options.length) {
nicholas@2498 2608 break;
nicholas@2498 2609 }
nicholas@2498 2610 }
nicholas@2498 2611 if (i >= this.options.length) {
nicholas@2498 2612 response.textContent = 'null';
nicholas@2498 2613 } else {
nicholas@2498 2614 response.textContent = this.options[i].getAttribute('setvalue');
nicholas@2498 2615 response.setAttribute('number', i);
nicholas@2498 2616 }
nicholas@2498 2617 console.log('Comment: ' + question.textContent);
nicholas@2498 2618 console.log('Response: ' + response.textContent);
nicholas@2498 2619 root.appendChild(question);
nicholas@2498 2620 root.appendChild(response);
nicholas@2224 2621 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2622 return root;
nicholas@2498 2623 };
nicholas@2498 2624 this.resize = function () {
nicholas@2498 2625 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2626 if (boxwidth >= 600) {
nicholas@2498 2627 boxwidth = 600;
nicholas@2498 2628 } else if (boxwidth < 400) {
nicholas@2498 2629 boxwidth = 400;
nicholas@2498 2630 }
nicholas@2498 2631 this.holder.style.width = boxwidth + "px";
nicholas@2498 2632 };
nicholas@2498 2633 this.resize();
nicholas@2498 2634 };
nicholas@2498 2635
nicholas@2498 2636 this.checkboxBox = function (commentQuestion) {
nicholas@2498 2637 this.specification = commentQuestion;
nicholas@2498 2638 // Create document objects to hold the comment boxes
nicholas@2498 2639 this.holder = document.createElement('div');
nicholas@2498 2640 this.holder.className = 'comment-div';
nicholas@2498 2641 // Create a string next to each comment asking for a comment
nicholas@2498 2642 this.string = document.createElement('span');
nicholas@2498 2643 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2644 // Add to the holder.
nicholas@2498 2645 this.holder.appendChild(this.string);
nicholas@2498 2646 this.options = [];
nicholas@2498 2647 this.inputs = document.createElement('div');
nicholas@2294 2648 this.inputs.className = "comment-checkbox-inputs-holder";
nicholas@2498 2649
nicholas@2498 2650 var optCount = commentQuestion.options.length;
nicholas@2498 2651 for (var i = 0; i < optCount; i++) {
nicholas@2498 2652 var div = document.createElement('div');
nicholas@2711 2653 div.className = "comment-checkbox-inputs-flex";
nicholas@2722 2654
nicholas@2711 2655 var span = document.createElement('span');
nicholas@2711 2656 span.textContent = commentQuestion.options[i].text;
nicholas@2711 2657 span.className = 'comment-radio-span';
nicholas@2711 2658 div.appendChild(span);
nicholas@2722 2659
nicholas@2498 2660 var input = document.createElement('input');
nicholas@2498 2661 input.type = 'checkbox';
nicholas@2498 2662 input.name = commentQuestion.id;
nicholas@2498 2663 input.setAttribute('setvalue', commentQuestion.options[i].name);
nicholas@2498 2664 input.className = 'comment-radio';
nicholas@2498 2665 div.appendChild(input);
nicholas@2722 2666
nicholas@2498 2667 this.inputs.appendChild(div);
nicholas@2498 2668 this.options.push(input);
nicholas@2498 2669 }
nicholas@2498 2670 this.holder.appendChild(this.inputs);
nicholas@2498 2671
nicholas@2498 2672 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2673 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2674 root.id = this.specification.id;
nicholas@2498 2675 root.setAttribute('type', this.specification.type);
nicholas@2498 2676 var question = document.createElement('question');
nicholas@2498 2677 question.textContent = this.string.textContent;
nicholas@2498 2678 root.appendChild(question);
nicholas@2498 2679 console.log('Comment: ' + question.textContent);
nicholas@2498 2680 for (var i = 0; i < this.options.length; i++) {
nicholas@2498 2681 var response = document.createElement('response');
nicholas@2498 2682 response.textContent = this.options[i].checked;
nicholas@2498 2683 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
nicholas@2498 2684 root.appendChild(response);
nicholas@2498 2685 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
nicholas@2498 2686 }
nicholas@2224 2687 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2688 return root;
nicholas@2498 2689 };
nicholas@2498 2690 this.resize = function () {
nicholas@2498 2691 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2692 if (boxwidth >= 600) {
nicholas@2498 2693 boxwidth = 600;
nicholas@2498 2694 } else if (boxwidth < 400) {
nicholas@2498 2695 boxwidth = 400;
nicholas@2498 2696 }
nicholas@2498 2697 this.holder.style.width = boxwidth + "px";
nicholas@2498 2698 };
nicholas@2498 2699 this.resize();
nicholas@2498 2700 };
nicholas@2498 2701
n@2579 2702 this.sliderBox = function (commentQuestion) {
n@2579 2703 this.specification = commentQuestion;
n@2579 2704 this.holder = document.createElement("div");
n@2579 2705 this.holder.className = 'comment-div';
n@2579 2706 this.string = document.createElement("span");
n@2579 2707 this.string.innerHTML = commentQuestion.statement;
n@2579 2708 this.slider = document.createElement("input");
n@2579 2709 this.slider.type = "range";
n@2579 2710 this.slider.min = commentQuestion.min;
n@2579 2711 this.slider.max = commentQuestion.max;
n@2579 2712 this.slider.step = commentQuestion.step;
n@2579 2713 this.slider.value = commentQuestion.value;
n@2579 2714 var br = document.createElement('br');
n@2579 2715
n@2580 2716 var textHolder = document.createElement("div");
n@2580 2717 textHolder.className = "comment-slider-text-holder";
n@2580 2718
n@2580 2719 this.leftText = document.createElement("span");
n@2580 2720 this.leftText.textContent = commentQuestion.leftText;
n@2580 2721 this.rightText = document.createElement("span");
n@2580 2722 this.rightText.textContent = commentQuestion.rightText;
n@2580 2723 textHolder.appendChild(this.leftText);
n@2580 2724 textHolder.appendChild(this.rightText);
n@2580 2725
n@2579 2726 this.holder.appendChild(this.string);
n@2579 2727 this.holder.appendChild(br);
n@2579 2728 this.holder.appendChild(this.slider);
n@2580 2729 this.holder.appendChild(textHolder);
n@2579 2730
n@2579 2731 this.exportXMLDOM = function (storePoint) {
n@2579 2732 var root = storePoint.parent.document.createElement('comment');
n@2579 2733 root.id = this.specification.id;
n@2579 2734 root.setAttribute('type', this.specification.type);
n@2579 2735 console.log("Question: " + this.string.textContent);
n@2579 2736 console.log("Response: " + this.slider.value);
n@2579 2737 var question = storePoint.parent.document.createElement('question');
n@2579 2738 question.textContent = this.string.textContent;
n@2579 2739 var response = storePoint.parent.document.createElement('response');
n@2579 2740 response.textContent = this.slider.value;
n@2579 2741 root.appendChild(question);
n@2579 2742 root.appendChild(response);
n@2579 2743 storePoint.XMLDOM.appendChild(root);
n@2579 2744 return root;
n@2579 2745 };
n@2579 2746 this.resize = function () {
n@2579 2747 var boxwidth = (window.innerWidth - 100) / 2;
n@2579 2748 if (boxwidth >= 600) {
n@2579 2749 boxwidth = 600;
n@2579 2750 } else if (boxwidth < 400) {
n@2579 2751 boxwidth = 400;
n@2579 2752 }
n@2579 2753 this.holder.style.width = boxwidth + "px";
n@2579 2754 this.slider.style.width = boxwidth - 24 + "px";
n@2579 2755 };
n@2579 2756 this.resize();
n@2579 2757 };
n@2579 2758
nicholas@2498 2759 this.createCommentQuestion = function (element) {
nicholas@2498 2760 var node;
nicholas@2498 2761 if (element.type == 'question') {
nicholas@2498 2762 node = new this.commentBox(element);
nicholas@2498 2763 } else if (element.type == 'radio') {
nicholas@2498 2764 node = new this.radioBox(element);
nicholas@2498 2765 } else if (element.type == 'checkbox') {
nicholas@2498 2766 node = new this.checkboxBox(element);
n@2579 2767 } else if (element.type == 'slider') {
n@2579 2768 node = new this.sliderBox(element);
nicholas@2498 2769 }
nicholas@2498 2770 this.commentQuestions.push(node);
nicholas@2498 2771 return node;
nicholas@2498 2772 };
nicholas@2498 2773
nicholas@2498 2774 this.deleteCommentQuestions = function () {
nicholas@2498 2775 this.commentQuestions = [];
nicholas@2498 2776 };
nicholas@2498 2777
nicholas@2498 2778 this.outsideReferenceDOM = function (audioObject, index, inject) {
nicholas@2224 2779 this.parent = audioObject;
nicholas@2224 2780 this.outsideReferenceHolder = document.createElement('button');
nicholas@2224 2781 this.outsideReferenceHolder.className = 'outside-reference';
nicholas@2498 2782 this.outsideReferenceHolder.setAttribute('track-id', index);
nicholas@2409 2783 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
nicholas@2224 2784 this.outsideReferenceHolder.disabled = true;
nicholas@2708 2785 this.handleEvent = function (event) {
nicholas@2708 2786 audioEngineContext.play(this.parent.id);
nicholas@2224 2787 };
nicholas@2708 2788 this.outsideReferenceHolder.addEventListener("click", this);
nicholas@2224 2789 inject.appendChild(this.outsideReferenceHolder);
nicholas@2498 2790 this.enable = function () {
nicholas@2498 2791 if (this.parent.state == 1) {
nicholas@2224 2792 this.outsideReferenceHolder.disabled = false;
nicholas@2224 2793 }
nicholas@2224 2794 };
nicholas@2498 2795 this.updateLoading = function (progress) {
nicholas@2498 2796 if (progress != 100) {
nicholas@2224 2797 progress = String(progress);
nicholas@2224 2798 progress = progress.split('.')[0];
nicholas@2498 2799 this.outsideReferenceHolder.textContent = progress + '%';
nicholas@2224 2800 } else {
nicholas@2409 2801 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
nicholas@2224 2802 }
nicholas@2224 2803 };
nicholas@2498 2804 this.startPlayback = function () {
nicholas@2224 2805 // Called when playback has begun
nicholas@2224 2806 $('.track-slider').removeClass('track-slider-playing');
nicholas@2224 2807 $('.comment-div').removeClass('comment-box-playing');
nicholas@2224 2808 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
nicholas@2224 2809 };
nicholas@2498 2810 this.stopPlayback = function () {
nicholas@2224 2811 // Called when playback has stopped. This gets called even if playback never started!
nicholas@2224 2812 this.outsideReferenceHolder.style.backgroundColor = "";
nicholas@2224 2813 };
nicholas@2498 2814 this.exportXMLDOM = function (audioObject) {
nicholas@2224 2815 return null;
nicholas@2224 2816 };
nicholas@2498 2817 this.getValue = function () {
nicholas@2224 2818 return 0;
nicholas@2224 2819 };
nicholas@2498 2820 this.getPresentedId = function () {
nicholas@2409 2821 return this.parent.specification.label || "Reference";
nicholas@2224 2822 };
nicholas@2498 2823 this.canMove = function () {
nicholas@2224 2824 return false;
nicholas@2224 2825 };
nicholas@2498 2826 this.error = function () {
nicholas@2498 2827 // audioObject has an error!!
nicholas@2224 2828 this.outsideReferenceHolder.textContent = "Error";
nicholas@2224 2829 this.outsideReferenceHolder.style.backgroundColor = "#F00";
nicholas@2708 2830 };
nicholas@2708 2831 };
nicholas@2498 2832
nicholas@2712 2833 this.playhead = (function () {
nicholas@2722 2834 var playhead = {};
nicholas@2712 2835 playhead.object = document.createElement('div');
nicholas@2712 2836 playhead.object.className = 'playhead';
nicholas@2712 2837 playhead.object.align = 'left';
nicholas@2498 2838 var curTime = document.createElement('div');
nicholas@2498 2839 curTime.style.width = '50px';
nicholas@2712 2840 playhead.curTimeSpan = document.createElement('span');
nicholas@2712 2841 playhead.curTimeSpan.textContent = '00:00';
nicholas@2712 2842 curTime.appendChild(playhead.curTimeSpan);
nicholas@2712 2843 playhead.object.appendChild(curTime);
nicholas@2712 2844 playhead.scrubberTrack = document.createElement('div');
nicholas@2712 2845 playhead.scrubberTrack.className = 'playhead-scrub-track';
nicholas@2498 2846
nicholas@2712 2847 playhead.scrubberHead = document.createElement('div');
nicholas@2712 2848 playhead.scrubberHead.id = 'playhead-scrubber';
nicholas@2712 2849 playhead.scrubberTrack.appendChild(playhead.scrubberHead);
nicholas@2712 2850 playhead.object.appendChild(playhead.scrubberTrack);
nicholas@2498 2851
nicholas@2712 2852 playhead.timePerPixel = 0;
nicholas@2712 2853 playhead.maxTime = 0;
nicholas@2498 2854
nicholas@2712 2855 playhead.playbackObject = undefined;
nicholas@2498 2856
nicholas@2712 2857 playhead.setTimePerPixel = function (audioObject) {
nicholas@2498 2858 //maxTime must be in seconds
nicholas@2498 2859 this.playbackObject = audioObject;
nicholas@2498 2860 this.maxTime = audioObject.buffer.buffer.duration;
nicholas@2498 2861 var width = 490; //500 - 10, 5 each side of the tracker head
nicholas@2498 2862 this.timePerPixel = this.maxTime / 490;
nicholas@2498 2863 if (this.maxTime < 60) {
nicholas@2498 2864 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2865 } else {
nicholas@2498 2866 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2867 }
nicholas@2498 2868 };
nicholas@2498 2869
nicholas@2712 2870 playhead.update = function () {
nicholas@2498 2871 // Update the playhead position, startPlay must be called
nicholas@2498 2872 if (this.timePerPixel > 0) {
nicholas@2498 2873 var time = this.playbackObject.getCurrentPosition();
nicholas@2498 2874 if (time > 0 && time < this.maxTime) {
nicholas@2498 2875 var width = 490;
nicholas@2498 2876 var pix = Math.floor(time / this.timePerPixel);
nicholas@2498 2877 this.scrubberHead.style.left = pix + 'px';
nicholas@2498 2878 if (this.maxTime > 60.0) {
nicholas@2498 2879 var secs = time % 60;
nicholas@2498 2880 var mins = Math.floor((time - secs) / 60);
nicholas@2498 2881 secs = secs.toString();
nicholas@2498 2882 secs = secs.substr(0, 2);
nicholas@2498 2883 mins = mins.toString();
nicholas@2498 2884 this.curTimeSpan.textContent = mins + ':' + secs;
nicholas@2498 2885 } else {
nicholas@2498 2886 time = time.toString();
nicholas@2498 2887 this.curTimeSpan.textContent = time.substr(0, 4);
nicholas@2498 2888 }
nicholas@2498 2889 } else {
nicholas@2498 2890 this.scrubberHead.style.left = '0px';
nicholas@2498 2891 if (this.maxTime < 60) {
nicholas@2498 2892 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2893 } else {
nicholas@2498 2894 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2895 }
nicholas@2498 2896 }
nicholas@2498 2897 }
nicholas@2498 2898 };
nicholas@2498 2899
nicholas@2712 2900 playhead.interval = undefined;
nicholas@2498 2901
nicholas@2712 2902 playhead.start = function () {
nicholas@2708 2903 if (this.playbackObject !== undefined && this.interval === undefined) {
nicholas@2498 2904 if (this.maxTime < 60) {
nicholas@2708 2905 this.interval = window.setInterval(function () {
nicholas@2498 2906 interfaceContext.playhead.update();
nicholas@2498 2907 }, 10);
nicholas@2498 2908 } else {
nicholas@2708 2909 this.interval = window.setInterval(function () {
nicholas@2498 2910 interfaceContext.playhead.update();
nicholas@2498 2911 }, 100);
nicholas@2498 2912 }
nicholas@2498 2913 }
nicholas@2498 2914 };
nicholas@2712 2915 playhead.stop = function () {
nicholas@2708 2916 window.clearInterval(this.interval);
nicholas@2498 2917 this.interval = undefined;
nicholas@2224 2918 this.scrubberHead.style.left = '0px';
nicholas@2498 2919 if (this.maxTime < 60) {
nicholas@2498 2920 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2921 } else {
nicholas@2498 2922 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2923 }
nicholas@2498 2924 };
nicholas@2712 2925 return playhead;
nicholas@2712 2926 })();
nicholas@2498 2927
nicholas@2712 2928 this.volume = (function () {
nicholas@2224 2929 // An in-built volume module which can be viewed on page
nicholas@2224 2930 // Includes trackers on page-by-page data
nicholas@2224 2931 // Volume does NOT reset to 0dB on each page load
nicholas@2712 2932 var volume = {};
nicholas@2712 2933 volume.valueLin = 1.0;
nicholas@2712 2934 volume.valueDB = 0.0;
nicholas@2712 2935 volume.root = document.createElement('div');
nicholas@2712 2936 volume.root.id = 'master-volume-root';
nicholas@2712 2937 volume.object = document.createElement('div');
nicholas@2712 2938 volume.object.className = 'master-volume-holder-float';
nicholas@2712 2939 volume.object.appendChild(volume.root);
nicholas@2712 2940 volume.slider = document.createElement('input');
nicholas@2712 2941 volume.slider.id = 'master-volume-control';
nicholas@2712 2942 volume.slider.type = 'range';
nicholas@2712 2943 volume.valueText = document.createElement('span');
nicholas@2712 2944 volume.valueText.id = 'master-volume-feedback';
nicholas@2712 2945 volume.valueText.textContent = '0dB';
nicholas@2498 2946
nicholas@2712 2947 volume.slider.min = -60;
nicholas@2712 2948 volume.slider.max = 12;
nicholas@2712 2949 volume.slider.value = 0;
nicholas@2712 2950 volume.slider.step = 1;
nicholas@2712 2951 volume.handleEvent = function (event) {
nicholas@2669 2952 if (event.type == "mousemove") {
nicholas@2669 2953 this.valueDB = Number(this.slider.value);
nicholas@2669 2954 this.valueLin = decibelToLinear(this.valueDB);
nicholas@2669 2955 this.valueText.textContent = this.valueDB + 'dB';
nicholas@2669 2956 audioEngineContext.outputGain.gain.value = this.valueLin;
nicholas@2669 2957 } else if (event.type == "mouseup") {
nicholas@2669 2958 this.onmouseup();
nicholas@2669 2959 }
nicholas@2669 2960 this.slider.value = this.valueDB;
nicholas@2669 2961
nicholas@2669 2962 if (event.stopPropagation) {
nicholas@2669 2963 event.stopPropagation();
nicholas@2669 2964 }
nicholas@2711 2965 };
nicholas@2712 2966 volume.onmouseup = function () {
nicholas@2224 2967 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
nicholas@2708 2968 if (storePoint.length === 0) {
nicholas@2224 2969 storePoint = storage.document.createElement('metricresult');
nicholas@2498 2970 storePoint.setAttribute('name', 'volumeTracker');
nicholas@2224 2971 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
nicholas@2498 2972 } else {
nicholas@2224 2973 storePoint = storePoint[0];
nicholas@2224 2974 }
nicholas@2224 2975 var node = storage.document.createElement('movement');
nicholas@2498 2976 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
nicholas@2669 2977 node.setAttribute('volume', this.valueDB);
nicholas@2498 2978 node.setAttribute('format', 'dBFS');
nicholas@2224 2979 storePoint.appendChild(node);
nicholas@2711 2980 };
nicholas@2712 2981 volume.slider.addEventListener("mousemove", volume);
nicholas@2712 2982 volume.root.addEventListener("mouseup", volume);
nicholas@2498 2983
nicholas@2224 2984 var title = document.createElement('div');
nicholas@2224 2985 title.innerHTML = '<span>Master Volume Control</span>';
nicholas@2224 2986 title.style.fontSize = '0.75em';
nicholas@2224 2987 title.style.width = "100%";
nicholas@2224 2988 title.align = 'center';
nicholas@2712 2989 volume.root.appendChild(title);
nicholas@2498 2990
nicholas@2712 2991 volume.root.appendChild(volume.slider);
nicholas@2712 2992 volume.root.appendChild(volume.valueText);
nicholas@2498 2993
nicholas@2712 2994 volume.resize = function (event) {
nicholas@2352 2995 if (window.innerWidth < 1000) {
nicholas@2708 2996 this.object.className = "master-volume-holder-inline";
nicholas@2352 2997 } else {
nicholas@2352 2998 this.object.className = 'master-volume-holder-float';
nicholas@2352 2999 }
nicholas@2708 3000 };
nicholas@2712 3001 return volume;
nicholas@2712 3002 })();
nicholas@2498 3003
nicholas@2224 3004 this.calibrationModuleObject = null;
nicholas@2498 3005 this.calibrationModule = function () {
nicholas@2224 3006 // This creates an on-page calibration module
nicholas@2224 3007 this.storeDOM = storage.document.createElement("calibration");
nicholas@2224 3008 storage.root.appendChild(this.storeDOM);
nicholas@2224 3009 // The calibration is a fixed state module
nicholas@2224 3010 this.calibrationNodes = [];
nicholas@2224 3011 this.holder = null;
nicholas@2498 3012 this.build = function (inject) {
nicholas@2224 3013 var f0 = 62.5;
nicholas@2224 3014 this.holder = document.createElement("div");
nicholas@2224 3015 this.holder.className = "calibration-holder";
nicholas@2224 3016 this.calibrationNodes = [];
nicholas@2498 3017 while (f0 < 20000) {
nicholas@2712 3018 /* jshint loopfunc: true */
nicholas@2224 3019 var obj = {
nicholas@2224 3020 root: document.createElement("div"),
nicholas@2224 3021 input: document.createElement("input"),
nicholas@2224 3022 oscillator: audioContext.createOscillator(),
nicholas@2224 3023 gain: audioContext.createGain(),
nicholas@2224 3024 f: f0,
nicholas@2224 3025 parent: this,
nicholas@2498 3026 handleEvent: function (event) {
nicholas@2498 3027 switch (event.type) {
nicholas@2224 3028 case "mouseenter":
nicholas@2224 3029 this.oscillator.start(0);
nicholas@2224 3030 break;
nicholas@2224 3031 case "mouseleave":
nicholas@2224 3032 this.oscillator.stop(0);
nicholas@2224 3033 this.oscillator = audioContext.createOscillator();
nicholas@2224 3034 this.oscillator.connect(this.gain);
nicholas@2224 3035 this.oscillator.frequency.value = this.f;
nicholas@2224 3036 break;
nicholas@2224 3037 case "mousemove":
nicholas@2498 3038 var value = Math.pow(10, this.input.value / 20);
nicholas@2224 3039 if (this.f == 1000) {
nicholas@2224 3040 audioEngineContext.outputGain.gain.value = value;
nicholas@2224 3041 interfaceContext.volume.slider.value = this.input.value;
nicholas@2224 3042 } else {
nicholas@2708 3043 this.gain.gain.value = value;
nicholas@2224 3044 }
nicholas@2224 3045 break;
nicholas@2224 3046 }
nicholas@2224 3047 },
nicholas@2498 3048 disconnect: function () {
nicholas@2224 3049 this.gain.disconnect();
nicholas@2224 3050 }
nicholas@2708 3051 };
nicholas@2224 3052 obj.root.className = "calibration-slider";
nicholas@2224 3053 obj.root.appendChild(obj.input);
nicholas@2224 3054 obj.oscillator.connect(obj.gain);
nicholas@2224 3055 obj.gain.connect(audioEngineContext.outputGain);
nicholas@2498 3056 obj.gain.gain.value = Math.random() * 2;
nicholas@2224 3057 obj.input.value = obj.gain.gain.value;
nicholas@2498 3058 obj.input.setAttribute('orient', 'vertical');
nicholas@2224 3059 obj.input.type = "range";
nicholas@2593 3060 obj.input.min = -12;
nicholas@2593 3061 obj.input.max = 0;
nicholas@2224 3062 obj.input.step = 0.25;
nicholas@2224 3063 if (f0 != 1000) {
nicholas@2498 3064 obj.input.value = (Math.random() * 12) - 6;
nicholas@2224 3065 } else {
nicholas@2224 3066 obj.input.value = 0;
nicholas@2498 3067 obj.root.style.backgroundColor = "rgb(255,125,125)";
nicholas@2224 3068 }
nicholas@2498 3069 obj.input.addEventListener("mousemove", obj);
nicholas@2498 3070 obj.input.addEventListener("mouseenter", obj);
nicholas@2498 3071 obj.input.addEventListener("mouseleave", obj);
nicholas@2498 3072 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
nicholas@2224 3073 obj.oscillator.frequency.value = f0;
nicholas@2224 3074 this.calibrationNodes.push(obj);
nicholas@2224 3075 this.holder.appendChild(obj.root);
nicholas@2224 3076 f0 *= 2;
nicholas@2224 3077 }
nicholas@2224 3078 inject.appendChild(this.holder);
nicholas@2708 3079 };
nicholas@2498 3080 this.collect = function () {
nicholas@2708 3081 this.calibrationNodes.forEach(function (obj) {
nicholas@2224 3082 var node = storage.document.createElement("calibrationresult");
nicholas@2498 3083 node.setAttribute("frequency", obj.f);
nicholas@2498 3084 node.setAttribute("range-min", obj.input.min);
nicholas@2498 3085 node.setAttribute("range-max", obj.input.max);
nicholas@2498 3086 node.setAttribute("gain-lin", obj.gain.gain.value);
nicholas@2224 3087 this.storeDOM.appendChild(node);
nicholas@2708 3088 }, this);
nicholas@2708 3089 };
nicholas@2708 3090 };
nicholas@2498 3091
nicholas@2498 3092
nicholas@2498 3093 // Global Checkers
nicholas@2498 3094 // These functions will help enforce the checkers
nicholas@2498 3095 this.checkHiddenAnchor = function () {
nicholas@2708 3096 var anchors = audioEngineContext.audioObjects.filter(function (ao) {
nicholas@2708 3097 return ao.specification.type === "anchor";
nicholas@2708 3098 });
nicholas@2708 3099 var state = anchors.some(function (ao) {
nicholas@2708 3100 return (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0);
nicholas@2708 3101 });
nicholas@2708 3102 if (state) {
nicholas@2708 3103 console.log('Anchor node not below marker value');
nicholas@2708 3104 interfaceContext.lightbox.post("Message", 'Please keep listening');
nicholas@2708 3105 this.storeErrorNode('Anchor node not below marker value');
nicholas@2708 3106 return false;
nicholas@2498 3107 }
nicholas@2498 3108 return true;
nicholas@2498 3109 };
nicholas@2498 3110
nicholas@2498 3111 this.checkHiddenReference = function () {
nicholas@2708 3112 var references = audioEngineContext.audioObjects.filter(function (ao) {
nicholas@2708 3113 return ao.specification.type === "reference";
nicholas@2708 3114 });
nicholas@2708 3115 var state = references.some(function (ao) {
nicholas@2708 3116 return (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0);
nicholas@2708 3117 });
nicholas@2708 3118 if (state) {
nicholas@2708 3119 console.log('Reference node not below marker value');
nicholas@2708 3120 interfaceContext.lightbox.post("Message", 'Please keep listening');
nicholas@2708 3121 this.storeErrorNode('Reference node not below marker value');
nicholas@2708 3122 return false;
nicholas@2498 3123 }
nicholas@2498 3124 return true;
nicholas@2498 3125 };
nicholas@2498 3126
nicholas@2498 3127 this.checkFragmentsFullyPlayed = function () {
nicholas@2498 3128 // Checks the entire file has been played back
nicholas@2498 3129 // NOTE ! This will return true IF playback is Looped!!!
nicholas@2498 3130 if (audioEngineContext.loopPlayback) {
nicholas@2498 3131 console.log("WARNING - Looped source: Cannot check fragments are fully played");
nicholas@2498 3132 return true;
nicholas@2498 3133 }
nicholas@2498 3134 var check_pass = true;
nicholas@2708 3135 var error_obj = [],
nicholas@2708 3136 i;
nicholas@2708 3137 for (i = 0; i < audioEngineContext.audioObjects.length; i++) {
nicholas@2498 3138 var object = audioEngineContext.audioObjects[i];
nicholas@2498 3139 var time = object.buffer.buffer.duration;
nicholas@2498 3140 var metric = object.metric;
nicholas@2498 3141 var passed = false;
nicholas@2498 3142 for (var j = 0; j < metric.listenTracker.length; j++) {
nicholas@2498 3143 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
nicholas@2498 3144 var start_time = Number(bt[0].getAttribute('start'));
nicholas@2498 3145 var stop_time = Number(bt[0].getAttribute('stop'));
nicholas@2498 3146 var delta = stop_time - start_time;
nicholas@2498 3147 if (delta >= time) {
nicholas@2498 3148 passed = true;
nicholas@2498 3149 break;
nicholas@2498 3150 }
nicholas@2498 3151 }
nicholas@2708 3152 if (passed === false) {
nicholas@2498 3153 check_pass = false;
nicholas@2498 3154 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
nicholas@2498 3155 error_obj.push(object.interfaceDOM.getPresentedId());
nicholas@2498 3156 }
nicholas@2498 3157 }
nicholas@2708 3158 if (check_pass === false) {
nicholas@2498 3159 var str_start = "You have not completely listened to fragments ";
nicholas@2708 3160 for (i = 0; i < error_obj.length; i++) {
nicholas@2498 3161 str_start += error_obj[i];
nicholas@2498 3162 if (i != error_obj.length - 1) {
nicholas@2498 3163 str_start += ', ';
nicholas@2498 3164 }
nicholas@2498 3165 }
nicholas@2498 3166 str_start += ". Please keep listening";
nicholas@2498 3167 console.log("[ALERT]: " + str_start);
nicholas@2498 3168 this.storeErrorNode("[ALERT]: " + str_start);
nicholas@2498 3169 interfaceContext.lightbox.post("Error", str_start);
nicholas@2444 3170 return false;
nicholas@2498 3171 }
nicholas@2444 3172 return true;
nicholas@2498 3173 };
nicholas@2498 3174 this.checkAllMoved = function () {
nicholas@2498 3175 var str = "You have not moved ";
nicholas@2498 3176 var failed = [];
nicholas@2708 3177 audioEngineContext.audioObjects.forEach(function (ao) {
nicholas@2708 3178 if (ao.metric.wasMoved === false && ao.interfaceDOM.canMove() === true) {
nicholas@2498 3179 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@2498 3180 }
nicholas@2708 3181 }, this);
nicholas@2708 3182 if (failed.length === 0) {
nicholas@2498 3183 return true;
nicholas@2498 3184 } else if (failed.length == 1) {
nicholas@2498 3185 str += 'track ' + failed[0];
nicholas@2498 3186 } else {
nicholas@2498 3187 str += 'tracks ';
nicholas@2498 3188 for (var i = 0; i < failed.length - 1; i++) {
nicholas@2498 3189 str += failed[i] + ', ';
nicholas@2498 3190 }
nicholas@2498 3191 str += 'and ' + failed[i];
nicholas@2498 3192 }
nicholas@2498 3193 str += '.';
nicholas@2498 3194 interfaceContext.lightbox.post("Error", str);
nicholas@2498 3195 console.log(str);
nicholas@2224 3196 this.storeErrorNode(str);
nicholas@2498 3197 return false;
nicholas@2498 3198 };
nicholas@2498 3199 this.checkAllPlayed = function () {
nicholas@2498 3200 var str = "You have not played ";
nicholas@2498 3201 var failed = [];
nicholas@2708 3202 audioEngineContext.audioObjects.forEach(function (ao) {
nicholas@2708 3203 if (ao.metric.wasListenedTo === false) {
nicholas@2498 3204 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@2498 3205 }
nicholas@2708 3206 }, this);
nicholas@2708 3207 if (failed.length === 0) {
nicholas@2498 3208 return true;
nicholas@2498 3209 } else if (failed.length == 1) {
nicholas@2498 3210 str += 'track ' + failed[0];
nicholas@2498 3211 } else {
nicholas@2498 3212 str += 'tracks ';
nicholas@2498 3213 for (var i = 0; i < failed.length - 1; i++) {
nicholas@2498 3214 str += failed[i] + ', ';
nicholas@2498 3215 }
nicholas@2498 3216 str += 'and ' + failed[i];
nicholas@2498 3217 }
nicholas@2498 3218 str += '.';
nicholas@2498 3219 interfaceContext.lightbox.post("Error", str);
nicholas@2498 3220 console.log(str);
nicholas@2224 3221 this.storeErrorNode(str);
nicholas@2498 3222 return false;
nicholas@2498 3223 };
nicholas@2540 3224 this.checkAllCommented = function () {
nicholas@2540 3225 var str = "You have not commented on all the fragments.";
nicholas@2540 3226 var cont = true,
nicholas@2540 3227 boxes = this.commentBoxes.boxes,
nicholas@2540 3228 numBoxes = boxes.length,
nicholas@2540 3229 i;
nicholas@2540 3230 for (i = 0; i < numBoxes; i++) {
nicholas@2540 3231 if (boxes[i].trackCommentBox.value === "") {
nicholas@2540 3232 interfaceContext.lightbox.post("Error", str);
nicholas@2540 3233 console.log(str);
nicholas@2540 3234 this.storeErrorNode(str);
nicholas@2540 3235 return false;
nicholas@2540 3236 }
nicholas@2540 3237 }
nicholas@2540 3238 return true;
nicholas@2708 3239 };
nicholas@2708 3240 this.checkScaleRange = function () {
nicholas@2310 3241 var page = testState.getCurrentTestPage();
nicholas@2708 3242 var interfaceObject = page.interfaces;
nicholas@2310 3243 var state = true;
nicholas@2310 3244 var str = "Please keep listening. ";
nicholas@2708 3245 if (interfaceObject === undefined) {
nicholas@2708 3246 return true;
nicholas@2310 3247 }
nicholas@2708 3248 interfaceObject = interfaceObject[0];
nicholas@2708 3249 var scales = (function () {
nicholas@2708 3250 var scaleRange = interfaceObject.options.find(function (a) {
nicholas@2708 3251 return a.name == "scalerange";
nicholas@2708 3252 });
nicholas@2708 3253 return {
nicholas@2708 3254 min: scaleRange.min,
nicholas@2708 3255 max: scaleRange.max
nicholas@2708 3256 };
nicholas@2708 3257 })();
nicholas@2708 3258 var range = audioEngineContext.audioObjects.reduce(function (a, b) {
nicholas@2708 3259 var v = b.interfaceDOM.getValue();
nicholas@2708 3260 return {
nicholas@2708 3261 min: Math.min(a.min, v),
nicholas@2708 3262 max: Math.max(a.max, v)
nicholas@2712 3263 };
nicholas@2708 3264 }, {
nicholas@2708 3265 min: 100,
nicholas@2708 3266 max: 0
nicholas@2708 3267 });
nicholas@2708 3268 if (range.min > scales.min) {
nicholas@2708 3269 str += "At least one fragment must be below the " + range.min + " mark.";
nicholas@2708 3270 state = false;
nicholas@2712 3271 } else if (range.max < scales.max) {
nicholas@2708 3272 str += "At least one fragment must be above the " + range.max + " mark.";
nicholas@2310 3273 state = false;
nicholas@2310 3274 }
nicholas@2708 3275 if (state === false) {
nicholas@2310 3276 console.log(str);
nicholas@2310 3277 this.storeErrorNode(str);
nicholas@2498 3278 interfaceContext.lightbox.post("Error", str);
nicholas@2310 3279 }
nicholas@2310 3280 return state;
nicholas@2708 3281 };
nicholas@2498 3282
nicholas@2498 3283 this.storeErrorNode = function (errorMessage) {
nicholas@2224 3284 var time = audioEngineContext.timer.getTestTime();
nicholas@2224 3285 var node = storage.document.createElement('error');
nicholas@2498 3286 node.setAttribute('time', time);
nicholas@2224 3287 node.textContent = errorMessage;
nicholas@2224 3288 testState.currentStore.XMLDOM.appendChild(node);
nicholas@2224 3289 };
nicholas@2595 3290
nicholas@2595 3291 this.getLabel = function (labelType, index, labelStart) {
nicholas@2595 3292 /*
nicholas@2595 3293 Get the correct label based on type, index and offset
nicholas@2595 3294 */
nicholas@2595 3295
nicholas@2595 3296 function calculateLabel(labelType, index, offset) {
nicholas@2595 3297 if (labelType == "none") {
nicholas@2595 3298 return "";
nicholas@2595 3299 }
nicholas@2595 3300 switch (labelType) {
nicholas@2595 3301 case "letter":
nicholas@2596 3302 return String.fromCharCode((index + offset) % 26 + 97);
nicholas@2595 3303 case "capital":
nicholas@2607 3304 return String.fromCharCode((index + offset) % 26 + 65);
nicholas@2625 3305 case "samediff":
nicholas@2708 3306 if (index === 0) {
nicholas@2625 3307 return "Same";
nicholas@2625 3308 } else if (index == 1) {
nicholas@2625 3309 return "Difference";
nicholas@2625 3310 }
nicholas@2708 3311 return "";
nicholas@2595 3312 case "number":
nicholas@2595 3313 return String(index + offset);
nicholas@2595 3314 default:
nicholas@2595 3315 return "";
nicholas@2595 3316 }
nicholas@2595 3317 }
nicholas@2595 3318
nicholas@2708 3319 if (typeof labelStart !== "string" || labelStart.length === 0) {
nicholas@2595 3320 labelStart = String.fromCharCode(0);
nicholas@2595 3321 }
nicholas@2595 3322
nicholas@2595 3323 switch (labelType) {
nicholas@2595 3324 case "letter":
nicholas@2595 3325 labelStart = labelStart.charCodeAt(0);
nicholas@2596 3326 if (labelStart < 97 || labelStart > 122) {
nicholas@2595 3327 labelStart = 97;
nicholas@2595 3328 }
nicholas@2595 3329 labelStart -= 97;
nicholas@2595 3330 break;
nicholas@2595 3331 case "capital":
nicholas@2595 3332 labelStart = labelStart.charCodeAt(0);
nicholas@2596 3333 if (labelStart < 65 || labelStart > 90) {
nicholas@2595 3334 labelStart = 65;
nicholas@2595 3335 }
nicholas@2595 3336 labelStart -= 65;
nicholas@2595 3337 break;
nicholas@2595 3338 case "number":
nicholas@2608 3339 labelStart = Number(labelStart);
nicholas@2608 3340 if (!isFinite(labelStart)) {
nicholas@2595 3341 labelStart = 1;
nicholas@2595 3342 }
nicholas@2595 3343 break;
nicholas@2595 3344 default:
nicholas@2596 3345 labelStart = 0;
nicholas@2595 3346 }
nicholas@2595 3347 if (typeof index == "number") {
nicholas@2595 3348 return calculateLabel(labelType, index, labelStart);
nicholas@2595 3349 } else if (index.length && index.length > 0) {
nicholas@2595 3350 var a = [],
nicholas@2595 3351 l = index.length,
nicholas@2595 3352 i;
nicholas@2595 3353 for (i = 0; i < l; i++) {
nicholas@2595 3354 a[i] = calculateLabel(labelType, index[i], labelStart);
nicholas@2595 3355 }
nicholas@2595 3356 return a;
nicholas@2595 3357 } else {
nicholas@2595 3358 throw ("Invalid arguments");
nicholas@2595 3359 }
nicholas@2708 3360 };
nicholas@2649 3361
nicholas@2649 3362 this.getCombinedInterfaces = function (page) {
nicholas@2649 3363 // Combine the interfaces with the global interface nodes
nicholas@2649 3364 var global = specification.interfaces,
nicholas@2649 3365 local = page.interfaces;
nicholas@2649 3366 local.forEach(function (locInt) {
nicholas@2649 3367 // Iterate through the options nodes
nicholas@2649 3368 var addList = [];
nicholas@2649 3369 global.options.forEach(function (gopt) {
nicholas@2649 3370 var lopt = locInt.options.find(function (lopt) {
nicholas@2649 3371 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
nicholas@2649 3372 });
nicholas@2649 3373 if (!lopt) {
nicholas@2649 3374 // Global option doesn't exist locally
nicholas@2649 3375 addList.push(gopt);
nicholas@2649 3376 }
nicholas@2649 3377 });
nicholas@2649 3378 locInt.options = locInt.options.concat(addList);
nicholas@2649 3379 if (!locInt.scales && global.scales) {
nicholas@2649 3380 // Use the global default scales
nicholas@2649 3381 locInt.scales = global.scales;
nicholas@2649 3382 }
nicholas@2649 3383 });
nicholas@2649 3384 return local;
nicholas@2708 3385 };
nicholas@2224 3386 }
nicholas@2224 3387
nicholas@2498 3388 function Storage() {
nicholas@2498 3389 // Holds results in XML format until ready for collection
nicholas@2498 3390 this.globalPreTest = null;
nicholas@2498 3391 this.globalPostTest = null;
nicholas@2498 3392 this.testPages = [];
nicholas@2498 3393 this.document = null;
nicholas@2498 3394 this.root = null;
nicholas@2498 3395 this.state = 0;
nicholas@2733 3396 var pFilenamePrefix = "save";
nicholas@2498 3397
nicholas@2498 3398 this.initialise = function (existingStore) {
nicholas@2708 3399 if (existingStore === undefined) {
nicholas@2224 3400 // We need to get the sessionKey
nicholas@2510 3401 this.SessionKey.requestKey();
nicholas@2498 3402 this.document = document.implementation.createDocument(null, "waetresult", null);
nicholas@2224 3403 this.root = this.document.childNodes[0];
nicholas@2224 3404 var projectDocument = specification.projectXML;
nicholas@2708 3405 projectDocument.setAttribute('file-name', specification.url);
nicholas@2708 3406 projectDocument.setAttribute('url', qualifyURL(specification.url));
nicholas@2224 3407 this.root.appendChild(projectDocument);
nicholas@2224 3408 this.root.appendChild(interfaceContext.returnDateNode());
nicholas@2224 3409 this.root.appendChild(interfaceContext.returnNavigator());
nicholas@2224 3410 } else {
nicholas@2224 3411 this.document = existingStore;
nicholas@2294 3412 this.root = existingStore.firstChild;
nicholas@2224 3413 this.SessionKey.key = this.root.getAttribute("key");
nicholas@2224 3414 }
nicholas@2708 3415 if (specification.preTest !== undefined) {
nicholas@2498 3416 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
nicholas@2498 3417 }
nicholas@2708 3418 if (specification.postTest !== undefined) {
nicholas@2498 3419 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
nicholas@2498 3420 }
nicholas@2498 3421 };
nicholas@2498 3422
nicholas@2224 3423 this.SessionKey = {
nicholas@2224 3424 key: null,
nicholas@2224 3425 request: new XMLHttpRequest(),
nicholas@2224 3426 parent: this,
nicholas@2498 3427 handleEvent: function () {
nicholas@2224 3428 var parse = new DOMParser();
nicholas@2498 3429 var xml = parse.parseFromString(this.request.response, "text/xml");
nicholas@2708 3430 if (this.request.response.length === 0) {
nicholas@2515 3431 console.error("An unspecified error occured, no server key could be generated");
nicholas@2376 3432 return;
nicholas@2376 3433 }
nicholas@2498 3434 if (xml.getElementsByTagName("state").length > 0) {
nicholas@2498 3435 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
nicholas@2498 3436 this.key = xml.getAllElementsByTagName("key")[0].textContent;
nicholas@2498 3437 this.parent.root.setAttribute("key", this.key);
nicholas@2498 3438 this.parent.root.setAttribute("state", "empty");
nicholas@2516 3439 this.update();
nicholas@2515 3440 return;
nicholas@2514 3441 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
nicholas@2515 3442 this.key = null;
nicholas@2514 3443 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
nicholas@2515 3444 return;
nicholas@2498 3445 }
nicholas@2498 3446 }
nicholas@2515 3447 this.key = null;
nicholas@2515 3448 console.error("An unspecified error occured, no server key could be generated");
nicholas@2224 3449 },
nicholas@2510 3450 requestKey: function () {
nicholas@2510 3451 // For new servers, request a new key from the server
nicholas@2510 3452 var returnURL = "";
nicholas@2510 3453 if (typeof specification.projectReturn == "string") {
nicholas@2510 3454 if (specification.projectReturn.substr(0, 4) == "http") {
nicholas@2510 3455 returnURL = specification.projectReturn;
nicholas@2510 3456 }
nicholas@2510 3457 }
nicholas@2510 3458 this.request.open("GET", returnURL + "php/requestKey.php", true);
nicholas@2510 3459 this.request.addEventListener("load", this);
nicholas@2510 3460 this.request.send();
nicholas@2510 3461 },
nicholas@2498 3462 update: function () {
nicholas@2708 3463 if (this.key === null) {
nicholas@2357 3464 console.log("Cannot save as key == null");
nicholas@2357 3465 return;
nicholas@2357 3466 }
nicholas@2498 3467 this.parent.root.setAttribute("state", "update");
nicholas@2224 3468 var xmlhttp = new XMLHttpRequest();
nicholas@2302 3469 var returnURL = "";
nicholas@2302 3470 if (typeof specification.projectReturn == "string") {
nicholas@2498 3471 if (specification.projectReturn.substr(0, 4) == "http") {
nicholas@2302 3472 returnURL = specification.projectReturn;
nicholas@2302 3473 }
nicholas@2302 3474 }
nicholas@2722 3475 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=" + this.parent.filenamePrefix);
nicholas@2224 3476 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nicholas@2498 3477 xmlhttp.onerror = function () {
nicholas@2224 3478 console.log('Error updating file to server!');
nicholas@2224 3479 };
nicholas@2224 3480 var hold = document.createElement("div");
nicholas@2224 3481 var clone = this.parent.root.cloneNode(true);
nicholas@2224 3482 hold.appendChild(clone);
nicholas@2498 3483 xmlhttp.onload = function () {
nicholas@2224 3484 if (this.status >= 300) {
nicholas@2224 3485 console.log("WARNING - Could not update at this time");
nicholas@2224 3486 } else {
nicholas@2224 3487 var parser = new DOMParser();
nicholas@2224 3488 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nicholas@2224 3489 var response = xmlDoc.getElementsByTagName('response')[0];
nicholas@2224 3490 if (response.getAttribute("state") == "OK") {
nicholas@2224 3491 var file = response.getElementsByTagName("file")[0];
nicholas@2498 3492 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
nicholas@2224 3493 } else {
nicholas@2224 3494 var message = response.getElementsByTagName("message");
nicholas@2498 3495 console.log("Intermediate save: Error! " + message.textContent);
nicholas@2224 3496 }
nicholas@2224 3497 }
nicholas@2708 3498 };
nicholas@2224 3499 xmlhttp.send([hold.innerHTML]);
nicholas@2723 3500 },
nicholas@2723 3501 finish: function () {
nicholas@2723 3502 // Final upload to complete the test
nicholas@2723 3503 this.parent.finish();
nicholas@2723 3504 var hold = document.createElement("div");
nicholas@2723 3505 var clone = this.parent.root.cloneNode(true);
nicholas@2723 3506 hold.appendChild(clone);
nicholas@2733 3507 var saveURL = specification.returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=";
nicholas@2733 3508 if (this.parent.filenamePrefix.length == 0) {
nicholas@2733 3509 saveURL += "save";
nicholas@2733 3510 } else {
nicholas@2733 3511 saveURL += this.parent.filenamePrefix;
nicholas@2733 3512 }
nicholas@2723 3513 return new Promise(function (resolve, reject) {
nicholas@2723 3514 var xmlhttp = new XMLHttpRequest();
nicholas@2723 3515 xmlhttp.open("POST", saveURL);
nicholas@2723 3516 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nicholas@2723 3517 xmlhttp.onerror = function () {
nicholas@2723 3518 console.log('Error updating file to server!');
nicholas@2723 3519 createProjectSave("local");
nicholas@2723 3520 };
nicholas@2723 3521 xmlhttp.onload = function () {
nicholas@2723 3522 if (this.status >= 300) {
nicholas@2723 3523 console.log("WARNING - Could not update at this time");
nicholas@2723 3524 createProjectSave("local");
nicholas@2723 3525 } else {
nicholas@2723 3526 var parser = new DOMParser();
nicholas@2723 3527 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nicholas@2723 3528 var response = xmlDoc.getElementsByTagName('response')[0];
nicholas@2723 3529 if (response.getAttribute("state") == "OK") {
nicholas@2723 3530 var file = response.getElementsByTagName("file")[0];
nicholas@2723 3531 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
nicholas@2723 3532 resolve(response);
nicholas@2723 3533 } else {
nicholas@2723 3534 var message = response.getElementsByTagName("message");
nicholas@2723 3535 reject(message);
nicholas@2723 3536 }
nicholas@2723 3537 }
nicholas@2723 3538 };
nicholas@2723 3539 xmlhttp.send([hold.innerHTML]);
nicholas@2723 3540 });
nicholas@2224 3541 }
nicholas@2708 3542 };
nicholas@2498 3543
nicholas@2498 3544 this.createTestPageStore = function (specification) {
nicholas@2498 3545 var store = new this.pageNode(this, specification);
nicholas@2498 3546 this.testPages.push(store);
nicholas@2498 3547 return this.testPages[this.testPages.length - 1];
nicholas@2498 3548 };
nicholas@2498 3549
nicholas@2498 3550 this.surveyNode = function (parent, root, specification) {
nicholas@2498 3551 this.specification = specification;
nicholas@2498 3552 this.parent = parent;
nicholas@2224 3553 this.state = "empty";
nicholas@2498 3554 this.XMLDOM = this.parent.document.createElement('survey');
nicholas@2498 3555 this.XMLDOM.setAttribute('location', this.specification.location);
nicholas@2498 3556 this.XMLDOM.setAttribute("state", this.state);
nicholas@2708 3557 this.specification.options.forEach(function (optNode) {
nicholas@2498 3558 if (optNode.type != 'statement') {
nicholas@2498 3559 var node = this.parent.document.createElement('surveyresult');
nicholas@2498 3560 node.setAttribute("ref", optNode.id);
nicholas@2498 3561 node.setAttribute('type', optNode.type);
nicholas@2498 3562 this.XMLDOM.appendChild(node);
nicholas@2498 3563 }
nicholas@2708 3564 }, this);
nicholas@2498 3565 root.appendChild(this.XMLDOM);
nicholas@2498 3566
nicholas@2498 3567 this.postResult = function (node) {
nicholas@2708 3568 function postNumber(doc, value) {
nicholas@2708 3569 var child = doc.createElement("response");
nicholas@2708 3570 child.textContent = value;
nicholas@2708 3571 return child;
nicholas@2708 3572 }
nicholas@2708 3573
nicholas@2708 3574 function postRadio(doc, node) {
nicholas@2708 3575 var child = doc.createElement('response');
nicholas@2708 3576 if (node.response !== null) {
nicholas@2708 3577 child.setAttribute('name', node.response.name);
nicholas@2708 3578 child.textContent = node.response.text;
nicholas@2708 3579 }
nicholas@2708 3580 return child;
nicholas@2708 3581 }
nicholas@2708 3582
nicholas@2708 3583 function postCheckbox(doc, node) {
nicholas@2708 3584 var checkNode = doc.createElement('response');
nicholas@2708 3585 checkNode.setAttribute('name', node.name);
nicholas@2708 3586 checkNode.setAttribute('checked', node.checked);
nicholas@2708 3587 return checkNode;
nicholas@2708 3588 }
nicholas@2498 3589 // From popup: node is the popupOption node containing both spec. and results
nicholas@2498 3590 // ID is the position
nicholas@2498 3591 if (node.specification.type == 'statement') {
nicholas@2498 3592 return;
nicholas@2498 3593 }
nicholas@2498 3594 var surveyresult = this.XMLDOM.firstChild;
nicholas@2708 3595 while (surveyresult !== null) {
nicholas@2498 3596 if (surveyresult.getAttribute("ref") == node.specification.id) {
nicholas@2224 3597 break;
nicholas@2224 3598 }
nicholas@2224 3599 surveyresult = surveyresult.nextElementSibling;
nicholas@2224 3600 }
nicholas@2498 3601 switch (node.specification.type) {
nicholas@2498 3602 case "number":
nicholas@2498 3603 case "question":
n@2583 3604 case "slider":
nicholas@2708 3605 surveyresult.appendChild(postNumber(this.parent.document, node.response));
nicholas@2464 3606 break;
nicholas@2498 3607 case "radio":
nicholas@2708 3608 surveyresult.appendChild(postRadio(this.parent.document, node));
nicholas@2498 3609 break;
nicholas@2498 3610 case "checkbox":
nicholas@2708 3611 if (node.response === undefined) {
nicholas@2498 3612 surveyresult.appendChild(this.parent.document.createElement('response'));
nicholas@2498 3613 break;
nicholas@2498 3614 }
nicholas@2498 3615 for (var i = 0; i < node.response.length; i++) {
nicholas@2708 3616 surveyresult.appendChild(postCheckbox(this.parent.document, node.response[i]));
nicholas@2498 3617 }
nicholas@2498 3618 break;
nicholas@2498 3619 }
nicholas@2498 3620 };
nicholas@2498 3621 this.complete = function () {
nicholas@2498 3622 this.state = "complete";
nicholas@2498 3623 this.XMLDOM.setAttribute("state", this.state);
nicholas@2708 3624 };
nicholas@2498 3625 };
nicholas@2498 3626
nicholas@2498 3627 this.pageNode = function (parent, specification) {
nicholas@2498 3628 // Create one store per test page
nicholas@2498 3629 this.specification = specification;
nicholas@2498 3630 this.parent = parent;
nicholas@2498 3631 this.state = "empty";
nicholas@2498 3632 this.XMLDOM = this.parent.document.createElement('page');
nicholas@2498 3633 this.XMLDOM.setAttribute('ref', specification.id);
nicholas@2498 3634 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
nicholas@2498 3635 this.XMLDOM.setAttribute("state", this.state);
nicholas@2708 3636 if (specification.preTest !== undefined) {
nicholas@2498 3637 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
nicholas@2498 3638 }
nicholas@2708 3639 if (specification.postTest !== undefined) {
nicholas@2498 3640 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
nicholas@2498 3641 }
nicholas@2498 3642
nicholas@2498 3643 // Add any page metrics
nicholas@2498 3644 var page_metric = this.parent.document.createElement('metric');
nicholas@2498 3645 this.XMLDOM.appendChild(page_metric);
nicholas@2498 3646
nicholas@2498 3647 // Add the audioelement
nicholas@2708 3648 this.specification.audioElements.forEach(function (element) {
nicholas@2498 3649 var aeNode = this.parent.document.createElement('audioelement');
nicholas@2498 3650 aeNode.setAttribute('ref', element.id);
nicholas@2708 3651 if (element.name !== undefined) {
nicholas@2708 3652 aeNode.setAttribute('name', element.name);
nicholas@2708 3653 }
nicholas@2498 3654 aeNode.setAttribute('type', element.type);
nicholas@2498 3655 aeNode.setAttribute('url', element.url);
nicholas@2498 3656 aeNode.setAttribute('fqurl', qualifyURL(element.url));
nicholas@2498 3657 aeNode.setAttribute('gain', element.gain);
nicholas@2498 3658 if (element.type == 'anchor' || element.type == 'reference') {
nicholas@2498 3659 if (element.marker > 0) {
nicholas@2498 3660 aeNode.setAttribute('marker', element.marker);
nicholas@2464 3661 }
nicholas@2498 3662 }
nicholas@2498 3663 var ae_metric = this.parent.document.createElement('metric');
nicholas@2498 3664 aeNode.appendChild(ae_metric);
nicholas@2498 3665 this.XMLDOM.appendChild(aeNode);
nicholas@2708 3666 }, this);
nicholas@2498 3667
nicholas@2498 3668 this.parent.root.appendChild(this.XMLDOM);
nicholas@2498 3669
nicholas@2498 3670 this.complete = function () {
nicholas@2224 3671 this.state = "complete";
nicholas@2498 3672 this.XMLDOM.setAttribute("state", "complete");
nicholas@2708 3673 };
nicholas@2498 3674 };
nicholas@2498 3675 this.update = function () {
nicholas@2224 3676 this.SessionKey.update();
nicholas@2708 3677 };
nicholas@2498 3678 this.finish = function () {
nicholas@2498 3679 this.state = 1;
nicholas@2498 3680 this.root.setAttribute("state", "complete");
nicholas@2498 3681 return this.root;
nicholas@2498 3682 };
nicholas@2722 3683
nicholas@2722 3684 Object.defineProperties(this, {
nicholas@2722 3685 'filenamePrefix': {
nicholas@2722 3686 'get': function () {
nicholas@2722 3687 return pFilenamePrefix;
nicholas@2722 3688 },
nicholas@2722 3689 'set': function (value) {
nicholas@2722 3690 if (typeof value !== "string") {
nicholas@2722 3691 value = String(value);
nicholas@2722 3692 }
nicholas@2722 3693 pFilenamePrefix = value;
nicholas@2722 3694 return value;
nicholas@2722 3695 }
nicholas@2722 3696 }
nicholas@2725 3697 });
nicholas@2224 3698 }
nicholas@2384 3699
nicholas@2401 3700 var window_depedancy_callback;
nicholas@2498 3701 window_depedancy_callback = window.setInterval(function () {
nicholas@2401 3702 if (check_dependancies()) {
nicholas@2401 3703 window.clearInterval(window_depedancy_callback);
nicholas@2401 3704 onload();
nicholas@2401 3705 } else {
nicholas@2401 3706 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
nicholas@2401 3707 }
nicholas@2498 3708 }, 100);