annotate js/core.js @ 2579:d83b38564fbb

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