annotate js/core.js @ 2681:95f614a82762

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