annotate js/core.js @ 2399:5d7ad658c699

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