annotate core.js @ 630:9dcfd654abad Dev_main

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