annotate js/core.js @ 2319:6897339ac651

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