annotate core.js @ 636:94cc405066e4 Dev_main

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