annotate core.js @ 608:0256f3748b27 multiple-tests-concatenation

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