annotate core.js @ 1268:a7ad6fc6a3b2

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