annotate core.js @ 2212:279733b3b67e

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