annotate js/core.js @ 2586:42446b5eeee8

Fix scoreParser for multiple-axis results
author Nicholas Jillings <nicholas.jillings@mail.bcu.ac.uk>
date Sat, 05 Nov 2016 13:05:30 +0000
parents 5f7c11fa5f83
children 89f787e2e90c
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';
n@2583 802 if (node.specification.min != null) {
nicholas@2498 803 input.min = node.specification.min;
nicholas@2498 804 }
n@2583 805 if (node.specification.max != null) {
nicholas@2498 806 input.max = node.specification.max;
nicholas@2498 807 }
n@2583 808 if (node.specification.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);
n@2583 826 } else if (node.specification.type == "slider") {
n@2583 827 var hold = document.createElement('div');
n@2583 828 var input = document.createElement('input');
n@2583 829 input.type = 'range';
n@2583 830 input.style.width = "90%";
n@2583 831 if (node.specification.min != null) {
n@2583 832 input.min = node.specification.min;
n@2583 833 }
n@2583 834 if (node.specification.max != null) {
n@2583 835 input.max = node.specification.max;
n@2583 836 }
n@2583 837 if (node.response != undefined) {
n@2583 838 input.value = node.response;
n@2583 839 }
n@2583 840 hold.className = "survey-slider-text-holder";
n@2583 841 var minText = document.createElement('span');
n@2583 842 var maxText = document.createElement('span');
n@2583 843 minText.textContent = node.specification.leftText;
n@2583 844 maxText.textContent = node.specification.rightText;
n@2583 845 hold.appendChild(minText);
n@2583 846 hold.appendChild(maxText);
n@2583 847 this.popupResponse.appendChild(input);
n@2583 848 this.popupResponse.appendChild(hold);
n@2583 849 this.popupResponse.style.textAlign = "center";
nicholas@2491 850 }
nicholas@2498 851 if (this.currentIndex + 1 == this.popupOptions.length) {
nicholas@2498 852 if (this.node.location == "pre") {
nicholas@2498 853 this.buttonProceed.textContent = 'Start';
nicholas@2498 854 } else {
nicholas@2498 855 this.buttonProceed.textContent = 'Submit';
nicholas@2498 856 }
nicholas@2498 857 } else {
nicholas@2498 858 this.buttonProceed.textContent = 'Next';
nicholas@2498 859 }
nicholas@2498 860 if (this.currentIndex > 0)
nicholas@2498 861 this.buttonPrevious.style.visibility = 'visible';
nicholas@2498 862 else
nicholas@2498 863 this.buttonPrevious.style.visibility = 'hidden';
nicholas@2498 864 };
nicholas@2498 865
nicholas@2498 866 this.initState = function (node, store) {
nicholas@2498 867 //Call this with your preTest and postTest nodes when needed to
nicholas@2498 868 // initialise the popup procedure.
nicholas@2498 869 if (node.options.length > 0) {
nicholas@2498 870 this.popupOptions = [];
nicholas@2498 871 this.node = node;
nicholas@2498 872 this.store = store;
nicholas@2498 873 for (var opt of node.options) {
nicholas@2498 874 this.popupOptions.push({
nicholas@2498 875 specification: opt,
nicholas@2498 876 response: null
nicholas@2498 877 });
nicholas@2498 878 }
nicholas@2498 879 this.currentIndex = 0;
nicholas@2498 880 this.showPopup();
nicholas@2498 881 this.postNode();
nicholas@2498 882 } else {
nicholas@2498 883 advanceState();
nicholas@2498 884 }
nicholas@2498 885 };
nicholas@2498 886
nicholas@2498 887 this.proceedClicked = function () {
nicholas@2498 888 // Each time the popup button is clicked!
nicholas@2224 889 if (testState.stateIndex == 0 && specification.calibration) {
nicholas@2224 890 interfaceContext.calibrationModuleObject.collect();
nicholas@2224 891 advanceState();
nicholas@2224 892 return;
nicholas@2224 893 }
nicholas@2498 894 var node = this.popupOptions[this.currentIndex];
nicholas@2498 895 if (node.specification.type == 'question') {
nicholas@2498 896 // Must extract the question data
nicholas@2498 897 var textArea = $(popup.popupContent).find('textarea')[0];
nicholas@2498 898 if (node.specification.mandatory == true && textArea.value.length == 0) {
nicholas@2498 899 interfaceContext.lightbox.post("Error", "This question is mandatory");
nicholas@2498 900 return;
nicholas@2498 901 } else {
nicholas@2498 902 // Save the text content
nicholas@2498 903 console.log("Question: " + node.specification.statement);
nicholas@2498 904 console.log("Question Response: " + textArea.value);
nicholas@2498 905 node.response = textArea.value;
nicholas@2498 906 }
nicholas@2464 907 // Perform the conditional
nicholas@2464 908 for (var condition of node.specification.conditions) {
nicholas@2464 909 var pass = false;
nicholas@2498 910 switch (condition.check) {
nicholas@2464 911 case "equals":
nicholas@2464 912 if (textArea.value == condition.value) {
nicholas@2464 913 pass = true;
nicholas@2464 914 }
nicholas@2464 915 break;
nicholas@2464 916 case "greaterThan":
nicholas@2464 917 case "lessThan":
nicholas@2464 918 console.log("Survey Element of type 'question' cannot interpret greaterThan/lessThan conditions. IGNORING");
nicholas@2464 919 break;
nicholas@2464 920 case "contains":
nicholas@2464 921 if (textArea.value.includes(condition.value)) {
nicholas@2464 922 pass = true;
nicholas@2464 923 }
nicholas@2464 924 break;
nicholas@2464 925 }
nicholas@2464 926 var jumpID;
nicholas@2464 927 if (pass) {
nicholas@2464 928 jumpID = condition.jumpToOnPass;
nicholas@2464 929 } else {
nicholas@2464 930 jumpID = condition.jumpToOnFail;
nicholas@2464 931 }
nicholas@2464 932 if (jumpID != undefined) {
nicholas@2498 933 var index = this.popupOptions.findIndex(function (item, index, element) {
nicholas@2498 934 if (item.specification.id == jumpID) {
nicholas@2498 935 return true;
nicholas@2498 936 } else {
nicholas@2498 937 return false;
nicholas@2498 938 }
nicholas@2498 939 }, this);
nicholas@2498 940 this.currentIndex = index - 1;
nicholas@2464 941 break;
nicholas@2464 942 }
nicholas@2464 943 }
nicholas@2498 944 } else if (node.specification.type == 'checkbox') {
nicholas@2498 945 // Must extract checkbox data
nicholas@2498 946 console.log("Checkbox: " + node.specification.statement);
nicholas@2498 947 var inputs = this.popupResponse.getElementsByTagName('input');
nicholas@2498 948 node.response = [];
nicholas@2566 949 var numChecked = 0;
nicholas@2570 950 for (var i = 0; i < node.specification.options.length; i++) {
nicholas@2570 951 if (inputs[i].checked) {
nicholas@2570 952 numChecked++;
nicholas@2570 953 }
nicholas@2566 954 }
nicholas@2566 955 if (node.specification.min != undefined) {
nicholas@2566 956 if (node.specification.max == undefined) {
nicholas@2566 957 if (numChecked < node.specification.min) {
nicholas@2570 958 var msg = "You must select at least " + node.specification.min + " option";
nicholas@2566 959 if (node.specification.min > 1) {
nicholas@2566 960 msg += "s";
nicholas@2566 961 }
nicholas@2570 962 interfaceContext.lightbox.post("Error", msg);
nicholas@2566 963 return;
nicholas@2566 964 }
nicholas@2570 965 } else {
nicholas@2566 966 if (numChecked < node.specification.min || numChecked > node.specification.max) {
nicholas@2566 967 if (node.specification.min == node.specification.max) {
nicholas@2570 968 interfaceContext.lightbox.post("Error", "You must only select " + node.specification.min);
nicholas@2566 969 } else {
nicholas@2570 970 interfaceContext.lightbox.post("Error", "You must select between " + node.specification.min + " and " + node.specification.max);
nicholas@2566 971 }
nicholas@2566 972 return;
nicholas@2566 973 }
nicholas@2566 974 }
nicholas@2566 975 }
nicholas@2498 976 for (var i = 0; i < node.specification.options.length; i++) {
nicholas@2498 977 node.response.push({
nicholas@2498 978 name: node.specification.options[i].name,
nicholas@2498 979 text: node.specification.options[i].text,
nicholas@2498 980 checked: inputs[i].checked
nicholas@2498 981 });
nicholas@2498 982 console.log(node.specification.options[i].name + ": " + inputs[i].checked);
nicholas@2498 983 }
nicholas@2464 984 // Perform the conditional
nicholas@2464 985 for (var condition of node.specification.conditions) {
nicholas@2464 986 var pass = false;
nicholas@2498 987 switch (condition.check) {
nicholas@2464 988 case "equals":
nicholas@2464 989 case "greaterThan":
nicholas@2464 990 case "lessThan":
nicholas@2464 991 console.log("Survey Element of type 'checkbox' cannot interpret equals/greaterThan/lessThan conditions. IGNORING");
nicholas@2464 992 break;
nicholas@2464 993 case "contains":
nicholas@2464 994 for (var response of node.response) {
nicholas@2464 995 if (response.name == condition.value && response.checked) {
nicholas@2464 996 pass = true;
nicholas@2464 997 break;
nicholas@2464 998 }
nicholas@2464 999 }
nicholas@2464 1000 break;
nicholas@2464 1001 }
nicholas@2464 1002 var jumpID;
nicholas@2464 1003 if (pass) {
nicholas@2464 1004 jumpID = condition.jumpToOnPass;
nicholas@2464 1005 } else {
nicholas@2464 1006 jumpID = condition.jumpToOnFail;
nicholas@2464 1007 }
nicholas@2464 1008 if (jumpID != undefined) {
nicholas@2498 1009 var index = this.popupOptions.findIndex(function (item, index, element) {
nicholas@2498 1010 if (item.specification.id == jumpID) {
nicholas@2498 1011 return true;
nicholas@2498 1012 } else {
nicholas@2498 1013 return false;
nicholas@2498 1014 }
nicholas@2498 1015 }, this);
nicholas@2498 1016 this.currentIndex = index - 1;
nicholas@2464 1017 break;
nicholas@2464 1018 }
nicholas@2464 1019 }
nicholas@2498 1020 } else if (node.specification.type == "radio") {
nicholas@2498 1021 var optHold = this.popupResponse;
nicholas@2498 1022 console.log("Radio: " + node.specification.statement);
nicholas@2498 1023 node.response = null;
nicholas@2498 1024 var i = 0;
nicholas@2498 1025 var inputs = optHold.getElementsByTagName('input');
nicholas@2498 1026 while (node.response == null) {
nicholas@2498 1027 if (i == inputs.length) {
nicholas@2498 1028 if (node.specification.mandatory == true) {
nicholas@2498 1029 interfaceContext.lightbox.post("Error", "Please select one option");
nicholas@2316 1030 return;
nicholas@2498 1031 }
nicholas@2316 1032 break;
nicholas@2498 1033 }
nicholas@2498 1034 if (inputs[i].checked == true) {
nicholas@2498 1035 node.response = node.specification.options[i];
nicholas@2498 1036 console.log("Selected: " + node.specification.options[i].name);
nicholas@2498 1037 }
nicholas@2498 1038 i++;
nicholas@2498 1039 }
nicholas@2464 1040 // Perform the conditional
nicholas@2464 1041 for (var condition of node.specification.conditions) {
nicholas@2464 1042 var pass = false;
nicholas@2498 1043 switch (condition.check) {
nicholas@2464 1044 case "contains":
nicholas@2464 1045 case "greaterThan":
nicholas@2464 1046 case "lessThan":
nicholas@2464 1047 console.log("Survey Element of type 'radio' cannot interpret contains/greaterThan/lessThan conditions. IGNORING");
nicholas@2464 1048 break;
nicholas@2464 1049 case "equals":
nicholas@2464 1050 if (node.response == condition.value) {
nicholas@2464 1051 pass = true;
nicholas@2464 1052 }
nicholas@2464 1053 break;
nicholas@2464 1054 }
nicholas@2464 1055 var jumpID;
nicholas@2464 1056 if (pass) {
nicholas@2464 1057 jumpID = condition.jumpToOnPass;
nicholas@2464 1058 } else {
nicholas@2464 1059 jumpID = condition.jumpToOnFail;
nicholas@2464 1060 }
nicholas@2464 1061 if (jumpID != undefined) {
nicholas@2498 1062 var index = this.popupOptions.findIndex(function (item, index, element) {
nicholas@2498 1063 if (item.specification.id == jumpID) {
nicholas@2498 1064 return true;
nicholas@2498 1065 } else {
nicholas@2498 1066 return false;
nicholas@2498 1067 }
nicholas@2498 1068 }, this);
nicholas@2498 1069 this.currentIndex = index - 1;
nicholas@2464 1070 break;
nicholas@2464 1071 }
nicholas@2464 1072 }
nicholas@2498 1073 } else if (node.specification.type == "number") {
nicholas@2498 1074 var input = this.popupContent.getElementsByTagName('input')[0];
nicholas@2498 1075 if (node.mandatory == true && input.value.length == 0) {
nicholas@2498 1076 interfaceContext.lightbox.post("Error", 'This question is mandatory. Please enter a number');
nicholas@2498 1077 return;
nicholas@2498 1078 }
nicholas@2498 1079 var enteredNumber = Number(input.value);
nicholas@2498 1080 if (isNaN(enteredNumber)) {
nicholas@2498 1081 interfaceContext.lightbox.post("Error", 'Please enter a valid number');
nicholas@2498 1082 return;
nicholas@2498 1083 }
nicholas@2498 1084 if (enteredNumber < node.min && node.min != null) {
nicholas@2498 1085 interfaceContext.lightbox.post("Error", 'Number is below the minimum value of ' + node.min);
nicholas@2498 1086 return;
nicholas@2498 1087 }
nicholas@2498 1088 if (enteredNumber > node.max && node.max != null) {
nicholas@2498 1089 interfaceContext.lightbox.post("Error", 'Number is above the maximum value of ' + node.max);
nicholas@2498 1090 return;
nicholas@2498 1091 }
nicholas@2498 1092 node.response = input.value;
nicholas@2464 1093 // Perform the conditional
nicholas@2464 1094 for (var condition of node.specification.conditions) {
nicholas@2464 1095 var pass = false;
nicholas@2498 1096 switch (condition.check) {
nicholas@2464 1097 case "contains":
nicholas@2464 1098 console.log("Survey Element of type 'number' cannot interpret contains conditions. IGNORING");
nicholas@2464 1099 break;
nicholas@2464 1100 case "greaterThan":
nicholas@2464 1101 if (node.response > Number(condition.value)) {
nicholas@2464 1102 pass = true;
nicholas@2464 1103 }
nicholas@2464 1104 break;
nicholas@2464 1105 case "lessThan":
nicholas@2464 1106 if (node.response < Number(condition.value)) {
nicholas@2464 1107 pass = true;
nicholas@2464 1108 }
nicholas@2464 1109 break;
nicholas@2464 1110 case "equals":
nicholas@2464 1111 if (node.response == condition.value) {
nicholas@2464 1112 pass = true;
nicholas@2464 1113 }
nicholas@2464 1114 break;
nicholas@2464 1115 }
nicholas@2464 1116 var jumpID;
nicholas@2464 1117 if (pass) {
nicholas@2464 1118 jumpID = condition.jumpToOnPass;
nicholas@2464 1119 } else {
nicholas@2464 1120 jumpID = condition.jumpToOnFail;
nicholas@2464 1121 }
nicholas@2464 1122 if (jumpID != undefined) {
nicholas@2498 1123 var index = this.popupOptions.findIndex(function (item, index, element) {
nicholas@2498 1124 if (item.specification.id == jumpID) {
nicholas@2498 1125 return true;
nicholas@2498 1126 } else {
nicholas@2498 1127 return false;
nicholas@2498 1128 }
nicholas@2498 1129 }, this);
nicholas@2498 1130 this.currentIndex = index - 1;
nicholas@2464 1131 break;
nicholas@2464 1132 }
nicholas@2464 1133 }
n@2583 1134 } else if (node.specification.type == 'slider') {
n@2583 1135 var input = this.popupContent.getElementsByTagName('input')[0];
n@2583 1136 node.response = input.value;
n@2583 1137 for (var condition of node.specification.conditions) {
n@2583 1138 var pass = false;
n@2583 1139 switch (condition.check) {
n@2583 1140 case "contains":
n@2583 1141 console.log("Survey Element of type 'number' cannot interpret contains conditions. IGNORING");
n@2583 1142 break;
n@2583 1143 case "greaterThan":
n@2583 1144 if (node.response > Number(condition.value)) {
n@2583 1145 pass = true;
n@2583 1146 }
n@2583 1147 break;
n@2583 1148 case "lessThan":
n@2583 1149 if (node.response < Number(condition.value)) {
n@2583 1150 pass = true;
n@2583 1151 }
n@2583 1152 break;
n@2583 1153 case "equals":
n@2583 1154 if (node.response == condition.value) {
n@2583 1155 pass = true;
n@2583 1156 }
n@2583 1157 break;
n@2583 1158 }
n@2583 1159 var jumpID;
n@2583 1160 if (pass) {
n@2583 1161 jumpID = condition.jumpToOnPass;
n@2583 1162 } else {
n@2583 1163 jumpID = condition.jumpToOnFail;
n@2583 1164 }
n@2583 1165 if (jumpID != undefined) {
n@2583 1166 var index = this.popupOptions.findIndex(function (item, index, element) {
n@2583 1167 if (item.specification.id == jumpID) {
n@2583 1168 return true;
n@2583 1169 } else {
n@2583 1170 return false;
n@2583 1171 }
n@2583 1172 }, this);
n@2583 1173 this.currentIndex = index - 1;
n@2583 1174 break;
n@2583 1175 }
n@2583 1176 }
nicholas@2498 1177 }
nicholas@2498 1178 this.currentIndex++;
nicholas@2498 1179 if (this.currentIndex < this.popupOptions.length) {
nicholas@2498 1180 this.postNode();
nicholas@2498 1181 } else {
nicholas@2498 1182 // Reached the end of the popupOptions
nicholas@2498 1183 this.popupTitle.textContent = "";
nicholas@2498 1184 this.popupResponse.innerHTML = "";
nicholas@2498 1185 this.hidePopup();
nicholas@2498 1186 for (var node of this.popupOptions) {
nicholas@2498 1187 this.store.postResult(node);
nicholas@2498 1188 }
nicholas@2224 1189 this.store.complete();
nicholas@2498 1190 advanceState();
nicholas@2498 1191 }
nicholas@2498 1192 };
nicholas@2498 1193
nicholas@2498 1194 this.previousClick = function () {
nicholas@2498 1195 // Triggered when the 'Back' button is clicked in the survey
nicholas@2498 1196 if (this.currentIndex > 0) {
nicholas@2498 1197 this.currentIndex--;
nicholas@2498 1198 this.postNode();
nicholas@2498 1199 }
nicholas@2498 1200 };
nicholas@2498 1201
nicholas@2498 1202 this.resize = function (event) {
nicholas@2498 1203 // Called on window resize;
nicholas@2498 1204 if (this.popup != null) {
nicholas@2498 1205 this.popup.style.left = (window.innerWidth / 2) - 250 + 'px';
nicholas@2498 1206 this.popup.style.top = (window.innerHeight / 2) - 125 + 'px';
nicholas@2498 1207 var blank = document.getElementsByClassName('testHalt')[0];
nicholas@2498 1208 blank.style.width = window.innerWidth;
nicholas@2498 1209 blank.style.height = window.innerHeight;
nicholas@2498 1210 }
nicholas@2498 1211 };
nicholas@2498 1212 this.hideNextButton = function () {
nicholas@2224 1213 this.buttonProceed.style.visibility = "hidden";
nicholas@2224 1214 }
nicholas@2498 1215 this.hidePreviousButton = function () {
nicholas@2224 1216 this.buttonPrevious.style.visibility = "hidden";
nicholas@2224 1217 }
nicholas@2498 1218 this.showNextButton = function () {
nicholas@2224 1219 this.buttonProceed.style.visibility = "visible";
nicholas@2224 1220 }
nicholas@2498 1221 this.showPreviousButton = function () {
nicholas@2224 1222 this.buttonPrevious.style.visibility = "visible";
nicholas@2224 1223 }
nicholas@2224 1224 }
nicholas@2224 1225
nicholas@2498 1226 function advanceState() {
nicholas@2498 1227 // Just for complete clarity
nicholas@2498 1228 testState.advanceState();
nicholas@2224 1229 }
nicholas@2224 1230
nicholas@2498 1231 function stateMachine() {
nicholas@2498 1232 // Object prototype for tracking and managing the test state
nicholas@2498 1233 this.stateMap = [];
nicholas@2498 1234 this.preTestSurvey = null;
nicholas@2498 1235 this.postTestSurvey = null;
nicholas@2498 1236 this.stateIndex = null;
nicholas@2498 1237 this.currentStateMap = null;
nicholas@2498 1238 this.currentStatePosition = null;
nicholas@2224 1239 this.currentStore = null;
nicholas@2498 1240 this.initialise = function () {
nicholas@2498 1241
nicholas@2498 1242 // Get the data from Specification
nicholas@2498 1243 var pagePool = [];
nicholas@2224 1244 var pageInclude = [];
nicholas@2498 1245 for (var page of specification.pages) {
nicholas@2224 1246 if (page.alwaysInclude) {
nicholas@2224 1247 pageInclude.push(page);
nicholas@2224 1248 } else {
nicholas@2224 1249 pagePool.push(page);
nicholas@2224 1250 }
nicholas@2498 1251 }
nicholas@2498 1252
nicholas@2224 1253 // Find how many are left to get
nicholas@2224 1254 var numPages = specification.poolSize;
nicholas@2224 1255 if (numPages > pagePool.length) {
nicholas@2224 1256 console.log("WARNING - You have specified more pages in <setup poolSize> than you have created!!");
nicholas@2224 1257 numPages = specification.pages.length;
nicholas@2224 1258 }
nicholas@2224 1259 if (specification.poolSize == 0) {
nicholas@2224 1260 numPages = specification.pages.length;
nicholas@2224 1261 }
nicholas@2224 1262 numPages -= pageInclude.length;
nicholas@2498 1263
nicholas@2224 1264 if (numPages > 0) {
nicholas@2224 1265 // Go find the rest of the pages from the pool
nicholas@2224 1266 var subarr = null;
nicholas@2224 1267 if (specification.randomiseOrder) {
nicholas@2224 1268 // Append a random sub-array
nicholas@2498 1269 subarr = randomSubArray(pagePool, numPages);
nicholas@2224 1270 } else {
nicholas@2224 1271 // Append the matching number
nicholas@2498 1272 subarr = pagePool.slice(0, numPages);
nicholas@2224 1273 }
nicholas@2224 1274 pageInclude = pageInclude.concat(subarr);
nicholas@2224 1275 }
nicholas@2498 1276
nicholas@2224 1277 // We now have our selected pages in pageInclude array
nicholas@2498 1278 if (specification.randomiseOrder) {
nicholas@2498 1279 pageInclude = randomiseOrder(pageInclude);
nicholas@2498 1280 }
nicholas@2498 1281 for (var i = 0; i < pageInclude.length; i++) {
nicholas@2224 1282 pageInclude[i].presentedId = i;
nicholas@2498 1283 this.stateMap.push(pageInclude[i]);
nicholas@2224 1284 // For each selected page, we must get the sub pool
nicholas@2224 1285 if (pageInclude[i].poolSize != 0 && pageInclude[i].poolSize != pageInclude[i].audioElements.length) {
nicholas@2224 1286 var elemInclude = [];
nicholas@2224 1287 var elemPool = [];
nicholas@2224 1288 for (var elem of pageInclude[i].audioElements) {
nicholas@2224 1289 if (elem.include || elem.type != "normal") {
nicholas@2224 1290 elemInclude.push(elem);
nicholas@2224 1291 } else {
nicholas@2224 1292 elemPool.push(elem);
nicholas@2224 1293 }
nicholas@2224 1294 }
nicholas@2224 1295 var numElems = pageInclude[i].poolSize - elemInclude.length;
nicholas@2498 1296 pageInclude[i].audioElements = elemInclude.concat(randomSubArray(elemPool, numElems));
nicholas@2224 1297 }
nicholas@2224 1298 storage.createTestPageStore(pageInclude[i]);
nicholas@2224 1299 audioEngineContext.loadPageData(pageInclude[i]);
nicholas@2498 1300 }
nicholas@2498 1301
nicholas@2498 1302 if (specification.preTest != null) {
nicholas@2498 1303 this.preTestSurvey = specification.preTest;
nicholas@2498 1304 }
nicholas@2498 1305 if (specification.postTest != null) {
nicholas@2498 1306 this.postTestSurvey = specification.postTest;
nicholas@2498 1307 }
nicholas@2498 1308
nicholas@2498 1309 if (this.stateMap.length > 0) {
nicholas@2498 1310 if (this.stateIndex != null) {
nicholas@2498 1311 console.log('NOTE - State already initialise');
nicholas@2498 1312 }
nicholas@2498 1313 this.stateIndex = -2;
nicholas@2224 1314 console.log('Starting test...');
nicholas@2498 1315 } else {
nicholas@2498 1316 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
nicholas@2498 1317 }
nicholas@2498 1318 };
nicholas@2498 1319 this.advanceState = function () {
nicholas@2498 1320 if (this.stateIndex == null) {
nicholas@2498 1321 this.initialise();
nicholas@2498 1322 }
nicholas@2357 1323 if (this.stateIndex > -2) {
nicholas@2357 1324 storage.update();
nicholas@2357 1325 }
nicholas@2498 1326 if (this.stateIndex == -2) {
nicholas@2224 1327 this.stateIndex++;
nicholas@2498 1328 if (this.preTestSurvey != null) {
nicholas@2498 1329 popup.initState(this.preTestSurvey, storage.globalPreTest);
nicholas@2498 1330 } else {
nicholas@2498 1331 this.advanceState();
nicholas@2498 1332 }
nicholas@2498 1333 } else if (this.stateIndex == -1) {
nicholas@2224 1334 this.stateIndex++;
nicholas@2224 1335 if (specification.calibration) {
nicholas@2224 1336 popup.showPopup();
nicholas@2224 1337 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 1338 interfaceContext.calibrationModuleObject = new interfaceContext.calibrationModule();
nicholas@2224 1339 interfaceContext.calibrationModuleObject.build(popup.popupResponse);
nicholas@2224 1340 popup.hidePreviousButton();
nicholas@2224 1341 } else {
nicholas@2224 1342 this.advanceState();
nicholas@2224 1343 }
nicholas@2498 1344 } else if (this.stateIndex == this.stateMap.length) {
nicholas@2498 1345 // All test pages complete, post test
nicholas@2498 1346 console.log('Ending test ...');
nicholas@2498 1347 this.stateIndex++;
nicholas@2498 1348 if (this.postTestSurvey == null) {
nicholas@2498 1349 this.advanceState();
nicholas@2498 1350 } else {
nicholas@2498 1351 popup.initState(this.postTestSurvey, storage.globalPostTest);
nicholas@2498 1352 }
nicholas@2498 1353 } else if (this.stateIndex > this.stateMap.length) {
nicholas@2498 1354 createProjectSave(specification.projectReturn);
nicholas@2498 1355 } else {
nicholas@2224 1356 popup.hidePopup();
nicholas@2498 1357 if (this.currentStateMap == null) {
nicholas@2498 1358 this.currentStateMap = this.stateMap[this.stateIndex];
nicholas@2349 1359 // Find and extract the outside reference
nicholas@2498 1360 var elements = [],
nicholas@2498 1361 ref = [];
nicholas@2399 1362 var elem;
nicholas@2498 1363 while (elem = this.currentStateMap.audioElements.pop()) {
nicholas@2399 1364 if (elem.type == "outside-reference") {
nicholas@2399 1365 ref.push(elem);
nicholas@2498 1366 } else {
nicholas@2399 1367 elements.push(elem);
nicholas@2399 1368 }
nicholas@2349 1369 }
nicholas@2443 1370 elements = elements.reverse();
nicholas@2498 1371 if (this.currentStateMap.randomiseOrder) {
nicholas@2498 1372 elements = randomiseOrder(elements);
nicholas@2498 1373 }
nicholas@2399 1374 this.currentStateMap.audioElements = elements.concat(ref);
nicholas@2498 1375
nicholas@2224 1376 this.currentStore = storage.testPages[this.stateIndex];
nicholas@2498 1377 if (this.currentStateMap.preTest != null) {
nicholas@2498 1378 this.currentStatePosition = 'pre';
nicholas@2498 1379 popup.initState(this.currentStateMap.preTest, storage.testPages[this.stateIndex].preTest);
nicholas@2498 1380 } else {
nicholas@2498 1381 this.currentStatePosition = 'test';
nicholas@2498 1382 }
nicholas@2498 1383 interfaceContext.newPage(this.currentStateMap, storage.testPages[this.stateIndex]);
nicholas@2498 1384 return;
nicholas@2498 1385 }
nicholas@2498 1386 switch (this.currentStatePosition) {
nicholas@2498 1387 case 'pre':
nicholas@2498 1388 this.currentStatePosition = 'test';
nicholas@2498 1389 break;
nicholas@2498 1390 case 'test':
nicholas@2498 1391 this.currentStatePosition = 'post';
nicholas@2498 1392 // Save the data
nicholas@2498 1393 this.testPageCompleted();
nicholas@2498 1394 if (this.currentStateMap.postTest == null) {
nicholas@2498 1395 this.advanceState();
nicholas@2498 1396 return;
nicholas@2498 1397 } else {
nicholas@2498 1398 popup.initState(this.currentStateMap.postTest, storage.testPages[this.stateIndex].postTest);
nicholas@2498 1399 }
nicholas@2498 1400 break;
nicholas@2498 1401 case 'post':
nicholas@2498 1402 this.stateIndex++;
nicholas@2498 1403 this.currentStateMap = null;
nicholas@2498 1404 this.advanceState();
nicholas@2498 1405 break;
nicholas@2498 1406 };
nicholas@2498 1407 }
nicholas@2498 1408 };
nicholas@2498 1409
nicholas@2498 1410 this.testPageCompleted = function () {
nicholas@2498 1411 // Function called each time a test page has been completed
nicholas@2498 1412 var storePoint = storage.testPages[this.stateIndex];
nicholas@2498 1413 // First get the test metric
nicholas@2498 1414
nicholas@2498 1415 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
nicholas@2498 1416 if (audioEngineContext.metric.enableTestTimer) {
nicholas@2498 1417 var testTime = storePoint.parent.document.createElement('metricresult');
nicholas@2498 1418 testTime.id = 'testTime';
nicholas@2498 1419 testTime.textContent = audioEngineContext.timer.testDuration;
nicholas@2498 1420 metric.appendChild(testTime);
nicholas@2498 1421 }
nicholas@2498 1422
nicholas@2498 1423 var audioObjects = audioEngineContext.audioObjects;
nicholas@2498 1424 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 1425 ao.exportXMLDOM();
nicholas@2498 1426 }
nicholas@2498 1427 for (var element of interfaceContext.commentQuestions) {
nicholas@2498 1428 element.exportXMLDOM(storePoint);
nicholas@2498 1429 }
nicholas@2498 1430 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
nicholas@2224 1431 storePoint.complete();
nicholas@2498 1432 };
nicholas@2498 1433
nicholas@2498 1434 this.getCurrentTestPage = function () {
nicholas@2498 1435 if (this.stateIndex >= 0 && this.stateIndex < this.stateMap.length) {
nicholas@2310 1436 return this.currentStateMap;
nicholas@2310 1437 } else {
nicholas@2310 1438 return null;
nicholas@2310 1439 }
nicholas@2310 1440 }
nicholas@2498 1441 this.getCurrentTestPageStore = function () {
nicholas@2498 1442 if (this.stateIndex >= 0 && this.stateIndex < this.stateMap.length) {
nicholas@2312 1443 return this.currentStore;
nicholas@2312 1444 } else {
nicholas@2312 1445 return null;
nicholas@2312 1446 }
nicholas@2312 1447 }
nicholas@2224 1448 }
nicholas@2224 1449
nicholas@2224 1450 function AudioEngine(specification) {
nicholas@2498 1451
nicholas@2498 1452 // Create two output paths, the main outputGain and fooGain.
nicholas@2498 1453 // Output gain is default to 1 and any items for playback route here
nicholas@2498 1454 // Foo gain is used for analysis to ensure paths get processed, but are not heard
nicholas@2498 1455 // because web audio will optimise and any route which does not go to the destination gets ignored.
nicholas@2498 1456 this.outputGain = audioContext.createGain();
nicholas@2498 1457 this.fooGain = audioContext.createGain();
nicholas@2508 1458 this.fooGain.gain.value = 0;
nicholas@2498 1459
nicholas@2498 1460 // Use this to detect playback state: 0 - stopped, 1 - playing
nicholas@2498 1461 this.status = 0;
nicholas@2498 1462
nicholas@2498 1463 // Connect both gains to output
nicholas@2498 1464 this.outputGain.connect(audioContext.destination);
nicholas@2498 1465 this.fooGain.connect(audioContext.destination);
nicholas@2498 1466
nicholas@2498 1467 // Create the timer Object
nicholas@2498 1468 this.timer = new timer();
nicholas@2498 1469 // Create session metrics
nicholas@2498 1470 this.metric = new sessionMetrics(this, specification);
nicholas@2498 1471
nicholas@2498 1472 this.loopPlayback = false;
nicholas@2351 1473 this.synchPlayback = false;
nicholas@2351 1474 this.pageSpecification = null;
nicholas@2498 1475
nicholas@2498 1476 this.pageStore = null;
nicholas@2498 1477
nicholas@2508 1478 // Chrome 53+ Error solution
nicholas@2508 1479 // Empty buffer for keep-alive
nicholas@2508 1480 var nullBuffer = audioContext.createBuffer(1, audioContext.sampleRate, audioContext.sampleRate);
nicholas@2508 1481 this.nullBufferSource = audioContext.createBufferSource();
nicholas@2508 1482 this.nullBufferSource.buffer = nullBuffer;
nicholas@2508 1483 this.nullBufferSource.loop = true;
nicholas@2508 1484 this.nullBufferSource.start(0);
nicholas@2508 1485
nicholas@2498 1486 // Create store for new audioObjects
nicholas@2498 1487 this.audioObjects = [];
nicholas@2498 1488
nicholas@2498 1489 this.buffers = [];
nicholas@2498 1490 this.bufferObj = function () {
nicholas@2498 1491 this.url = null;
nicholas@2498 1492 this.buffer = null;
nicholas@2498 1493 this.xmlRequest = new XMLHttpRequest();
nicholas@2498 1494 this.xmlRequest.parent = this;
nicholas@2498 1495 this.users = [];
nicholas@2224 1496 this.progress = 0;
nicholas@2224 1497 this.status = 0;
nicholas@2498 1498 this.ready = function () {
nicholas@2498 1499 if (this.status >= 2) {
nicholas@2224 1500 this.status = 3;
nicholas@2224 1501 }
nicholas@2498 1502 for (var i = 0; i < this.users.length; i++) {
nicholas@2498 1503 this.users[i].state = 1;
nicholas@2498 1504 if (this.users[i].interfaceDOM != null) {
nicholas@2498 1505 this.users[i].bufferLoaded(this);
nicholas@2498 1506 }
nicholas@2498 1507 }
nicholas@2498 1508 };
nicholas@2498 1509 this.getMedia = function (url) {
nicholas@2498 1510 this.url = url;
nicholas@2498 1511 this.xmlRequest.open('GET', this.url, true);
nicholas@2498 1512 this.xmlRequest.responseType = 'arraybuffer';
nicholas@2498 1513
nicholas@2498 1514 var bufferObj = this;
nicholas@2498 1515
nicholas@2498 1516 // Create callback to decode the data asynchronously
nicholas@2498 1517 this.xmlRequest.onloadend = function () {
nicholas@2224 1518 // Use inbuilt WAVE decoder first
nicholas@2498 1519 if (this.status == -1) {
nicholas@2498 1520 return;
nicholas@2498 1521 }
nicholas@2224 1522 var waveObj = new WAVE();
nicholas@2498 1523 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function (decodedData) {
nicholas@2498 1524 bufferObj.buffer = decodedData;
nicholas@2498 1525 bufferObj.status = 2;
nicholas@2498 1526 calculateLoudness(bufferObj, "I");
nicholas@2498 1527 }, function (e) {
nicholas@2403 1528 var waveObj = new WAVE();
nicholas@2498 1529 if (waveObj.open(bufferObj.xmlRequest.response) == 0) {
nicholas@2498 1530 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels, waveObj.num_samples, waveObj.sample_rate);
nicholas@2498 1531 for (var c = 0; c < waveObj.num_channels; c++) {
nicholas@2403 1532 var buffer_ptr = bufferObj.buffer.getChannelData(c);
nicholas@2498 1533 for (var n = 0; n < waveObj.num_samples; n++) {
nicholas@2403 1534 buffer_ptr[n] = waveObj.decoded_data[c][n];
nicholas@2224 1535 }
nicholas@2224 1536 }
nicholas@2403 1537
nicholas@2403 1538 delete waveObj;
nicholas@2403 1539 }
nicholas@2498 1540 if (bufferObj.buffer != undefined) {
nicholas@2403 1541 bufferObj.status = 2;
nicholas@2498 1542 calculateLoudness(bufferObj, "I");
nicholas@2403 1543 }
nicholas@2403 1544 });
nicholas@2498 1545 };
nicholas@2498 1546
nicholas@2224 1547 // Create callback for any error in loading
nicholas@2498 1548 this.xmlRequest.onerror = function () {
nicholas@2224 1549 this.parent.status = -1;
nicholas@2498 1550 for (var i = 0; i < this.parent.users.length; i++) {
nicholas@2224 1551 this.parent.users[i].state = -1;
nicholas@2498 1552 if (this.parent.users[i].interfaceDOM != null) {
nicholas@2224 1553 this.parent.users[i].bufferLoaded(this);
nicholas@2224 1554 }
nicholas@2224 1555 }
nicholas@2498 1556 interfaceContext.lightbox.post("Error", "Could not load resource " + this.parent.url);
nicholas@2224 1557 }
nicholas@2498 1558
nicholas@2498 1559 this.progress = 0;
nicholas@2498 1560 this.progressCallback = function (event) {
nicholas@2498 1561 if (event.lengthComputable) {
nicholas@2498 1562 this.parent.progress = event.loaded / event.total;
nicholas@2498 1563 for (var i = 0; i < this.parent.users.length; i++) {
nicholas@2498 1564 if (this.parent.users[i].interfaceDOM != null) {
nicholas@2498 1565 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function") {
nicholas@2498 1566 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress * 100);
nicholas@2498 1567 }
nicholas@2498 1568 }
nicholas@2498 1569 }
nicholas@2498 1570 }
nicholas@2498 1571 };
nicholas@2498 1572 this.xmlRequest.addEventListener("progress", this.progressCallback);
nicholas@2224 1573 this.status = 1;
nicholas@2498 1574 this.xmlRequest.send();
nicholas@2498 1575 };
nicholas@2498 1576
nicholas@2498 1577 this.registerAudioObject = function (audioObject) {
nicholas@2224 1578 // Called by an audioObject to register to the buffer for use
nicholas@2224 1579 // First check if already in the register pool
nicholas@2498 1580 for (var objects of this.users) {
nicholas@2498 1581 if (audioObject.id == objects.id) {
nicholas@2498 1582 return 0;
nicholas@2498 1583 }
nicholas@2224 1584 }
nicholas@2224 1585 this.users.push(audioObject);
nicholas@2498 1586 if (this.status == 3 || this.status == -1) {
nicholas@2224 1587 // The buffer is already ready, trigger bufferLoaded
nicholas@2224 1588 audioObject.bufferLoaded(this);
nicholas@2224 1589 }
nicholas@2224 1590 };
nicholas@2498 1591
nicholas@2498 1592 this.copyBuffer = function (preSilenceTime, postSilenceTime) {
nicholas@2224 1593 // Copies the entire bufferObj.
nicholas@2498 1594 if (preSilenceTime == undefined) {
nicholas@2498 1595 preSilenceTime = 0;
nicholas@2498 1596 }
nicholas@2498 1597 if (postSilenceTime == undefined) {
nicholas@2498 1598 postSilenceTime = 0;
nicholas@2498 1599 }
nicholas@2498 1600 var preSilenceSamples = secondsToSamples(preSilenceTime, this.buffer.sampleRate);
nicholas@2498 1601 var postSilenceSamples = secondsToSamples(postSilenceTime, this.buffer.sampleRate);
nicholas@2498 1602 var newLength = this.buffer.length + preSilenceSamples + postSilenceSamples;
nicholas@2460 1603 var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
nicholas@2224 1604 // Now we can use some efficient background copy schemes if we are just padding the end
nicholas@2460 1605 if (preSilenceSamples == 0 && typeof copybuffer.copyToChannel == "function") {
nicholas@2498 1606 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
nicholas@2498 1607 copybuffer.copyToChannel(this.buffer.getChannelData(c), c);
nicholas@2224 1608 }
nicholas@2224 1609 } else {
nicholas@2498 1610 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
nicholas@2224 1611 var src = this.buffer.getChannelData(c);
nicholas@2460 1612 var dst = copybuffer.getChannelData(c);
nicholas@2498 1613 for (var n = 0; n < src.length; n++)
nicholas@2498 1614 dst[n + preSilenceSamples] = src[n];
nicholas@2224 1615 }
nicholas@2224 1616 }
nicholas@2224 1617 // Copy in the rest of the buffer information
nicholas@2460 1618 copybuffer.lufs = this.buffer.lufs;
nicholas@2460 1619 copybuffer.playbackGain = this.buffer.playbackGain;
nicholas@2460 1620 return copybuffer;
nicholas@2460 1621 }
nicholas@2498 1622
nicholas@2498 1623 this.cropBuffer = function (startTime, stopTime) {
nicholas@2460 1624 // Copy and return the cropped buffer
nicholas@2498 1625 var start_sample = Math.floor(startTime * this.buffer.sampleRate);
nicholas@2498 1626 var stop_sample = Math.floor(stopTime * this.buffer.sampleRate);
nicholas@2460 1627 var newLength = stop_sample - start_sample;
nicholas@2460 1628 var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
nicholas@2460 1629 // Now we can use some efficient background copy schemes if we are just padding the end
nicholas@2498 1630 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
nicholas@2460 1631 var buffer = this.buffer.getChannelData(c);
nicholas@2498 1632 var sub_frame = buffer.subarray(start_sample, stop_sample);
nicholas@2460 1633 if (typeof copybuffer.copyToChannel == "function") {
nicholas@2498 1634 copybuffer.copyToChannel(sub_frame, c);
nicholas@2460 1635 } else {
nicholas@2460 1636 var dst = copybuffer.getChannelData(c);
nicholas@2498 1637 for (var n = 0; n < newLength; n++)
nicholas@2505 1638 dst[n] = buffer[n + start_sample];
nicholas@2460 1639 }
nicholas@2460 1640 }
nicholas@2460 1641 return copybuffer;
nicholas@2224 1642 }
nicholas@2498 1643 };
nicholas@2498 1644
nicholas@2498 1645 this.loadPageData = function (page) {
nicholas@2224 1646 // Load the URL from pages
nicholas@2224 1647 for (var element of page.audioElements) {
nicholas@2224 1648 var URL = page.hostURL + element.url;
nicholas@2224 1649 var buffer = null;
nicholas@2224 1650 for (var buffObj of this.buffers) {
nicholas@2224 1651 if (URL == buffObj.url) {
nicholas@2224 1652 buffer = buffObj;
nicholas@2224 1653 break;
nicholas@2224 1654 }
nicholas@2224 1655 }
nicholas@2224 1656 if (buffer == null) {
nicholas@2224 1657 buffer = new this.bufferObj();
nicholas@2224 1658 buffer.getMedia(URL);
nicholas@2224 1659 this.buffers.push(buffer);
nicholas@2224 1660 }
nicholas@2224 1661 }
nicholas@2224 1662 };
nicholas@2498 1663
nicholas@2498 1664 this.play = function (id) {
nicholas@2498 1665 // Start the timer and set the audioEngine state to playing (1)
nicholas@2498 1666 if (this.status == 0) {
nicholas@2498 1667 // Check if all audioObjects are ready
nicholas@2498 1668 this.bufferReady(id);
nicholas@2498 1669 } else {
nicholas@2498 1670 this.status = 1;
nicholas@2498 1671 }
nicholas@2498 1672 if (this.status == 1) {
nicholas@2498 1673 this.timer.startTest();
nicholas@2498 1674 if (id == undefined) {
nicholas@2498 1675 id = -1;
nicholas@2498 1676 console.error('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
nicholas@2498 1677 return;
nicholas@2498 1678 } else {
nicholas@2498 1679 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
nicholas@2498 1680 }
nicholas@2498 1681 if (this.synchPlayback && this.loopPlayback) {
nicholas@2351 1682 // Traditional looped playback
nicholas@2498 1683 var setTime = audioContext.currentTime + specification.crossFade;
nicholas@2498 1684 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1685 this.audioObjects[i].play(audioContext.currentTime);
nicholas@2498 1686 if (id == i) {
nicholas@2498 1687 this.audioObjects[i].loopStart(setTime);
nicholas@2498 1688 } else {
nicholas@2498 1689 this.audioObjects[i].loopStop(setTime);
nicholas@2498 1690 }
nicholas@2498 1691 }
nicholas@2351 1692 } else {
nicholas@2498 1693 var setTime = audioContext.currentTime + specification.crossFade;
nicholas@2498 1694 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1695 if (i != id) {
nicholas@2498 1696 this.audioObjects[i].stop(setTime);
nicholas@2498 1697 } else if (i == id) {
nicholas@2498 1698 this.audioObjects[id].play(setTime);
nicholas@2498 1699 }
nicholas@2498 1700 }
nicholas@2498 1701 }
nicholas@2498 1702 interfaceContext.playhead.start();
nicholas@2498 1703 }
nicholas@2498 1704 };
nicholas@2224 1705
nicholas@2498 1706 this.stop = function () {
nicholas@2498 1707 // Send stop and reset command to all playback buffers
nicholas@2498 1708 if (this.status == 1) {
nicholas@2498 1709 var setTime = audioContext.currentTime + 0.1;
nicholas@2498 1710 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1711 this.audioObjects[i].stop(setTime);
nicholas@2498 1712 }
nicholas@2498 1713 interfaceContext.playhead.stop();
nicholas@2498 1714 }
nicholas@2498 1715 };
nicholas@2498 1716
nicholas@2498 1717 this.newTrack = function (element) {
nicholas@2498 1718 // Pull data from given URL into new audio buffer
nicholas@2498 1719 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
nicholas@2498 1720
nicholas@2498 1721 // Create the audioObject with ID of the new track length;
nicholas@2498 1722 audioObjectId = this.audioObjects.length;
nicholas@2498 1723 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
nicholas@2498 1724
nicholas@2498 1725 // Check if audioObject buffer is currently stored by full URL
nicholas@2498 1726 var URL = testState.currentStateMap.hostURL + element.url;
nicholas@2498 1727 var buffer = null;
nicholas@2498 1728 for (var i = 0; i < this.buffers.length; i++) {
nicholas@2498 1729 if (URL == this.buffers[i].url) {
nicholas@2498 1730 buffer = this.buffers[i];
nicholas@2498 1731 break;
nicholas@2498 1732 }
nicholas@2498 1733 }
nicholas@2498 1734 if (buffer == null) {
nicholas@2498 1735 console.log("[WARN]: Buffer was not loaded in pre-test! " + URL);
nicholas@2498 1736 buffer = new this.bufferObj();
nicholas@2224 1737 this.buffers.push(buffer);
nicholas@2498 1738 buffer.getMedia(URL);
nicholas@2498 1739 }
nicholas@2498 1740 this.audioObjects[audioObjectId].specification = element;
nicholas@2498 1741 this.audioObjects[audioObjectId].url = URL;
nicholas@2498 1742 // Obtain store node
nicholas@2498 1743 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
nicholas@2498 1744 for (var i = 0; i < aeNodes.length; i++) {
nicholas@2498 1745 if (aeNodes[i].getAttribute("ref") == element.id) {
nicholas@2498 1746 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
nicholas@2498 1747 break;
nicholas@2498 1748 }
nicholas@2498 1749 }
nicholas@2224 1750 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
nicholas@2498 1751 return this.audioObjects[audioObjectId];
nicholas@2498 1752 };
nicholas@2498 1753
nicholas@2498 1754 this.newTestPage = function (audioHolderObject, store) {
nicholas@2498 1755 this.pageStore = store;
nicholas@2351 1756 this.pageSpecification = audioHolderObject;
nicholas@2498 1757 this.status = 0;
nicholas@2498 1758 this.audioObjectsReady = false;
nicholas@2498 1759 this.metric.reset();
nicholas@2498 1760 for (var i = 0; i < this.buffers.length; i++) {
nicholas@2498 1761 this.buffers[i].users = [];
nicholas@2498 1762 }
nicholas@2498 1763 this.audioObjects = [];
nicholas@2224 1764 this.timer = new timer();
nicholas@2224 1765 this.loopPlayback = audioHolderObject.loop;
nicholas@2351 1766 this.synchPlayback = audioHolderObject.synchronous;
nicholas@2498 1767 };
nicholas@2498 1768
nicholas@2498 1769 this.checkAllPlayed = function () {
nicholas@2498 1770 arr = [];
nicholas@2498 1771 for (var id = 0; id < this.audioObjects.length; id++) {
nicholas@2498 1772 if (this.audioObjects[id].metric.wasListenedTo == false) {
nicholas@2498 1773 arr.push(this.audioObjects[id].id);
nicholas@2498 1774 }
nicholas@2498 1775 }
nicholas@2498 1776 return arr;
nicholas@2498 1777 };
nicholas@2498 1778
nicholas@2498 1779 this.checkAllReady = function () {
nicholas@2498 1780 var ready = true;
nicholas@2498 1781 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2498 1782 if (this.audioObjects[i].state == 0) {
nicholas@2498 1783 // Track not ready
nicholas@2498 1784 console.log('WAIT -- audioObject ' + i + ' not ready yet!');
nicholas@2498 1785 ready = false;
nicholas@2498 1786 };
nicholas@2498 1787 }
nicholas@2498 1788 return ready;
nicholas@2498 1789 };
nicholas@2498 1790
nicholas@2498 1791 this.setSynchronousLoop = function () {
nicholas@2570 1792 // Pads the signals so they are all exactly the same duration
nicholas@2570 1793 // Get the duration of the longest signal.
nicholas@2570 1794 var duration = 0;
nicholas@2498 1795 var maxId;
nicholas@2498 1796 for (var i = 0; i < this.audioObjects.length; i++) {
nicholas@2570 1797 if (duration < this.audioObjects[i].buffer.buffer.duration) {
nicholas@2570 1798 duration = this.audioObjects[i].buffer.buffer.duration;
nicholas@2498 1799 maxId = i;
nicholas@2498 1800 }
nicholas@2498 1801 }
nicholas@2498 1802 // Extract the audio and zero-pad
nicholas@2498 1803 for (var ao of this.audioObjects) {
nicholas@2570 1804 if (ao.buffer.buffer.duration !== duration) {
nicholas@2570 1805 ao.buffer.buffer = ao.buffer.copyBuffer(0, duration - ao.buffer.buffer.duration);
nicholas@2500 1806 }
nicholas@2498 1807 }
nicholas@2498 1808 };
nicholas@2498 1809
nicholas@2498 1810 this.bufferReady = function (id) {
nicholas@2498 1811 if (this.checkAllReady()) {
nicholas@2498 1812 if (this.synchPlayback) {
nicholas@2498 1813 this.setSynchronousLoop();
nicholas@2498 1814 }
nicholas@2460 1815 this.status = 1;
nicholas@2460 1816 return true;
nicholas@2460 1817 }
nicholas@2460 1818 return false;
nicholas@2460 1819 }
nicholas@2498 1820
nicholas@2498 1821 this.exportXML = function () {
nicholas@2498 1822
nicholas@2224 1823 };
nicholas@2498 1824
nicholas@2224 1825 }
nicholas@2224 1826
nicholas@2224 1827 function audioObject(id) {
nicholas@2498 1828 // The main buffer object with common control nodes to the AudioEngine
nicholas@2498 1829
nicholas@2498 1830 this.specification;
nicholas@2498 1831 this.id = id;
nicholas@2498 1832 this.state = 0; // 0 - no data, 1 - ready
nicholas@2498 1833 this.url = null; // Hold the URL given for the output back to the results.
nicholas@2498 1834 this.metric = new metricTracker(this);
nicholas@2498 1835 this.storeDOM = null;
nicholas@2498 1836
nicholas@2498 1837 // Bindings for GUI
nicholas@2498 1838 this.interfaceDOM = null;
nicholas@2498 1839 this.commentDOM = null;
nicholas@2498 1840
nicholas@2498 1841 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
nicholas@2498 1842 this.bufferNode = undefined;
nicholas@2498 1843 this.outputGain = audioContext.createGain();
nicholas@2498 1844
nicholas@2498 1845 this.onplayGain = 1.0;
nicholas@2498 1846
nicholas@2498 1847 // Connect buffer to the audio graph
nicholas@2498 1848 this.outputGain.connect(audioEngineContext.outputGain);
nicholas@2508 1849 audioEngineContext.nullBufferSource.connect(this.outputGain);
nicholas@2498 1850
nicholas@2498 1851 // the audiobuffer is not designed for multi-start playback
nicholas@2498 1852 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
nicholas@2498 1853 this.buffer;
nicholas@2498 1854
nicholas@2498 1855 this.bufferLoaded = function (callee) {
nicholas@2498 1856 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
nicholas@2498 1857 // audioObject and trigger the interfaceDOM.enable() function for user feedback
nicholas@2224 1858 if (callee.status == -1) {
nicholas@2224 1859 // ERROR
nicholas@2224 1860 this.state = -1;
nicholas@2498 1861 if (this.interfaceDOM != null) {
nicholas@2498 1862 this.interfaceDOM.error();
nicholas@2498 1863 }
nicholas@2224 1864 this.buffer = callee;
nicholas@2224 1865 return;
nicholas@2224 1866 }
nicholas@2224 1867 this.buffer = callee;
nicholas@2224 1868 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
nicholas@2224 1869 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
nicholas@2460 1870 var startTime = this.specification.startTime;
nicholas@2460 1871 var stopTime = this.specification.stopTime;
nicholas@2460 1872 var copybuffer = new callee.constructor();
nicholas@2500 1873
nicholas@2500 1874 copybuffer.buffer = callee.cropBuffer(startTime || 0, stopTime || callee.buffer.duration);
nicholas@2500 1875 if (preSilenceTime != 0 || postSilenceTime != 0) {
nicholas@2500 1876 copybuffer.buffer = copybuffer.copyBuffer(preSilenceTime, postSilenceTime);
nicholas@2460 1877 }
nicholas@2500 1878
nicholas@2500 1879 copybuffer.lufs = callee.buffer.lufs;
nicholas@2500 1880 this.buffer = copybuffer;
nicholas@2498 1881
nicholas@2498 1882 var targetLUFS = this.specification.parent.loudness || specification.loudness;
nicholas@2498 1883 if (typeof targetLUFS === "number" && isFinite(targetLUFS)) {
nicholas@2498 1884 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
nicholas@2498 1885 } else {
nicholas@2498 1886 this.buffer.buffer.playbackGain = 1.0;
nicholas@2498 1887 }
nicholas@2498 1888 if (this.interfaceDOM != null) {
nicholas@2498 1889 this.interfaceDOM.enable();
nicholas@2498 1890 }
nicholas@2498 1891 this.onplayGain = decibelToLinear(this.specification.gain) * (this.buffer.buffer.playbackGain || 1.0);
nicholas@2498 1892 this.storeDOM.setAttribute('playGain', linearToDecibel(this.onplayGain));
nicholas@2460 1893 this.state = 1;
nicholas@2460 1894 audioEngineContext.bufferReady(id);
nicholas@2498 1895 };
nicholas@2498 1896
nicholas@2498 1897 this.bindInterface = function (interfaceObject) {
nicholas@2498 1898 this.interfaceDOM = interfaceObject;
nicholas@2498 1899 this.metric.initialise(interfaceObject.getValue());
nicholas@2498 1900 if (this.state == 1) {
nicholas@2498 1901 this.interfaceDOM.enable();
nicholas@2498 1902 } else if (this.state == -1) {
nicholas@2224 1903 // ERROR
nicholas@2224 1904 this.interfaceDOM.error();
nicholas@2224 1905 return;
nicholas@2224 1906 }
nicholas@2498 1907 this.storeDOM.setAttribute('presentedId', interfaceObject.getPresentedId());
nicholas@2498 1908 };
nicholas@2498 1909
nicholas@2498 1910 this.loopStart = function (setTime) {
nicholas@2498 1911 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain, setTime);
nicholas@2498 1912 this.metric.startListening(audioEngineContext.timer.getTestTime());
nicholas@2224 1913 this.interfaceDOM.startPlayback();
nicholas@2498 1914 };
nicholas@2498 1915
nicholas@2498 1916 this.loopStop = function (setTime) {
nicholas@2498 1917 if (this.outputGain.gain.value != 0.0) {
nicholas@2498 1918 this.outputGain.gain.linearRampToValueAtTime(0.0, setTime);
nicholas@2498 1919 this.metric.stopListening(audioEngineContext.timer.getTestTime());
nicholas@2498 1920 }
nicholas@2224 1921 this.interfaceDOM.stopPlayback();
nicholas@2498 1922 };
nicholas@2498 1923
nicholas@2498 1924 this.play = function (startTime) {
nicholas@2498 1925 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
nicholas@2498 1926 this.bufferNode = audioContext.createBufferSource();
nicholas@2498 1927 this.bufferNode.owner = this;
nicholas@2498 1928 this.bufferNode.connect(this.outputGain);
nicholas@2498 1929 this.bufferNode.buffer = this.buffer.buffer;
nicholas@2498 1930 this.bufferNode.loop = audioEngineContext.loopPlayback;
nicholas@2498 1931 this.bufferNode.onended = function (event) {
nicholas@2498 1932 // Safari does not like using 'this' to reference the calling object!
nicholas@2498 1933 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
nicholas@2224 1934 if (event.currentTarget != null) {
nicholas@2498 1935 event.currentTarget.owner.stop(audioContext.currentTime + 1);
nicholas@2224 1936 }
nicholas@2498 1937 };
nicholas@2508 1938 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
nicholas@2498 1939 if (!audioEngineContext.loopPlayback || !audioEngineContext.synchPlayback) {
nicholas@2498 1940 this.metric.startListening(audioEngineContext.timer.getTestTime());
nicholas@2508 1941 this.outputGain.gain.setValueAtTime(this.onplayGain, startTime);
nicholas@2224 1942 this.interfaceDOM.startPlayback();
nicholas@2498 1943 } else {
nicholas@2498 1944 this.outputGain.gain.setValueAtTime(0.0, startTime);
nicholas@2224 1945 }
nicholas@2499 1946 if (audioEngineContext.loopPlayback) {
nicholas@2499 1947 this.bufferNode.loopStart = this.specification.startTime || 0;
nicholas@2499 1948 this.bufferNode.loopEnd = this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration;
nicholas@2499 1949 this.bufferNode.start(startTime);
nicholas@2499 1950 } else {
nicholas@2499 1951 this.bufferNode.start(startTime, this.specification.startTime || 0, this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration);
nicholas@2499 1952 }
nicholas@2224 1953 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
nicholas@2498 1954 }
nicholas@2498 1955 };
nicholas@2498 1956
nicholas@2498 1957 this.stop = function (stopTime) {
nicholas@2224 1958 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
nicholas@2498 1959 if (this.bufferNode != undefined) {
nicholas@2498 1960 this.metric.stopListening(audioEngineContext.timer.getTestTime(), this.getCurrentPosition());
nicholas@2498 1961 this.bufferNode.stop(stopTime);
nicholas@2498 1962 this.bufferNode = undefined;
nicholas@2498 1963 }
nicholas@2507 1964 this.outputGain.gain.setValueAtTime(0.0, stopTime);
nicholas@2224 1965 this.interfaceDOM.stopPlayback();
nicholas@2498 1966 };
nicholas@2498 1967
nicholas@2498 1968 this.getCurrentPosition = function () {
nicholas@2498 1969 var time = audioEngineContext.timer.getTestTime();
nicholas@2498 1970 if (this.bufferNode != undefined) {
nicholas@2498 1971 var position = (time - this.bufferNode.playbackStartTime) % this.buffer.buffer.duration;
nicholas@2498 1972 if (isNaN(position)) {
nicholas@2498 1973 return 0;
nicholas@2498 1974 }
nicholas@2224 1975 return position;
nicholas@2498 1976 } else {
nicholas@2498 1977 return 0;
nicholas@2498 1978 }
nicholas@2498 1979 };
nicholas@2498 1980
nicholas@2498 1981 this.exportXMLDOM = function () {
nicholas@2498 1982 var file = storage.document.createElement('file');
nicholas@2498 1983 file.setAttribute('sampleRate', this.buffer.buffer.sampleRate);
nicholas@2498 1984 file.setAttribute('channels', this.buffer.buffer.numberOfChannels);
nicholas@2498 1985 file.setAttribute('sampleCount', this.buffer.buffer.length);
nicholas@2498 1986 file.setAttribute('duration', this.buffer.buffer.duration);
nicholas@2498 1987 this.storeDOM.appendChild(file);
nicholas@2498 1988 if (this.specification.type != 'outside-reference') {
nicholas@2498 1989 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
nicholas@2498 1990 if (interfaceXML != null) {
nicholas@2498 1991 if (interfaceXML.length == undefined) {
nicholas@2498 1992 this.storeDOM.appendChild(interfaceXML);
nicholas@2498 1993 } else {
nicholas@2498 1994 for (var i = 0; i < interfaceXML.length; i++) {
nicholas@2498 1995 this.storeDOM.appendChild(interfaceXML[i]);
nicholas@2498 1996 }
nicholas@2498 1997 }
nicholas@2498 1998 }
nicholas@2498 1999 if (this.commentDOM != null) {
nicholas@2498 2000 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
nicholas@2498 2001 }
nicholas@2498 2002 }
nicholas@2498 2003 var nodes = this.metric.exportXMLDOM();
nicholas@2498 2004 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
nicholas@2498 2005 for (var i = 0; i < nodes.length; i++) {
nicholas@2498 2006 mroot.appendChild(nodes[i]);
nicholas@2498 2007 }
nicholas@2498 2008 };
nicholas@2224 2009 }
nicholas@2224 2010
nicholas@2498 2011 function timer() {
nicholas@2498 2012 /* Timer object used in audioEngine to keep track of session timings
nicholas@2498 2013 * Uses the timer of the web audio API, so sample resolution
nicholas@2498 2014 */
nicholas@2498 2015 this.testStarted = false;
nicholas@2498 2016 this.testStartTime = 0;
nicholas@2498 2017 this.testDuration = 0;
nicholas@2498 2018 this.minimumTestTime = 0; // No minimum test time
nicholas@2498 2019 this.startTest = function () {
nicholas@2498 2020 if (this.testStarted == false) {
nicholas@2498 2021 this.testStartTime = audioContext.currentTime;
nicholas@2498 2022 this.testStarted = true;
nicholas@2498 2023 this.updateTestTime();
nicholas@2498 2024 audioEngineContext.metric.initialiseTest();
nicholas@2498 2025 }
nicholas@2498 2026 };
nicholas@2498 2027 this.stopTest = function () {
nicholas@2498 2028 if (this.testStarted) {
nicholas@2498 2029 this.testDuration = this.getTestTime();
nicholas@2498 2030 this.testStarted = false;
nicholas@2498 2031 } else {
nicholas@2498 2032 console.log('ERR: Test tried to end before beginning');
nicholas@2498 2033 }
nicholas@2498 2034 };
nicholas@2498 2035 this.updateTestTime = function () {
nicholas@2498 2036 if (this.testStarted) {
nicholas@2498 2037 this.testDuration = audioContext.currentTime - this.testStartTime;
nicholas@2498 2038 }
nicholas@2498 2039 };
nicholas@2498 2040 this.getTestTime = function () {
nicholas@2498 2041 this.updateTestTime();
nicholas@2498 2042 return this.testDuration;
nicholas@2498 2043 };
nicholas@2224 2044 }
nicholas@2224 2045
nicholas@2498 2046 function sessionMetrics(engine, specification) {
nicholas@2498 2047 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
nicholas@2498 2048 */
nicholas@2498 2049 this.engine = engine;
nicholas@2498 2050 this.lastClicked = -1;
nicholas@2498 2051 this.data = -1;
nicholas@2498 2052 this.reset = function () {
nicholas@2498 2053 this.lastClicked = -1;
nicholas@2498 2054 this.data = -1;
nicholas@2498 2055 };
nicholas@2498 2056
nicholas@2498 2057 this.enableElementInitialPosition = false;
nicholas@2498 2058 this.enableElementListenTracker = false;
nicholas@2498 2059 this.enableElementTimer = false;
nicholas@2498 2060 this.enableElementTracker = false;
nicholas@2498 2061 this.enableFlagListenedTo = false;
nicholas@2498 2062 this.enableFlagMoved = false;
nicholas@2498 2063 this.enableTestTimer = false;
nicholas@2498 2064 // Obtain the metrics enabled
nicholas@2498 2065 for (var i = 0; i < specification.metrics.enabled.length; i++) {
nicholas@2498 2066 var node = specification.metrics.enabled[i];
nicholas@2498 2067 switch (node) {
nicholas@2498 2068 case 'testTimer':
nicholas@2498 2069 this.enableTestTimer = true;
nicholas@2498 2070 break;
nicholas@2498 2071 case 'elementTimer':
nicholas@2498 2072 this.enableElementTimer = true;
nicholas@2498 2073 break;
nicholas@2498 2074 case 'elementTracker':
nicholas@2498 2075 this.enableElementTracker = true;
nicholas@2498 2076 break;
nicholas@2498 2077 case 'elementListenTracker':
nicholas@2498 2078 this.enableElementListenTracker = true;
nicholas@2498 2079 break;
nicholas@2498 2080 case 'elementInitialPosition':
nicholas@2498 2081 this.enableElementInitialPosition = true;
nicholas@2498 2082 break;
nicholas@2498 2083 case 'elementFlagListenedTo':
nicholas@2498 2084 this.enableFlagListenedTo = true;
nicholas@2498 2085 break;
nicholas@2498 2086 case 'elementFlagMoved':
nicholas@2498 2087 this.enableFlagMoved = true;
nicholas@2498 2088 break;
nicholas@2498 2089 case 'elementFlagComments':
nicholas@2498 2090 this.enableFlagComments = true;
nicholas@2498 2091 break;
nicholas@2498 2092 }
nicholas@2498 2093 }
nicholas@2498 2094 this.initialiseTest = function () {};
nicholas@2224 2095 }
nicholas@2224 2096
nicholas@2498 2097 function metricTracker(caller) {
nicholas@2498 2098 /* Custom object to track and collect metric data
nicholas@2498 2099 * Used only inside the audioObjects object.
nicholas@2498 2100 */
nicholas@2498 2101
nicholas@2498 2102 this.listenedTimer = 0;
nicholas@2498 2103 this.listenStart = 0;
nicholas@2498 2104 this.listenHold = false;
nicholas@2498 2105 this.initialPosition = -1;
nicholas@2498 2106 this.movementTracker = [];
nicholas@2498 2107 this.listenTracker = [];
nicholas@2498 2108 this.wasListenedTo = false;
nicholas@2498 2109 this.wasMoved = false;
nicholas@2498 2110 this.hasComments = false;
nicholas@2498 2111 this.parent = caller;
nicholas@2498 2112
nicholas@2498 2113 this.initialise = function (position) {
nicholas@2498 2114 if (this.initialPosition == -1) {
nicholas@2498 2115 this.initialPosition = position;
nicholas@2498 2116 this.moved(0, position);
nicholas@2498 2117 }
nicholas@2498 2118 };
nicholas@2498 2119
nicholas@2498 2120 this.moved = function (time, position) {
nicholas@2498 2121 if (time > 0) {
nicholas@2498 2122 this.wasMoved = true;
nicholas@2498 2123 }
nicholas@2498 2124 this.movementTracker[this.movementTracker.length] = [time, position];
nicholas@2498 2125 };
nicholas@2498 2126
nicholas@2498 2127 this.startListening = function (time) {
nicholas@2498 2128 if (this.listenHold == false) {
nicholas@2498 2129 this.wasListenedTo = true;
nicholas@2498 2130 this.listenStart = time;
nicholas@2498 2131 this.listenHold = true;
nicholas@2498 2132
nicholas@2498 2133 var evnt = document.createElement('event');
nicholas@2498 2134 var testTime = document.createElement('testTime');
nicholas@2498 2135 testTime.setAttribute('start', time);
nicholas@2498 2136 var bufferTime = document.createElement('bufferTime');
nicholas@2498 2137 bufferTime.setAttribute('start', this.parent.getCurrentPosition());
nicholas@2498 2138 evnt.appendChild(testTime);
nicholas@2498 2139 evnt.appendChild(bufferTime);
nicholas@2498 2140 this.listenTracker.push(evnt);
nicholas@2498 2141
nicholas@2498 2142 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
nicholas@2498 2143 }
nicholas@2498 2144 };
nicholas@2498 2145
nicholas@2498 2146 this.stopListening = function (time, bufferStopTime) {
nicholas@2498 2147 if (this.listenHold == true) {
nicholas@2498 2148 var diff = time - this.listenStart;
nicholas@2498 2149 this.listenedTimer += (diff);
nicholas@2498 2150 this.listenStart = 0;
nicholas@2498 2151 this.listenHold = false;
nicholas@2498 2152
nicholas@2498 2153 var evnt = this.listenTracker[this.listenTracker.length - 1];
nicholas@2498 2154 var testTime = evnt.getElementsByTagName('testTime')[0];
nicholas@2498 2155 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
nicholas@2498 2156 testTime.setAttribute('stop', time);
nicholas@2498 2157 if (bufferStopTime == undefined) {
nicholas@2498 2158 bufferTime.setAttribute('stop', this.parent.getCurrentPosition());
nicholas@2498 2159 } else {
nicholas@2498 2160 bufferTime.setAttribute('stop', bufferStopTime);
nicholas@2498 2161 }
nicholas@2498 2162 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
nicholas@2498 2163 }
nicholas@2498 2164 };
nicholas@2498 2165
nicholas@2498 2166 this.exportXMLDOM = function () {
nicholas@2498 2167 var storeDOM = [];
nicholas@2498 2168 if (audioEngineContext.metric.enableElementTimer) {
nicholas@2498 2169 var mElementTimer = storage.document.createElement('metricresult');
nicholas@2498 2170 mElementTimer.setAttribute('name', 'enableElementTimer');
nicholas@2498 2171 mElementTimer.textContent = this.listenedTimer;
nicholas@2498 2172 storeDOM.push(mElementTimer);
nicholas@2498 2173 }
nicholas@2498 2174 if (audioEngineContext.metric.enableElementTracker) {
nicholas@2498 2175 var elementTrackerFull = storage.document.createElement('metricresult');
nicholas@2498 2176 elementTrackerFull.setAttribute('name', 'elementTrackerFull');
nicholas@2498 2177 for (var k = 0; k < this.movementTracker.length; k++) {
nicholas@2498 2178 var timePos = storage.document.createElement('movement');
nicholas@2498 2179 timePos.setAttribute("time", this.movementTracker[k][0]);
nicholas@2498 2180 timePos.setAttribute("value", this.movementTracker[k][1]);
nicholas@2498 2181 elementTrackerFull.appendChild(timePos);
nicholas@2498 2182 }
nicholas@2498 2183 storeDOM.push(elementTrackerFull);
nicholas@2498 2184 }
nicholas@2498 2185 if (audioEngineContext.metric.enableElementListenTracker) {
nicholas@2498 2186 var elementListenTracker = storage.document.createElement('metricresult');
nicholas@2498 2187 elementListenTracker.setAttribute('name', 'elementListenTracker');
nicholas@2498 2188 for (var k = 0; k < this.listenTracker.length; k++) {
nicholas@2498 2189 elementListenTracker.appendChild(this.listenTracker[k]);
nicholas@2498 2190 }
nicholas@2498 2191 storeDOM.push(elementListenTracker);
nicholas@2498 2192 }
nicholas@2498 2193 if (audioEngineContext.metric.enableElementInitialPosition) {
nicholas@2498 2194 var elementInitial = storage.document.createElement('metricresult');
nicholas@2498 2195 elementInitial.setAttribute('name', 'elementInitialPosition');
nicholas@2498 2196 elementInitial.textContent = this.initialPosition;
nicholas@2498 2197 storeDOM.push(elementInitial);
nicholas@2498 2198 }
nicholas@2498 2199 if (audioEngineContext.metric.enableFlagListenedTo) {
nicholas@2498 2200 var flagListenedTo = storage.document.createElement('metricresult');
nicholas@2498 2201 flagListenedTo.setAttribute('name', 'elementFlagListenedTo');
nicholas@2498 2202 flagListenedTo.textContent = this.wasListenedTo;
nicholas@2498 2203 storeDOM.push(flagListenedTo);
nicholas@2498 2204 }
nicholas@2498 2205 if (audioEngineContext.metric.enableFlagMoved) {
nicholas@2498 2206 var flagMoved = storage.document.createElement('metricresult');
nicholas@2498 2207 flagMoved.setAttribute('name', 'elementFlagMoved');
nicholas@2498 2208 flagMoved.textContent = this.wasMoved;
nicholas@2498 2209 storeDOM.push(flagMoved);
nicholas@2498 2210 }
nicholas@2498 2211 if (audioEngineContext.metric.enableFlagComments) {
nicholas@2498 2212 var flagComments = storage.document.createElement('metricresult');
nicholas@2498 2213 flagComments.setAttribute('name', 'elementFlagComments');
nicholas@2498 2214 if (this.parent.commentDOM == null) {
nicholas@2498 2215 flag.textContent = 'false';
nicholas@2498 2216 } else if (this.parent.commentDOM.textContent.length == 0) {
nicholas@2498 2217 flag.textContent = 'false';
nicholas@2498 2218 } else {
nicholas@2498 2219 flag.textContet = 'true';
nicholas@2498 2220 }
nicholas@2498 2221 storeDOM.push(flagComments);
nicholas@2498 2222 }
nicholas@2498 2223 return storeDOM;
nicholas@2498 2224 };
nicholas@2224 2225 }
nicholas@2498 2226
nicholas@2224 2227 function Interface(specificationObject) {
nicholas@2498 2228 // This handles the bindings between the interface and the audioEngineContext;
nicholas@2498 2229 this.specification = specificationObject;
nicholas@2498 2230 this.insertPoint = document.getElementById("topLevelBody");
nicholas@2498 2231
nicholas@2498 2232 this.newPage = function (audioHolderObject, store) {
nicholas@2498 2233 audioEngineContext.newTestPage(audioHolderObject, store);
nicholas@2498 2234 interfaceContext.commentBoxes.deleteCommentBoxes();
nicholas@2498 2235 interfaceContext.deleteCommentQuestions();
nicholas@2498 2236 loadTest(audioHolderObject, store);
nicholas@2498 2237 };
nicholas@2498 2238
nicholas@2498 2239 // Bounded by interface!!
nicholas@2498 2240 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
nicholas@2498 2241 // For example, APE returns the slider position normalised in a <value> tag.
nicholas@2498 2242 this.interfaceObjects = [];
nicholas@2498 2243 this.interfaceObject = function () {};
nicholas@2498 2244
nicholas@2498 2245 this.resizeWindow = function (event) {
nicholas@2498 2246 popup.resize(event);
nicholas@2352 2247 this.volume.resize();
nicholas@2360 2248 this.lightbox.resize();
nicholas@2498 2249 for (var i = 0; i < this.commentBoxes.length; i++) {
nicholas@2498 2250 this.commentBoxes[i].resize();
nicholas@2498 2251 }
nicholas@2498 2252 for (var i = 0; i < this.commentQuestions.length; i++) {
nicholas@2498 2253 this.commentQuestions[i].resize();
nicholas@2498 2254 }
nicholas@2498 2255 try {
nicholas@2498 2256 resizeWindow(event);
nicholas@2498 2257 } catch (err) {
nicholas@2498 2258 console.log("Warning - Interface does not have Resize option");
nicholas@2498 2259 console.log(err);
nicholas@2498 2260 }
nicholas@2498 2261 };
nicholas@2498 2262
nicholas@2498 2263 this.returnNavigator = function () {
nicholas@2498 2264 var node = storage.document.createElement("navigator");
nicholas@2498 2265 var platform = storage.document.createElement("platform");
nicholas@2498 2266 platform.textContent = navigator.platform;
nicholas@2498 2267 var vendor = storage.document.createElement("vendor");
nicholas@2498 2268 vendor.textContent = navigator.vendor;
nicholas@2498 2269 var userAgent = storage.document.createElement("uagent");
nicholas@2498 2270 userAgent.textContent = navigator.userAgent;
nicholas@2224 2271 var screen = storage.document.createElement("window");
nicholas@2498 2272 screen.setAttribute('innerWidth', window.innerWidth);
nicholas@2498 2273 screen.setAttribute('innerHeight', window.innerHeight);
nicholas@2498 2274 node.appendChild(platform);
nicholas@2498 2275 node.appendChild(vendor);
nicholas@2498 2276 node.appendChild(userAgent);
nicholas@2224 2277 node.appendChild(screen);
nicholas@2498 2278 return node;
nicholas@2498 2279 };
nicholas@2498 2280
nicholas@2498 2281 this.returnDateNode = function () {
nicholas@2224 2282 // Create an XML Node for the Date and Time a test was conducted
nicholas@2224 2283 // Structure is
nicholas@2224 2284 // <datetime>
nicholas@2224 2285 // <date year="##" month="##" day="##">DD/MM/YY</date>
nicholas@2224 2286 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
nicholas@2224 2287 // </datetime>
nicholas@2224 2288 var dateTime = new Date();
nicholas@2224 2289 var hold = storage.document.createElement("datetime");
nicholas@2224 2290 var date = storage.document.createElement("date");
nicholas@2224 2291 var time = storage.document.createElement("time");
nicholas@2498 2292 date.setAttribute('year', dateTime.getFullYear());
nicholas@2498 2293 date.setAttribute('month', dateTime.getMonth() + 1);
nicholas@2498 2294 date.setAttribute('day', dateTime.getDate());
nicholas@2498 2295 time.setAttribute('hour', dateTime.getHours());
nicholas@2498 2296 time.setAttribute('minute', dateTime.getMinutes());
nicholas@2498 2297 time.setAttribute('secs', dateTime.getSeconds());
nicholas@2498 2298
nicholas@2224 2299 hold.appendChild(date);
nicholas@2224 2300 hold.appendChild(time);
nicholas@2224 2301 return hold;
nicholas@2224 2302
nicholas@2224 2303 }
nicholas@2498 2304
nicholas@2360 2305 this.lightbox = {
nicholas@2360 2306 parent: this,
nicholas@2360 2307 root: document.createElement("div"),
nicholas@2360 2308 content: document.createElement("div"),
nicholas@2360 2309 accept: document.createElement("button"),
nicholas@2360 2310 blanker: document.createElement("div"),
nicholas@2498 2311 post: function (type, message) {
nicholas@2498 2312 switch (type) {
nicholas@2360 2313 case "Error":
nicholas@2360 2314 this.content.className = "lightbox-error";
nicholas@2360 2315 break;
nicholas@2360 2316 case "Warning":
nicholas@2360 2317 this.content.className = "lightbox-warning";
nicholas@2360 2318 break;
nicholas@2360 2319 default:
nicholas@2360 2320 this.content.className = "lightbox-message";
nicholas@2360 2321 break;
nicholas@2360 2322 }
nicholas@2360 2323 var msg = document.createElement("p");
nicholas@2360 2324 msg.textContent = message;
nicholas@2360 2325 this.content.appendChild(msg);
nicholas@2360 2326 this.show();
nicholas@2360 2327 },
nicholas@2498 2328 show: function () {
nicholas@2360 2329 this.root.style.visibility = "visible";
nicholas@2360 2330 this.blanker.style.visibility = "visible";
nicholas@2360 2331 },
nicholas@2498 2332 clear: function () {
nicholas@2360 2333 this.root.style.visibility = "";
nicholas@2360 2334 this.blanker.style.visibility = "";
nicholas@2360 2335 this.content.textContent = "";
nicholas@2360 2336 },
nicholas@2498 2337 handleEvent: function (event) {
nicholas@2360 2338 if (event.currentTarget == this.accept) {
nicholas@2360 2339 this.clear();
nicholas@2360 2340 }
nicholas@2360 2341 },
nicholas@2498 2342 resize: function (event) {
nicholas@2498 2343 this.root.style.left = (window.innerWidth / 2) - 250 + 'px';
nicholas@2360 2344 }
nicholas@2360 2345 }
nicholas@2498 2346
nicholas@2360 2347 this.lightbox.root.appendChild(this.lightbox.content);
nicholas@2360 2348 this.lightbox.root.appendChild(this.lightbox.accept);
nicholas@2360 2349 this.lightbox.root.className = "popupHolder";
nicholas@2360 2350 this.lightbox.root.id = "lightbox-root";
nicholas@2360 2351 this.lightbox.accept.className = "popupButton";
nicholas@2360 2352 this.lightbox.accept.style.bottom = "10px";
nicholas@2360 2353 this.lightbox.accept.textContent = "OK";
nicholas@2360 2354 this.lightbox.accept.style.left = "237.5px";
nicholas@2498 2355 this.lightbox.accept.addEventListener("click", this.lightbox);
nicholas@2360 2356 this.lightbox.blanker.className = "testHalt";
nicholas@2360 2357 this.lightbox.blanker.id = "lightbox-blanker";
nicholas@2360 2358 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
nicholas@2360 2359 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
nicholas@2498 2360
nicholas@2498 2361 this.commentBoxes = new function () {
nicholas@2224 2362 this.boxes = [];
nicholas@2224 2363 this.injectPoint = null;
nicholas@2498 2364 this.elementCommentBox = function (audioObject) {
nicholas@2224 2365 var element = audioObject.specification;
nicholas@2224 2366 this.audioObject = audioObject;
nicholas@2224 2367 this.id = audioObject.id;
nicholas@2224 2368 var audioHolderObject = audioObject.specification.parent;
nicholas@2224 2369 // Create document objects to hold the comment boxes
nicholas@2224 2370 this.trackComment = document.createElement('div');
nicholas@2224 2371 this.trackComment.className = 'comment-div';
nicholas@2498 2372 this.trackComment.id = 'comment-div-' + audioObject.id;
nicholas@2224 2373 // Create a string next to each comment asking for a comment
nicholas@2224 2374 this.trackString = document.createElement('span');
nicholas@2498 2375 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
nicholas@2224 2376 // Create the HTML5 comment box 'textarea'
nicholas@2224 2377 this.trackCommentBox = document.createElement('textarea');
nicholas@2224 2378 this.trackCommentBox.rows = '4';
nicholas@2224 2379 this.trackCommentBox.cols = '100';
nicholas@2498 2380 this.trackCommentBox.name = 'trackComment' + audioObject.id;
nicholas@2224 2381 this.trackCommentBox.className = 'trackComment';
nicholas@2224 2382 var br = document.createElement('br');
nicholas@2224 2383 // Add to the holder.
nicholas@2224 2384 this.trackComment.appendChild(this.trackString);
nicholas@2224 2385 this.trackComment.appendChild(br);
nicholas@2224 2386 this.trackComment.appendChild(this.trackCommentBox);
nicholas@2224 2387
nicholas@2498 2388 this.exportXMLDOM = function () {
nicholas@2224 2389 var root = document.createElement('comment');
nicholas@2224 2390 var question = document.createElement('question');
nicholas@2224 2391 question.textContent = this.trackString.textContent;
nicholas@2224 2392 var response = document.createElement('response');
nicholas@2224 2393 response.textContent = this.trackCommentBox.value;
nicholas@2498 2394 console.log("Comment frag-" + this.id + ": " + response.textContent);
nicholas@2224 2395 root.appendChild(question);
nicholas@2224 2396 root.appendChild(response);
nicholas@2224 2397 return root;
nicholas@2224 2398 };
nicholas@2498 2399 this.resize = function () {
nicholas@2498 2400 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2401 if (boxwidth >= 600) {
nicholas@2224 2402 boxwidth = 600;
nicholas@2498 2403 } else if (boxwidth < 400) {
nicholas@2224 2404 boxwidth = 400;
nicholas@2224 2405 }
nicholas@2498 2406 this.trackComment.style.width = boxwidth + "px";
nicholas@2498 2407 this.trackCommentBox.style.width = boxwidth - 6 + "px";
nicholas@2224 2408 };
nicholas@2224 2409 this.resize();
nicholas@2224 2410 };
nicholas@2498 2411 this.createCommentBox = function (audioObject) {
nicholas@2224 2412 var node = new this.elementCommentBox(audioObject);
nicholas@2224 2413 this.boxes.push(node);
nicholas@2224 2414 audioObject.commentDOM = node;
nicholas@2224 2415 return node;
nicholas@2224 2416 };
nicholas@2498 2417 this.sortCommentBoxes = function () {
nicholas@2498 2418 this.boxes.sort(function (a, b) {
nicholas@2498 2419 return a.id - b.id;
nicholas@2498 2420 });
nicholas@2224 2421 };
nicholas@2224 2422
nicholas@2498 2423 this.showCommentBoxes = function (inject, sort) {
nicholas@2224 2424 this.injectPoint = inject;
nicholas@2498 2425 if (sort) {
nicholas@2498 2426 this.sortCommentBoxes();
nicholas@2498 2427 }
nicholas@2224 2428 for (var box of this.boxes) {
nicholas@2224 2429 inject.appendChild(box.trackComment);
nicholas@2224 2430 }
nicholas@2224 2431 };
nicholas@2224 2432
nicholas@2498 2433 this.deleteCommentBoxes = function () {
nicholas@2224 2434 if (this.injectPoint != null) {
nicholas@2224 2435 for (var box of this.boxes) {
nicholas@2224 2436 this.injectPoint.removeChild(box.trackComment);
nicholas@2224 2437 }
nicholas@2224 2438 this.injectPoint = null;
nicholas@2224 2439 }
nicholas@2224 2440 this.boxes = [];
nicholas@2224 2441 };
nicholas@2224 2442 }
nicholas@2498 2443
nicholas@2498 2444 this.commentQuestions = [];
nicholas@2498 2445
nicholas@2498 2446 this.commentBox = function (commentQuestion) {
nicholas@2498 2447 this.specification = commentQuestion;
nicholas@2498 2448 // Create document objects to hold the comment boxes
nicholas@2498 2449 this.holder = document.createElement('div');
nicholas@2498 2450 this.holder.className = 'comment-div';
nicholas@2498 2451 // Create a string next to each comment asking for a comment
nicholas@2498 2452 this.string = document.createElement('span');
nicholas@2498 2453 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2454 // Create the HTML5 comment box 'textarea'
nicholas@2498 2455 this.textArea = document.createElement('textarea');
nicholas@2498 2456 this.textArea.rows = '4';
nicholas@2498 2457 this.textArea.cols = '100';
nicholas@2498 2458 this.textArea.className = 'trackComment';
nicholas@2498 2459 var br = document.createElement('br');
nicholas@2498 2460 // Add to the holder.
nicholas@2498 2461 this.holder.appendChild(this.string);
nicholas@2498 2462 this.holder.appendChild(br);
nicholas@2498 2463 this.holder.appendChild(this.textArea);
nicholas@2498 2464
nicholas@2498 2465 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2466 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2467 root.id = this.specification.id;
nicholas@2498 2468 root.setAttribute('type', this.specification.type);
nicholas@2498 2469 console.log("Question: " + this.string.textContent);
nicholas@2498 2470 console.log("Response: " + root.textContent);
nicholas@2224 2471 var question = storePoint.parent.document.createElement('question');
nicholas@2224 2472 question.textContent = this.string.textContent;
nicholas@2224 2473 var response = storePoint.parent.document.createElement('response');
nicholas@2224 2474 response.textContent = this.textArea.value;
nicholas@2224 2475 root.appendChild(question);
nicholas@2224 2476 root.appendChild(response);
nicholas@2224 2477 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2478 return root;
nicholas@2498 2479 };
nicholas@2498 2480 this.resize = function () {
nicholas@2498 2481 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2482 if (boxwidth >= 600) {
nicholas@2498 2483 boxwidth = 600;
nicholas@2498 2484 } else if (boxwidth < 400) {
nicholas@2498 2485 boxwidth = 400;
nicholas@2498 2486 }
nicholas@2498 2487 this.holder.style.width = boxwidth + "px";
nicholas@2498 2488 this.textArea.style.width = boxwidth - 6 + "px";
nicholas@2498 2489 };
nicholas@2498 2490 this.resize();
nicholas@2498 2491 };
nicholas@2498 2492
nicholas@2498 2493 this.radioBox = function (commentQuestion) {
nicholas@2498 2494 this.specification = commentQuestion;
nicholas@2498 2495 // Create document objects to hold the comment boxes
nicholas@2498 2496 this.holder = document.createElement('div');
nicholas@2498 2497 this.holder.className = 'comment-div';
nicholas@2498 2498 // Create a string next to each comment asking for a comment
nicholas@2498 2499 this.string = document.createElement('span');
nicholas@2498 2500 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2501 var br = document.createElement('br');
nicholas@2498 2502 // Add to the holder.
nicholas@2498 2503 this.holder.appendChild(this.string);
nicholas@2498 2504 this.holder.appendChild(br);
nicholas@2498 2505 this.options = [];
nicholas@2498 2506 this.inputs = document.createElement('div');
nicholas@2498 2507 this.span = document.createElement('div');
nicholas@2498 2508 this.inputs.align = 'center';
nicholas@2498 2509 this.inputs.style.marginLeft = '12px';
nicholas@2294 2510 this.inputs.className = "comment-radio-inputs-holder";
nicholas@2498 2511 this.span.style.marginLeft = '12px';
nicholas@2498 2512 this.span.align = 'center';
nicholas@2498 2513 this.span.style.marginTop = '15px';
nicholas@2294 2514 this.span.className = "comment-radio-span-holder";
nicholas@2498 2515
nicholas@2498 2516 var optCount = commentQuestion.options.length;
nicholas@2498 2517 for (var optNode of commentQuestion.options) {
nicholas@2498 2518 var div = document.createElement('div');
nicholas@2498 2519 div.style.width = '80px';
nicholas@2498 2520 div.style.float = 'left';
nicholas@2498 2521 var input = document.createElement('input');
nicholas@2498 2522 input.type = 'radio';
nicholas@2498 2523 input.name = commentQuestion.id;
nicholas@2498 2524 input.setAttribute('setvalue', optNode.name);
nicholas@2498 2525 input.className = 'comment-radio';
nicholas@2498 2526 div.appendChild(input);
nicholas@2498 2527 this.inputs.appendChild(div);
nicholas@2498 2528
nicholas@2498 2529
nicholas@2498 2530 div = document.createElement('div');
nicholas@2498 2531 div.style.width = '80px';
nicholas@2498 2532 div.style.float = 'left';
nicholas@2498 2533 div.align = 'center';
nicholas@2498 2534 var span = document.createElement('span');
nicholas@2498 2535 span.textContent = optNode.text;
nicholas@2498 2536 span.className = 'comment-radio-span';
nicholas@2498 2537 div.appendChild(span);
nicholas@2498 2538 this.span.appendChild(div);
nicholas@2498 2539 this.options.push(input);
nicholas@2498 2540 }
nicholas@2498 2541 this.holder.appendChild(this.span);
nicholas@2498 2542 this.holder.appendChild(this.inputs);
nicholas@2498 2543
nicholas@2498 2544 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2545 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2546 root.id = this.specification.id;
nicholas@2498 2547 root.setAttribute('type', this.specification.type);
nicholas@2498 2548 var question = document.createElement('question');
nicholas@2498 2549 question.textContent = this.string.textContent;
nicholas@2498 2550 var response = document.createElement('response');
nicholas@2498 2551 var i = 0;
nicholas@2498 2552 while (this.options[i].checked == false) {
nicholas@2498 2553 i++;
nicholas@2498 2554 if (i >= this.options.length) {
nicholas@2498 2555 break;
nicholas@2498 2556 }
nicholas@2498 2557 }
nicholas@2498 2558 if (i >= this.options.length) {
nicholas@2498 2559 response.textContent = 'null';
nicholas@2498 2560 } else {
nicholas@2498 2561 response.textContent = this.options[i].getAttribute('setvalue');
nicholas@2498 2562 response.setAttribute('number', i);
nicholas@2498 2563 }
nicholas@2498 2564 console.log('Comment: ' + question.textContent);
nicholas@2498 2565 console.log('Response: ' + response.textContent);
nicholas@2498 2566 root.appendChild(question);
nicholas@2498 2567 root.appendChild(response);
nicholas@2224 2568 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2569 return root;
nicholas@2498 2570 };
nicholas@2498 2571 this.resize = function () {
nicholas@2498 2572 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2573 if (boxwidth >= 600) {
nicholas@2498 2574 boxwidth = 600;
nicholas@2498 2575 } else if (boxwidth < 400) {
nicholas@2498 2576 boxwidth = 400;
nicholas@2498 2577 }
nicholas@2498 2578 this.holder.style.width = boxwidth + "px";
nicholas@2498 2579 var text = this.holder.getElementsByClassName("comment-radio-span-holder")[0];
nicholas@2498 2580 var options = this.holder.getElementsByClassName("comment-radio-inputs-holder")[0];
nicholas@2498 2581 var optCount = options.childElementCount;
nicholas@2498 2582 var spanMargin = Math.floor(((boxwidth - 20 - (optCount * 80)) / (optCount)) / 2) + 'px';
nicholas@2498 2583 var options = options.firstChild;
nicholas@2498 2584 var text = text.firstChild;
nicholas@2498 2585 options.style.marginRight = spanMargin;
nicholas@2498 2586 options.style.marginLeft = spanMargin;
nicholas@2498 2587 text.style.marginRight = spanMargin;
nicholas@2498 2588 text.style.marginLeft = spanMargin;
nicholas@2498 2589 while (options.nextSibling != undefined) {
nicholas@2498 2590 options = options.nextSibling;
nicholas@2498 2591 text = text.nextSibling;
nicholas@2498 2592 options.style.marginRight = spanMargin;
nicholas@2498 2593 options.style.marginLeft = spanMargin;
nicholas@2498 2594 text.style.marginRight = spanMargin;
nicholas@2498 2595 text.style.marginLeft = spanMargin;
nicholas@2498 2596 }
nicholas@2498 2597 };
nicholas@2498 2598 this.resize();
nicholas@2498 2599 };
nicholas@2498 2600
nicholas@2498 2601 this.checkboxBox = function (commentQuestion) {
nicholas@2498 2602 this.specification = commentQuestion;
nicholas@2498 2603 // Create document objects to hold the comment boxes
nicholas@2498 2604 this.holder = document.createElement('div');
nicholas@2498 2605 this.holder.className = 'comment-div';
nicholas@2498 2606 // Create a string next to each comment asking for a comment
nicholas@2498 2607 this.string = document.createElement('span');
nicholas@2498 2608 this.string.innerHTML = commentQuestion.statement;
nicholas@2498 2609 var br = document.createElement('br');
nicholas@2498 2610 // Add to the holder.
nicholas@2498 2611 this.holder.appendChild(this.string);
nicholas@2498 2612 this.holder.appendChild(br);
nicholas@2498 2613 this.options = [];
nicholas@2498 2614 this.inputs = document.createElement('div');
nicholas@2498 2615 this.span = document.createElement('div');
nicholas@2498 2616 this.inputs.align = 'center';
nicholas@2498 2617 this.inputs.style.marginLeft = '12px';
nicholas@2294 2618 this.inputs.className = "comment-checkbox-inputs-holder";
nicholas@2498 2619 this.span.style.marginLeft = '12px';
nicholas@2498 2620 this.span.align = 'center';
nicholas@2498 2621 this.span.style.marginTop = '15px';
nicholas@2294 2622 this.span.className = "comment-checkbox-span-holder";
nicholas@2498 2623
nicholas@2498 2624 var optCount = commentQuestion.options.length;
nicholas@2498 2625 for (var i = 0; i < optCount; i++) {
nicholas@2498 2626 var div = document.createElement('div');
nicholas@2498 2627 div.style.width = '80px';
nicholas@2498 2628 div.style.float = 'left';
nicholas@2498 2629 var input = document.createElement('input');
nicholas@2498 2630 input.type = 'checkbox';
nicholas@2498 2631 input.name = commentQuestion.id;
nicholas@2498 2632 input.setAttribute('setvalue', commentQuestion.options[i].name);
nicholas@2498 2633 input.className = 'comment-radio';
nicholas@2498 2634 div.appendChild(input);
nicholas@2498 2635 this.inputs.appendChild(div);
nicholas@2498 2636
nicholas@2498 2637
nicholas@2498 2638 div = document.createElement('div');
nicholas@2498 2639 div.style.width = '80px';
nicholas@2498 2640 div.style.float = 'left';
nicholas@2498 2641 div.align = 'center';
nicholas@2498 2642 var span = document.createElement('span');
nicholas@2498 2643 span.textContent = commentQuestion.options[i].text;
nicholas@2498 2644 span.className = 'comment-radio-span';
nicholas@2498 2645 div.appendChild(span);
nicholas@2498 2646 this.span.appendChild(div);
nicholas@2498 2647 this.options.push(input);
nicholas@2498 2648 }
nicholas@2498 2649 this.holder.appendChild(this.span);
nicholas@2498 2650 this.holder.appendChild(this.inputs);
nicholas@2498 2651
nicholas@2498 2652 this.exportXMLDOM = function (storePoint) {
nicholas@2498 2653 var root = storePoint.parent.document.createElement('comment');
nicholas@2498 2654 root.id = this.specification.id;
nicholas@2498 2655 root.setAttribute('type', this.specification.type);
nicholas@2498 2656 var question = document.createElement('question');
nicholas@2498 2657 question.textContent = this.string.textContent;
nicholas@2498 2658 root.appendChild(question);
nicholas@2498 2659 console.log('Comment: ' + question.textContent);
nicholas@2498 2660 for (var i = 0; i < this.options.length; i++) {
nicholas@2498 2661 var response = document.createElement('response');
nicholas@2498 2662 response.textContent = this.options[i].checked;
nicholas@2498 2663 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
nicholas@2498 2664 root.appendChild(response);
nicholas@2498 2665 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
nicholas@2498 2666 }
nicholas@2224 2667 storePoint.XMLDOM.appendChild(root);
nicholas@2498 2668 return root;
nicholas@2498 2669 };
nicholas@2498 2670 this.resize = function () {
nicholas@2498 2671 var boxwidth = (window.innerWidth - 100) / 2;
nicholas@2498 2672 if (boxwidth >= 600) {
nicholas@2498 2673 boxwidth = 600;
nicholas@2498 2674 } else if (boxwidth < 400) {
nicholas@2498 2675 boxwidth = 400;
nicholas@2498 2676 }
nicholas@2498 2677 this.holder.style.width = boxwidth + "px";
nicholas@2498 2678 var text = this.holder.getElementsByClassName("comment-checkbox-span-holder")[0];
nicholas@2498 2679 var options = this.holder.getElementsByClassName("comment-checkbox-inputs-holder")[0];
nicholas@2498 2680 var optCount = options.childElementCount;
nicholas@2498 2681 var spanMargin = Math.floor(((boxwidth - 20 - (optCount * 80)) / (optCount)) / 2) + 'px';
nicholas@2498 2682 var options = options.firstChild;
nicholas@2498 2683 var text = text.firstChild;
nicholas@2498 2684 options.style.marginRight = spanMargin;
nicholas@2498 2685 options.style.marginLeft = spanMargin;
nicholas@2498 2686 text.style.marginRight = spanMargin;
nicholas@2498 2687 text.style.marginLeft = spanMargin;
nicholas@2498 2688 while (options.nextSibling != undefined) {
nicholas@2498 2689 options = options.nextSibling;
nicholas@2498 2690 text = text.nextSibling;
nicholas@2498 2691 options.style.marginRight = spanMargin;
nicholas@2498 2692 options.style.marginLeft = spanMargin;
nicholas@2498 2693 text.style.marginRight = spanMargin;
nicholas@2498 2694 text.style.marginLeft = spanMargin;
nicholas@2498 2695 }
nicholas@2498 2696 };
nicholas@2498 2697 this.resize();
nicholas@2498 2698 };
nicholas@2498 2699
n@2579 2700 this.sliderBox = function (commentQuestion) {
n@2579 2701 this.specification = commentQuestion;
n@2579 2702 this.holder = document.createElement("div");
n@2579 2703 this.holder.className = 'comment-div';
n@2579 2704 this.string = document.createElement("span");
n@2579 2705 this.string.innerHTML = commentQuestion.statement;
n@2579 2706 this.slider = document.createElement("input");
n@2579 2707 this.slider.type = "range";
n@2579 2708 this.slider.min = commentQuestion.min;
n@2579 2709 this.slider.max = commentQuestion.max;
n@2579 2710 this.slider.step = commentQuestion.step;
n@2579 2711 this.slider.value = commentQuestion.value;
n@2579 2712 var br = document.createElement('br');
n@2579 2713
n@2580 2714 var textHolder = document.createElement("div");
n@2580 2715 textHolder.className = "comment-slider-text-holder";
n@2580 2716
n@2580 2717 this.leftText = document.createElement("span");
n@2580 2718 this.leftText.textContent = commentQuestion.leftText;
n@2580 2719 this.rightText = document.createElement("span");
n@2580 2720 this.rightText.textContent = commentQuestion.rightText;
n@2580 2721 textHolder.appendChild(this.leftText);
n@2580 2722 textHolder.appendChild(this.rightText);
n@2580 2723
n@2579 2724 this.holder.appendChild(this.string);
n@2579 2725 this.holder.appendChild(br);
n@2579 2726 this.holder.appendChild(this.slider);
n@2580 2727 this.holder.appendChild(textHolder);
n@2579 2728
n@2579 2729 this.exportXMLDOM = function (storePoint) {
n@2579 2730 var root = storePoint.parent.document.createElement('comment');
n@2579 2731 root.id = this.specification.id;
n@2579 2732 root.setAttribute('type', this.specification.type);
n@2579 2733 console.log("Question: " + this.string.textContent);
n@2579 2734 console.log("Response: " + this.slider.value);
n@2579 2735 var question = storePoint.parent.document.createElement('question');
n@2579 2736 question.textContent = this.string.textContent;
n@2579 2737 var response = storePoint.parent.document.createElement('response');
n@2579 2738 response.textContent = this.slider.value;
n@2579 2739 root.appendChild(question);
n@2579 2740 root.appendChild(response);
n@2579 2741 storePoint.XMLDOM.appendChild(root);
n@2579 2742 return root;
n@2579 2743 };
n@2579 2744 this.resize = function () {
n@2579 2745 var boxwidth = (window.innerWidth - 100) / 2;
n@2579 2746 if (boxwidth >= 600) {
n@2579 2747 boxwidth = 600;
n@2579 2748 } else if (boxwidth < 400) {
n@2579 2749 boxwidth = 400;
n@2579 2750 }
n@2579 2751 this.holder.style.width = boxwidth + "px";
n@2579 2752 this.slider.style.width = boxwidth - 24 + "px";
n@2579 2753 };
n@2579 2754 this.resize();
n@2579 2755 };
n@2579 2756
nicholas@2498 2757 this.createCommentQuestion = function (element) {
nicholas@2498 2758 var node;
nicholas@2498 2759 if (element.type == 'question') {
nicholas@2498 2760 node = new this.commentBox(element);
nicholas@2498 2761 } else if (element.type == 'radio') {
nicholas@2498 2762 node = new this.radioBox(element);
nicholas@2498 2763 } else if (element.type == 'checkbox') {
nicholas@2498 2764 node = new this.checkboxBox(element);
n@2579 2765 } else if (element.type == 'slider') {
n@2579 2766 node = new this.sliderBox(element);
nicholas@2498 2767 }
nicholas@2498 2768 this.commentQuestions.push(node);
nicholas@2498 2769 return node;
nicholas@2498 2770 };
nicholas@2498 2771
nicholas@2498 2772 this.deleteCommentQuestions = function () {
nicholas@2498 2773 this.commentQuestions = [];
nicholas@2498 2774 };
nicholas@2498 2775
nicholas@2498 2776 this.outsideReferenceDOM = function (audioObject, index, inject) {
nicholas@2224 2777 this.parent = audioObject;
nicholas@2224 2778 this.outsideReferenceHolder = document.createElement('button');
nicholas@2224 2779 this.outsideReferenceHolder.className = 'outside-reference';
nicholas@2498 2780 this.outsideReferenceHolder.setAttribute('track-id', index);
nicholas@2409 2781 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
nicholas@2224 2782 this.outsideReferenceHolder.disabled = true;
nicholas@2224 2783
nicholas@2498 2784 this.outsideReferenceHolder.onclick = function (event) {
nicholas@2224 2785 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
nicholas@2224 2786 };
nicholas@2224 2787 inject.appendChild(this.outsideReferenceHolder);
nicholas@2498 2788 this.enable = function () {
nicholas@2498 2789 if (this.parent.state == 1) {
nicholas@2224 2790 this.outsideReferenceHolder.disabled = false;
nicholas@2224 2791 }
nicholas@2224 2792 };
nicholas@2498 2793 this.updateLoading = function (progress) {
nicholas@2498 2794 if (progress != 100) {
nicholas@2224 2795 progress = String(progress);
nicholas@2224 2796 progress = progress.split('.')[0];
nicholas@2498 2797 this.outsideReferenceHolder.textContent = progress + '%';
nicholas@2224 2798 } else {
nicholas@2409 2799 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
nicholas@2224 2800 }
nicholas@2224 2801 };
nicholas@2498 2802 this.startPlayback = function () {
nicholas@2224 2803 // Called when playback has begun
nicholas@2224 2804 $('.track-slider').removeClass('track-slider-playing');
nicholas@2224 2805 $('.comment-div').removeClass('comment-box-playing');
nicholas@2224 2806 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
nicholas@2224 2807 };
nicholas@2498 2808 this.stopPlayback = function () {
nicholas@2224 2809 // Called when playback has stopped. This gets called even if playback never started!
nicholas@2224 2810 this.outsideReferenceHolder.style.backgroundColor = "";
nicholas@2224 2811 };
nicholas@2498 2812 this.exportXMLDOM = function (audioObject) {
nicholas@2224 2813 return null;
nicholas@2224 2814 };
nicholas@2498 2815 this.getValue = function () {
nicholas@2224 2816 return 0;
nicholas@2224 2817 };
nicholas@2498 2818 this.getPresentedId = function () {
nicholas@2409 2819 return this.parent.specification.label || "Reference";
nicholas@2224 2820 };
nicholas@2498 2821 this.canMove = function () {
nicholas@2224 2822 return false;
nicholas@2224 2823 };
nicholas@2498 2824 this.error = function () {
nicholas@2498 2825 // audioObject has an error!!
nicholas@2224 2826 this.outsideReferenceHolder.textContent = "Error";
nicholas@2224 2827 this.outsideReferenceHolder.style.backgroundColor = "#F00";
nicholas@2224 2828 }
nicholas@2224 2829 }
nicholas@2498 2830
nicholas@2498 2831 this.playhead = new function () {
nicholas@2498 2832 this.object = document.createElement('div');
nicholas@2498 2833 this.object.className = 'playhead';
nicholas@2498 2834 this.object.align = 'left';
nicholas@2498 2835 var curTime = document.createElement('div');
nicholas@2498 2836 curTime.style.width = '50px';
nicholas@2498 2837 this.curTimeSpan = document.createElement('span');
nicholas@2498 2838 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2839 curTime.appendChild(this.curTimeSpan);
nicholas@2498 2840 this.object.appendChild(curTime);
nicholas@2498 2841 this.scrubberTrack = document.createElement('div');
nicholas@2498 2842 this.scrubberTrack.className = 'playhead-scrub-track';
nicholas@2498 2843
nicholas@2498 2844 this.scrubberHead = document.createElement('div');
nicholas@2498 2845 this.scrubberHead.id = 'playhead-scrubber';
nicholas@2498 2846 this.scrubberTrack.appendChild(this.scrubberHead);
nicholas@2498 2847 this.object.appendChild(this.scrubberTrack);
nicholas@2498 2848
nicholas@2498 2849 this.timePerPixel = 0;
nicholas@2498 2850 this.maxTime = 0;
nicholas@2498 2851
nicholas@2498 2852 this.playbackObject;
nicholas@2498 2853
nicholas@2498 2854 this.setTimePerPixel = function (audioObject) {
nicholas@2498 2855 //maxTime must be in seconds
nicholas@2498 2856 this.playbackObject = audioObject;
nicholas@2498 2857 this.maxTime = audioObject.buffer.buffer.duration;
nicholas@2498 2858 var width = 490; //500 - 10, 5 each side of the tracker head
nicholas@2498 2859 this.timePerPixel = this.maxTime / 490;
nicholas@2498 2860 if (this.maxTime < 60) {
nicholas@2498 2861 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2862 } else {
nicholas@2498 2863 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2864 }
nicholas@2498 2865 };
nicholas@2498 2866
nicholas@2498 2867 this.update = function () {
nicholas@2498 2868 // Update the playhead position, startPlay must be called
nicholas@2498 2869 if (this.timePerPixel > 0) {
nicholas@2498 2870 var time = this.playbackObject.getCurrentPosition();
nicholas@2498 2871 if (time > 0 && time < this.maxTime) {
nicholas@2498 2872 var width = 490;
nicholas@2498 2873 var pix = Math.floor(time / this.timePerPixel);
nicholas@2498 2874 this.scrubberHead.style.left = pix + 'px';
nicholas@2498 2875 if (this.maxTime > 60.0) {
nicholas@2498 2876 var secs = time % 60;
nicholas@2498 2877 var mins = Math.floor((time - secs) / 60);
nicholas@2498 2878 secs = secs.toString();
nicholas@2498 2879 secs = secs.substr(0, 2);
nicholas@2498 2880 mins = mins.toString();
nicholas@2498 2881 this.curTimeSpan.textContent = mins + ':' + secs;
nicholas@2498 2882 } else {
nicholas@2498 2883 time = time.toString();
nicholas@2498 2884 this.curTimeSpan.textContent = time.substr(0, 4);
nicholas@2498 2885 }
nicholas@2498 2886 } else {
nicholas@2498 2887 this.scrubberHead.style.left = '0px';
nicholas@2498 2888 if (this.maxTime < 60) {
nicholas@2498 2889 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2890 } else {
nicholas@2498 2891 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2892 }
nicholas@2498 2893 }
nicholas@2498 2894 }
nicholas@2498 2895 };
nicholas@2498 2896
nicholas@2498 2897 this.interval = undefined;
nicholas@2498 2898
nicholas@2498 2899 this.start = function () {
nicholas@2498 2900 if (this.playbackObject != undefined && this.interval == undefined) {
nicholas@2498 2901 if (this.maxTime < 60) {
nicholas@2498 2902 this.interval = setInterval(function () {
nicholas@2498 2903 interfaceContext.playhead.update();
nicholas@2498 2904 }, 10);
nicholas@2498 2905 } else {
nicholas@2498 2906 this.interval = setInterval(function () {
nicholas@2498 2907 interfaceContext.playhead.update();
nicholas@2498 2908 }, 100);
nicholas@2498 2909 }
nicholas@2498 2910 }
nicholas@2498 2911 };
nicholas@2498 2912 this.stop = function () {
nicholas@2498 2913 clearInterval(this.interval);
nicholas@2498 2914 this.interval = undefined;
nicholas@2224 2915 this.scrubberHead.style.left = '0px';
nicholas@2498 2916 if (this.maxTime < 60) {
nicholas@2498 2917 this.curTimeSpan.textContent = '0.00';
nicholas@2498 2918 } else {
nicholas@2498 2919 this.curTimeSpan.textContent = '00:00';
nicholas@2498 2920 }
nicholas@2498 2921 };
nicholas@2498 2922 };
nicholas@2498 2923
nicholas@2498 2924 this.volume = new function () {
nicholas@2224 2925 // An in-built volume module which can be viewed on page
nicholas@2224 2926 // Includes trackers on page-by-page data
nicholas@2224 2927 // Volume does NOT reset to 0dB on each page load
nicholas@2224 2928 this.valueLin = 1.0;
nicholas@2224 2929 this.valueDB = 0.0;
nicholas@2352 2930 this.root = document.createElement('div');
nicholas@2352 2931 this.root.id = 'master-volume-root';
nicholas@2224 2932 this.object = document.createElement('div');
nicholas@2352 2933 this.object.className = 'master-volume-holder-float';
nicholas@2352 2934 this.object.appendChild(this.root);
nicholas@2224 2935 this.slider = document.createElement('input');
nicholas@2224 2936 this.slider.id = 'master-volume-control';
nicholas@2224 2937 this.slider.type = 'range';
nicholas@2224 2938 this.valueText = document.createElement('span');
nicholas@2224 2939 this.valueText.id = 'master-volume-feedback';
nicholas@2224 2940 this.valueText.textContent = '0dB';
nicholas@2498 2941
nicholas@2224 2942 this.slider.min = -60;
nicholas@2224 2943 this.slider.max = 12;
nicholas@2224 2944 this.slider.value = 0;
nicholas@2224 2945 this.slider.step = 1;
nicholas@2498 2946 this.slider.onmousemove = function (event) {
nicholas@2224 2947 interfaceContext.volume.valueDB = event.currentTarget.value;
nicholas@2224 2948 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
nicholas@2498 2949 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB + 'dB';
nicholas@2224 2950 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
nicholas@2224 2951 }
nicholas@2498 2952 this.slider.onmouseup = function (event) {
nicholas@2224 2953 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
nicholas@2498 2954 if (storePoint.length == 0) {
nicholas@2224 2955 storePoint = storage.document.createElement('metricresult');
nicholas@2498 2956 storePoint.setAttribute('name', 'volumeTracker');
nicholas@2224 2957 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
nicholas@2498 2958 } else {
nicholas@2224 2959 storePoint = storePoint[0];
nicholas@2224 2960 }
nicholas@2224 2961 var node = storage.document.createElement('movement');
nicholas@2498 2962 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
nicholas@2498 2963 node.setAttribute('volume', interfaceContext.volume.valueDB);
nicholas@2498 2964 node.setAttribute('format', 'dBFS');
nicholas@2224 2965 storePoint.appendChild(node);
nicholas@2224 2966 }
nicholas@2498 2967
nicholas@2224 2968 var title = document.createElement('div');
nicholas@2224 2969 title.innerHTML = '<span>Master Volume Control</span>';
nicholas@2224 2970 title.style.fontSize = '0.75em';
nicholas@2224 2971 title.style.width = "100%";
nicholas@2224 2972 title.align = 'center';
nicholas@2352 2973 this.root.appendChild(title);
nicholas@2498 2974
nicholas@2352 2975 this.root.appendChild(this.slider);
nicholas@2352 2976 this.root.appendChild(this.valueText);
nicholas@2498 2977
nicholas@2498 2978 this.resize = function (event) {
nicholas@2352 2979 if (window.innerWidth < 1000) {
nicholas@2352 2980 this.object.className = "master-volume-holder-inline"
nicholas@2352 2981 } else {
nicholas@2352 2982 this.object.className = 'master-volume-holder-float';
nicholas@2352 2983 }
nicholas@2352 2984 }
nicholas@2224 2985 }
nicholas@2498 2986
nicholas@2224 2987 this.calibrationModuleObject = null;
nicholas@2498 2988 this.calibrationModule = function () {
nicholas@2224 2989 // This creates an on-page calibration module
nicholas@2224 2990 this.storeDOM = storage.document.createElement("calibration");
nicholas@2224 2991 storage.root.appendChild(this.storeDOM);
nicholas@2224 2992 // The calibration is a fixed state module
nicholas@2224 2993 this.calibrationNodes = [];
nicholas@2224 2994 this.holder = null;
nicholas@2498 2995 this.build = function (inject) {
nicholas@2224 2996 var f0 = 62.5;
nicholas@2224 2997 this.holder = document.createElement("div");
nicholas@2224 2998 this.holder.className = "calibration-holder";
nicholas@2224 2999 this.calibrationNodes = [];
nicholas@2498 3000 while (f0 < 20000) {
nicholas@2224 3001 var obj = {
nicholas@2224 3002 root: document.createElement("div"),
nicholas@2224 3003 input: document.createElement("input"),
nicholas@2224 3004 oscillator: audioContext.createOscillator(),
nicholas@2224 3005 gain: audioContext.createGain(),
nicholas@2224 3006 f: f0,
nicholas@2224 3007 parent: this,
nicholas@2498 3008 handleEvent: function (event) {
nicholas@2498 3009 switch (event.type) {
nicholas@2224 3010 case "mouseenter":
nicholas@2224 3011 this.oscillator.start(0);
nicholas@2224 3012 break;
nicholas@2224 3013 case "mouseleave":
nicholas@2224 3014 this.oscillator.stop(0);
nicholas@2224 3015 this.oscillator = audioContext.createOscillator();
nicholas@2224 3016 this.oscillator.connect(this.gain);
nicholas@2224 3017 this.oscillator.frequency.value = this.f;
nicholas@2224 3018 break;
nicholas@2224 3019 case "mousemove":
nicholas@2498 3020 var value = Math.pow(10, this.input.value / 20);
nicholas@2224 3021 if (this.f == 1000) {
nicholas@2224 3022 audioEngineContext.outputGain.gain.value = value;
nicholas@2224 3023 interfaceContext.volume.slider.value = this.input.value;
nicholas@2224 3024 } else {
nicholas@2224 3025 this.gain.gain.value = value
nicholas@2224 3026 }
nicholas@2224 3027 break;
nicholas@2224 3028 }
nicholas@2224 3029 },
nicholas@2498 3030 disconnect: function () {
nicholas@2224 3031 this.gain.disconnect();
nicholas@2224 3032 }
nicholas@2224 3033 }
nicholas@2224 3034 obj.root.className = "calibration-slider";
nicholas@2224 3035 obj.root.appendChild(obj.input);
nicholas@2224 3036 obj.oscillator.connect(obj.gain);
nicholas@2224 3037 obj.gain.connect(audioEngineContext.outputGain);
nicholas@2498 3038 obj.gain.gain.value = Math.random() * 2;
nicholas@2224 3039 obj.input.value = obj.gain.gain.value;
nicholas@2498 3040 obj.input.setAttribute('orient', 'vertical');
nicholas@2224 3041 obj.input.type = "range";
nicholas@2224 3042 obj.input.min = -6;
nicholas@2224 3043 obj.input.max = 6;
nicholas@2224 3044 obj.input.step = 0.25;
nicholas@2224 3045 if (f0 != 1000) {
nicholas@2498 3046 obj.input.value = (Math.random() * 12) - 6;
nicholas@2224 3047 } else {
nicholas@2224 3048 obj.input.value = 0;
nicholas@2498 3049 obj.root.style.backgroundColor = "rgb(255,125,125)";
nicholas@2224 3050 }
nicholas@2498 3051 obj.input.addEventListener("mousemove", obj);
nicholas@2498 3052 obj.input.addEventListener("mouseenter", obj);
nicholas@2498 3053 obj.input.addEventListener("mouseleave", obj);
nicholas@2498 3054 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
nicholas@2224 3055 obj.oscillator.frequency.value = f0;
nicholas@2224 3056 this.calibrationNodes.push(obj);
nicholas@2224 3057 this.holder.appendChild(obj.root);
nicholas@2224 3058 f0 *= 2;
nicholas@2224 3059 }
nicholas@2224 3060 inject.appendChild(this.holder);
nicholas@2224 3061 }
nicholas@2498 3062 this.collect = function () {
nicholas@2224 3063 for (var obj of this.calibrationNodes) {
nicholas@2224 3064 var node = storage.document.createElement("calibrationresult");
nicholas@2498 3065 node.setAttribute("frequency", obj.f);
nicholas@2498 3066 node.setAttribute("range-min", obj.input.min);
nicholas@2498 3067 node.setAttribute("range-max", obj.input.max);
nicholas@2498 3068 node.setAttribute("gain-lin", obj.gain.gain.value);
nicholas@2224 3069 this.storeDOM.appendChild(node);
nicholas@2224 3070 }
nicholas@2224 3071 }
nicholas@2224 3072 }
nicholas@2498 3073
nicholas@2498 3074
nicholas@2498 3075 // Global Checkers
nicholas@2498 3076 // These functions will help enforce the checkers
nicholas@2498 3077 this.checkHiddenAnchor = function () {
nicholas@2498 3078 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3079 if (ao.specification.type == "anchor") {
nicholas@2498 3080 if (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0) {
nicholas@2498 3081 // Anchor is not set below
nicholas@2498 3082 console.log('Anchor node not below marker value');
nicholas@2498 3083 interfaceContext.lightbox.post("Message", 'Please keep listening');
nicholas@2224 3084 this.storeErrorNode('Anchor node not below marker value');
nicholas@2498 3085 return false;
nicholas@2498 3086 }
nicholas@2498 3087 }
nicholas@2498 3088 }
nicholas@2498 3089 return true;
nicholas@2498 3090 };
nicholas@2498 3091
nicholas@2498 3092 this.checkHiddenReference = function () {
nicholas@2498 3093 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3094 if (ao.specification.type == "reference") {
nicholas@2498 3095 if (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0) {
nicholas@2498 3096 // Anchor is not set below
nicholas@2498 3097 console.log('Reference node not above marker value');
nicholas@2224 3098 this.storeErrorNode('Reference node not above marker value');
nicholas@2498 3099 interfaceContext.lightbox.post("Message", 'Please keep listening');
nicholas@2498 3100 return false;
nicholas@2498 3101 }
nicholas@2498 3102 }
nicholas@2498 3103 }
nicholas@2498 3104 return true;
nicholas@2498 3105 };
nicholas@2498 3106
nicholas@2498 3107 this.checkFragmentsFullyPlayed = function () {
nicholas@2498 3108 // Checks the entire file has been played back
nicholas@2498 3109 // NOTE ! This will return true IF playback is Looped!!!
nicholas@2498 3110 if (audioEngineContext.loopPlayback) {
nicholas@2498 3111 console.log("WARNING - Looped source: Cannot check fragments are fully played");
nicholas@2498 3112 return true;
nicholas@2498 3113 }
nicholas@2498 3114 var check_pass = true;
nicholas@2498 3115 var error_obj = [];
nicholas@2498 3116 for (var i = 0; i < audioEngineContext.audioObjects.length; i++) {
nicholas@2498 3117 var object = audioEngineContext.audioObjects[i];
nicholas@2498 3118 var time = object.buffer.buffer.duration;
nicholas@2498 3119 var metric = object.metric;
nicholas@2498 3120 var passed = false;
nicholas@2498 3121 for (var j = 0; j < metric.listenTracker.length; j++) {
nicholas@2498 3122 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
nicholas@2498 3123 var start_time = Number(bt[0].getAttribute('start'));
nicholas@2498 3124 var stop_time = Number(bt[0].getAttribute('stop'));
nicholas@2498 3125 var delta = stop_time - start_time;
nicholas@2498 3126 if (delta >= time) {
nicholas@2498 3127 passed = true;
nicholas@2498 3128 break;
nicholas@2498 3129 }
nicholas@2498 3130 }
nicholas@2498 3131 if (passed == false) {
nicholas@2498 3132 check_pass = false;
nicholas@2498 3133 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
nicholas@2498 3134 error_obj.push(object.interfaceDOM.getPresentedId());
nicholas@2498 3135 }
nicholas@2498 3136 }
nicholas@2498 3137 if (check_pass == false) {
nicholas@2498 3138 var str_start = "You have not completely listened to fragments ";
nicholas@2498 3139 for (var i = 0; i < error_obj.length; i++) {
nicholas@2498 3140 str_start += error_obj[i];
nicholas@2498 3141 if (i != error_obj.length - 1) {
nicholas@2498 3142 str_start += ', ';
nicholas@2498 3143 }
nicholas@2498 3144 }
nicholas@2498 3145 str_start += ". Please keep listening";
nicholas@2498 3146 console.log("[ALERT]: " + str_start);
nicholas@2498 3147 this.storeErrorNode("[ALERT]: " + str_start);
nicholas@2498 3148 interfaceContext.lightbox.post("Error", str_start);
nicholas@2444 3149 return false;
nicholas@2498 3150 }
nicholas@2444 3151 return true;
nicholas@2498 3152 };
nicholas@2498 3153 this.checkAllMoved = function () {
nicholas@2498 3154 var str = "You have not moved ";
nicholas@2498 3155 var failed = [];
nicholas@2498 3156 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3157 if (ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true) {
nicholas@2498 3158 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@2498 3159 }
nicholas@2498 3160 }
nicholas@2498 3161 if (failed.length == 0) {
nicholas@2498 3162 return true;
nicholas@2498 3163 } else if (failed.length == 1) {
nicholas@2498 3164 str += 'track ' + failed[0];
nicholas@2498 3165 } else {
nicholas@2498 3166 str += 'tracks ';
nicholas@2498 3167 for (var i = 0; i < failed.length - 1; i++) {
nicholas@2498 3168 str += failed[i] + ', ';
nicholas@2498 3169 }
nicholas@2498 3170 str += 'and ' + failed[i];
nicholas@2498 3171 }
nicholas@2498 3172 str += '.';
nicholas@2498 3173 interfaceContext.lightbox.post("Error", str);
nicholas@2498 3174 console.log(str);
nicholas@2224 3175 this.storeErrorNode(str);
nicholas@2498 3176 return false;
nicholas@2498 3177 };
nicholas@2498 3178 this.checkAllPlayed = function () {
nicholas@2498 3179 var str = "You have not played ";
nicholas@2498 3180 var failed = [];
nicholas@2498 3181 for (var ao of audioEngineContext.audioObjects) {
nicholas@2498 3182 if (ao.metric.wasListenedTo == false) {
nicholas@2498 3183 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@2498 3184 }
nicholas@2498 3185 }
nicholas@2498 3186 if (failed.length == 0) {
nicholas@2498 3187 return true;
nicholas@2498 3188 } else if (failed.length == 1) {
nicholas@2498 3189 str += 'track ' + failed[0];
nicholas@2498 3190 } else {
nicholas@2498 3191 str += 'tracks ';
nicholas@2498 3192 for (var i = 0; i < failed.length - 1; i++) {
nicholas@2498 3193 str += failed[i] + ', ';
nicholas@2498 3194 }
nicholas@2498 3195 str += 'and ' + failed[i];
nicholas@2498 3196 }
nicholas@2498 3197 str += '.';
nicholas@2498 3198 interfaceContext.lightbox.post("Error", str);
nicholas@2498 3199 console.log(str);
nicholas@2224 3200 this.storeErrorNode(str);
nicholas@2498 3201 return false;
nicholas@2498 3202 };
nicholas@2498 3203 this.checkScaleRange = function (min, max) {
nicholas@2310 3204 var page = testState.getCurrentTestPage();
nicholas@2310 3205 var audioObjects = audioEngineContext.audioObjects;
nicholas@2310 3206 var state = true;
nicholas@2310 3207 var str = "Please keep listening. ";
nicholas@2310 3208 var minRanking = Infinity;
nicholas@2310 3209 var maxRanking = -Infinity;
nicholas@2310 3210 for (var ao of audioObjects) {
nicholas@2310 3211 var rank = ao.interfaceDOM.getValue();
nicholas@2498 3212 if (rank < minRanking) {
nicholas@2498 3213 minRanking = rank;
nicholas@2498 3214 }
nicholas@2498 3215 if (rank > maxRanking) {
nicholas@2498 3216 maxRanking = rank;
nicholas@2498 3217 }
nicholas@2310 3218 }
nicholas@2498 3219 if (minRanking * 100 > min) {
nicholas@2498 3220 str += "At least one fragment must be below the " + min + " mark.";
nicholas@2310 3221 state = false;
nicholas@2310 3222 }
nicholas@2498 3223 if (maxRanking * 100 < max) {
nicholas@2498 3224 str += "At least one fragment must be above the " + max + " mark."
nicholas@2310 3225 state = false;
nicholas@2310 3226 }
nicholas@2310 3227 if (!state) {
nicholas@2310 3228 console.log(str);
nicholas@2310 3229 this.storeErrorNode(str);
nicholas@2498 3230 interfaceContext.lightbox.post("Error", str);
nicholas@2310 3231 }
nicholas@2310 3232 return state;
nicholas@2310 3233 }
nicholas@2498 3234
nicholas@2498 3235 this.storeErrorNode = function (errorMessage) {
nicholas@2224 3236 var time = audioEngineContext.timer.getTestTime();
nicholas@2224 3237 var node = storage.document.createElement('error');
nicholas@2498 3238 node.setAttribute('time', time);
nicholas@2224 3239 node.textContent = errorMessage;
nicholas@2224 3240 testState.currentStore.XMLDOM.appendChild(node);
nicholas@2224 3241 };
nicholas@2224 3242 }
nicholas@2224 3243
nicholas@2498 3244 function Storage() {
nicholas@2498 3245 // Holds results in XML format until ready for collection
nicholas@2498 3246 this.globalPreTest = null;
nicholas@2498 3247 this.globalPostTest = null;
nicholas@2498 3248 this.testPages = [];
nicholas@2498 3249 this.document = null;
nicholas@2498 3250 this.root = null;
nicholas@2498 3251 this.state = 0;
nicholas@2498 3252
nicholas@2498 3253 this.initialise = function (existingStore) {
nicholas@2224 3254 if (existingStore == undefined) {
nicholas@2224 3255 // We need to get the sessionKey
nicholas@2510 3256 this.SessionKey.requestKey();
nicholas@2498 3257 this.document = document.implementation.createDocument(null, "waetresult", null);
nicholas@2224 3258 this.root = this.document.childNodes[0];
nicholas@2224 3259 var projectDocument = specification.projectXML;
nicholas@2498 3260 projectDocument.setAttribute('file-name', url);
nicholas@2498 3261 projectDocument.setAttribute('url', qualifyURL(url));
nicholas@2224 3262 this.root.appendChild(projectDocument);
nicholas@2224 3263 this.root.appendChild(interfaceContext.returnDateNode());
nicholas@2224 3264 this.root.appendChild(interfaceContext.returnNavigator());
nicholas@2224 3265 } else {
nicholas@2224 3266 this.document = existingStore;
nicholas@2294 3267 this.root = existingStore.firstChild;
nicholas@2224 3268 this.SessionKey.key = this.root.getAttribute("key");
nicholas@2224 3269 }
nicholas@2498 3270 if (specification.preTest != undefined) {
nicholas@2498 3271 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
nicholas@2498 3272 }
nicholas@2498 3273 if (specification.postTest != undefined) {
nicholas@2498 3274 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
nicholas@2498 3275 }
nicholas@2498 3276 };
nicholas@2498 3277
nicholas@2224 3278 this.SessionKey = {
nicholas@2224 3279 key: null,
nicholas@2224 3280 request: new XMLHttpRequest(),
nicholas@2224 3281 parent: this,
nicholas@2498 3282 handleEvent: function () {
nicholas@2224 3283 var parse = new DOMParser();
nicholas@2498 3284 var xml = parse.parseFromString(this.request.response, "text/xml");
nicholas@2376 3285 if (this.request.response.length == 0) {
nicholas@2515 3286 console.error("An unspecified error occured, no server key could be generated");
nicholas@2376 3287 return;
nicholas@2376 3288 }
nicholas@2498 3289 if (xml.getElementsByTagName("state").length > 0) {
nicholas@2498 3290 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
nicholas@2498 3291 this.key = xml.getAllElementsByTagName("key")[0].textContent;
nicholas@2498 3292 this.parent.root.setAttribute("key", this.key);
nicholas@2498 3293 this.parent.root.setAttribute("state", "empty");
nicholas@2516 3294 this.update();
nicholas@2515 3295 return;
nicholas@2514 3296 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
nicholas@2515 3297 this.key = null;
nicholas@2514 3298 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
nicholas@2515 3299 return;
nicholas@2498 3300 }
nicholas@2498 3301 }
nicholas@2515 3302 this.key = null;
nicholas@2515 3303 console.error("An unspecified error occured, no server key could be generated");
nicholas@2224 3304 },
nicholas@2510 3305 requestKey: function () {
nicholas@2510 3306 // For new servers, request a new key from the server
nicholas@2510 3307 var returnURL = "";
nicholas@2510 3308 if (typeof specification.projectReturn == "string") {
nicholas@2510 3309 if (specification.projectReturn.substr(0, 4) == "http") {
nicholas@2510 3310 returnURL = specification.projectReturn;
nicholas@2510 3311 }
nicholas@2510 3312 }
nicholas@2510 3313 this.request.open("GET", returnURL + "php/requestKey.php", true);
nicholas@2510 3314 this.request.addEventListener("load", this);
nicholas@2510 3315 this.request.send();
nicholas@2510 3316 },
nicholas@2498 3317 update: function () {
nicholas@2357 3318 if (this.key == null) {
nicholas@2357 3319 console.log("Cannot save as key == null");
nicholas@2357 3320 return;
nicholas@2357 3321 }
nicholas@2498 3322 this.parent.root.setAttribute("state", "update");
nicholas@2224 3323 var xmlhttp = new XMLHttpRequest();
nicholas@2302 3324 var returnURL = "";
nicholas@2302 3325 if (typeof specification.projectReturn == "string") {
nicholas@2498 3326 if (specification.projectReturn.substr(0, 4) == "http") {
nicholas@2302 3327 returnURL = specification.projectReturn;
nicholas@2302 3328 }
nicholas@2302 3329 }
nicholas@2498 3330 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key);
nicholas@2224 3331 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nicholas@2498 3332 xmlhttp.onerror = function () {
nicholas@2224 3333 console.log('Error updating file to server!');
nicholas@2224 3334 };
nicholas@2224 3335 var hold = document.createElement("div");
nicholas@2224 3336 var clone = this.parent.root.cloneNode(true);
nicholas@2224 3337 hold.appendChild(clone);
nicholas@2498 3338 xmlhttp.onload = function () {
nicholas@2224 3339 if (this.status >= 300) {
nicholas@2224 3340 console.log("WARNING - Could not update at this time");
nicholas@2224 3341 } else {
nicholas@2224 3342 var parser = new DOMParser();
nicholas@2224 3343 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nicholas@2224 3344 var response = xmlDoc.getElementsByTagName('response')[0];
nicholas@2224 3345 if (response.getAttribute("state") == "OK") {
nicholas@2224 3346 var file = response.getElementsByTagName("file")[0];
nicholas@2498 3347 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
nicholas@2224 3348 } else {
nicholas@2224 3349 var message = response.getElementsByTagName("message");
nicholas@2498 3350 console.log("Intermediate save: Error! " + message.textContent);
nicholas@2224 3351 }
nicholas@2224 3352 }
nicholas@2224 3353 }
nicholas@2224 3354 xmlhttp.send([hold.innerHTML]);
nicholas@2224 3355 }
nicholas@2224 3356 }
nicholas@2498 3357
nicholas@2498 3358 this.createTestPageStore = function (specification) {
nicholas@2498 3359 var store = new this.pageNode(this, specification);
nicholas@2498 3360 this.testPages.push(store);
nicholas@2498 3361 return this.testPages[this.testPages.length - 1];
nicholas@2498 3362 };
nicholas@2498 3363
nicholas@2498 3364 this.surveyNode = function (parent, root, specification) {
nicholas@2498 3365 this.specification = specification;
nicholas@2498 3366 this.parent = parent;
nicholas@2224 3367 this.state = "empty";
nicholas@2498 3368 this.XMLDOM = this.parent.document.createElement('survey');
nicholas@2498 3369 this.XMLDOM.setAttribute('location', this.specification.location);
nicholas@2498 3370 this.XMLDOM.setAttribute("state", this.state);
nicholas@2498 3371 for (var optNode of this.specification.options) {
nicholas@2498 3372 if (optNode.type != 'statement') {
nicholas@2498 3373 var node = this.parent.document.createElement('surveyresult');
nicholas@2498 3374 node.setAttribute("ref", optNode.id);
nicholas@2498 3375 node.setAttribute('type', optNode.type);
nicholas@2498 3376 this.XMLDOM.appendChild(node);
nicholas@2498 3377 }
nicholas@2498 3378 }
nicholas@2498 3379 root.appendChild(this.XMLDOM);
nicholas@2498 3380
nicholas@2498 3381 this.postResult = function (node) {
nicholas@2498 3382 // From popup: node is the popupOption node containing both spec. and results
nicholas@2498 3383 // ID is the position
nicholas@2498 3384 if (node.specification.type == 'statement') {
nicholas@2498 3385 return;
nicholas@2498 3386 }
nicholas@2498 3387 var surveyresult = this.XMLDOM.firstChild;
nicholas@2498 3388 while (surveyresult != null) {
nicholas@2498 3389 if (surveyresult.getAttribute("ref") == node.specification.id) {
nicholas@2224 3390 break;
nicholas@2224 3391 }
nicholas@2224 3392 surveyresult = surveyresult.nextElementSibling;
nicholas@2224 3393 }
nicholas@2498 3394 switch (node.specification.type) {
nicholas@2498 3395 case "number":
nicholas@2498 3396 case "question":
n@2583 3397 case "slider":
nicholas@2498 3398 var child = this.parent.document.createElement('response');
nicholas@2498 3399 child.textContent = node.response;
nicholas@2498 3400 surveyresult.appendChild(child);
nicholas@2464 3401 break;
nicholas@2498 3402 case "radio":
nicholas@2498 3403 var child = this.parent.document.createElement('response');
nicholas@2571 3404 if (node.response !== null) {
nicholas@2571 3405 child.setAttribute('name', node.response.name);
nicholas@2571 3406 child.textContent = node.response.text;
nicholas@2571 3407 }
nicholas@2498 3408 surveyresult.appendChild(child);
nicholas@2498 3409 break;
nicholas@2498 3410 case "checkbox":
nicholas@2498 3411 if (node.response == undefined) {
nicholas@2498 3412 surveyresult.appendChild(this.parent.document.createElement('response'));
nicholas@2498 3413 break;
nicholas@2498 3414 }
nicholas@2498 3415 for (var i = 0; i < node.response.length; i++) {
nicholas@2498 3416 var checkNode = this.parent.document.createElement('response');
nicholas@2498 3417 checkNode.setAttribute('name', node.response[i].name);
nicholas@2498 3418 checkNode.setAttribute('checked', node.response[i].checked);
nicholas@2498 3419 surveyresult.appendChild(checkNode);
nicholas@2498 3420 }
nicholas@2498 3421 break;
nicholas@2498 3422 }
nicholas@2498 3423 };
nicholas@2498 3424 this.complete = function () {
nicholas@2498 3425 this.state = "complete";
nicholas@2498 3426 this.XMLDOM.setAttribute("state", this.state);
nicholas@2498 3427 }
nicholas@2498 3428 };
nicholas@2498 3429
nicholas@2498 3430 this.pageNode = function (parent, specification) {
nicholas@2498 3431 // Create one store per test page
nicholas@2498 3432 this.specification = specification;
nicholas@2498 3433 this.parent = parent;
nicholas@2498 3434 this.state = "empty";
nicholas@2498 3435 this.XMLDOM = this.parent.document.createElement('page');
nicholas@2498 3436 this.XMLDOM.setAttribute('ref', specification.id);
nicholas@2498 3437 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
nicholas@2498 3438 this.XMLDOM.setAttribute("state", this.state);
nicholas@2498 3439 if (specification.preTest != undefined) {
nicholas@2498 3440 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
nicholas@2498 3441 }
nicholas@2498 3442 if (specification.postTest != undefined) {
nicholas@2498 3443 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
nicholas@2498 3444 }
nicholas@2498 3445
nicholas@2498 3446 // Add any page metrics
nicholas@2498 3447 var page_metric = this.parent.document.createElement('metric');
nicholas@2498 3448 this.XMLDOM.appendChild(page_metric);
nicholas@2498 3449
nicholas@2498 3450 // Add the audioelement
nicholas@2498 3451 for (var element of this.specification.audioElements) {
nicholas@2498 3452 var aeNode = this.parent.document.createElement('audioelement');
nicholas@2498 3453 aeNode.setAttribute('ref', element.id);
nicholas@2498 3454 if (element.name != undefined) {
nicholas@2498 3455 aeNode.setAttribute('name', element.name)
nicholas@2498 3456 };
nicholas@2498 3457 aeNode.setAttribute('type', element.type);
nicholas@2498 3458 aeNode.setAttribute('url', element.url);
nicholas@2498 3459 aeNode.setAttribute('fqurl', qualifyURL(element.url));
nicholas@2498 3460 aeNode.setAttribute('gain', element.gain);
nicholas@2498 3461 if (element.type == 'anchor' || element.type == 'reference') {
nicholas@2498 3462 if (element.marker > 0) {
nicholas@2498 3463 aeNode.setAttribute('marker', element.marker);
nicholas@2464 3464 }
nicholas@2498 3465 }
nicholas@2498 3466 var ae_metric = this.parent.document.createElement('metric');
nicholas@2498 3467 aeNode.appendChild(ae_metric);
nicholas@2498 3468 this.XMLDOM.appendChild(aeNode);
nicholas@2498 3469 }
nicholas@2498 3470
nicholas@2498 3471 this.parent.root.appendChild(this.XMLDOM);
nicholas@2498 3472
nicholas@2498 3473 this.complete = function () {
nicholas@2224 3474 this.state = "complete";
nicholas@2498 3475 this.XMLDOM.setAttribute("state", "complete");
nicholas@2224 3476 }
nicholas@2498 3477 };
nicholas@2498 3478 this.update = function () {
nicholas@2224 3479 this.SessionKey.update();
nicholas@2224 3480 }
nicholas@2498 3481 this.finish = function () {
nicholas@2498 3482 if (this.state == 0) {
nicholas@2224 3483 this.update();
nicholas@2498 3484 }
nicholas@2498 3485 this.state = 1;
nicholas@2498 3486 this.root.setAttribute("state", "complete");
nicholas@2498 3487 return this.root;
nicholas@2498 3488 };
nicholas@2224 3489 }
nicholas@2384 3490
nicholas@2401 3491 var window_depedancy_callback;
nicholas@2498 3492 window_depedancy_callback = window.setInterval(function () {
nicholas@2401 3493 if (check_dependancies()) {
nicholas@2401 3494 window.clearInterval(window_depedancy_callback);
nicholas@2401 3495 onload();
nicholas@2401 3496 } else {
nicholas@2401 3497 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
nicholas@2401 3498 }
nicholas@2498 3499 }, 100);