annotate js/core.js @ 2401:5e1fb07919e7

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