annotate core.js @ 623:2930218004f5 Dev_main

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