annotate core.js @ 625:f285bc1c5d9a Dev_main

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