annotate js/core.js @ 2357:66cbd68a4ed0

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