annotate js/core.js @ 2716:57ba3f490836

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