annotate js/core.js @ 2566:172c76d9414b

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