annotate js/core.js @ 2678:e446c9b43cd9

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