annotate core.js @ 584:d8be8a1111b6 Dev_main

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