annotate core.js @ 1261:158e06debceb

Fix for #1560. Fix for loadProjectSpecCallback error if Parser returns undefined.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Mon, 21 Mar 2016 15:01:57 +0000
parents 96a830d58da7
children 037d1365f1c3
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@961 1768 function returnDateNode()
n@961 1769 {
n@961 1770 // Create an XML Node for the Date and Time a test was conducted
n@961 1771 // Structure is
n@961 1772 // <datetime>
n@961 1773 // <date year="##" month="##" day="##">DD/MM/YY</date>
n@961 1774 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
n@961 1775 // </datetime>
n@961 1776 var dateTime = new Date();
n@961 1777 var year = document.createAttribute('year');
n@961 1778 var month = document.createAttribute('month');
n@961 1779 var day = document.createAttribute('day');
n@961 1780 var hour = document.createAttribute('hour');
n@961 1781 var minute = document.createAttribute('minute');
n@961 1782 var secs = document.createAttribute('secs');
n@961 1783
n@961 1784 year.nodeValue = dateTime.getFullYear();
n@961 1785 month.nodeValue = dateTime.getMonth()+1;
n@961 1786 day.nodeValue = dateTime.getDate();
n@961 1787 hour.nodeValue = dateTime.getHours();
n@961 1788 minute.nodeValue = dateTime.getMinutes();
n@961 1789 secs.nodeValue = dateTime.getSeconds();
n@961 1790
n@961 1791 var hold = document.createElement("datetime");
n@961 1792 var date = document.createElement("date");
n@961 1793 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
n@961 1794 var time = document.createElement("time");
n@961 1795 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
n@961 1796
n@961 1797 date.setAttributeNode(year);
n@961 1798 date.setAttributeNode(month);
n@961 1799 date.setAttributeNode(day);
n@961 1800 time.setAttributeNode(hour);
n@961 1801 time.setAttributeNode(minute);
n@961 1802 time.setAttributeNode(secs);
n@961 1803
n@961 1804 hold.appendChild(date);
n@961 1805 hold.appendChild(time);
n@771 1806 return hold;
n@961 1807
nicholas@934 1808 }
nicholas@934 1809
n@910 1810 function Specification() {
n@910 1811 // Handles the decoding of the project specification XML into a simple JavaScript Object.
n@910 1812
n@1124 1813 this.interface = null;
n@1173 1814 this.projectReturn = "null";
n@1124 1815 this.randomiseOrder = null;
n@1124 1816 this.testPages = null;
n@1124 1817 this.pages = [];
n@1124 1818 this.metrics = null;
n@1124 1819 this.interfaces = null;
n@1124 1820 this.loudness = null;
n@1124 1821 this.errors = [];
n@1124 1822 this.schema = null;
n@1118 1823
n@1257 1824 this.processAttribute = function(attribute,schema,schemaRoot)
n@769 1825 {
n@1124 1826 // attribute is the string returned from getAttribute on the XML
n@1124 1827 // schema is the <xs:attribute> node
n@1124 1828 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
n@769 1829 {
n@1257 1830 schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
n@1124 1831 }
n@1124 1832 var defaultOpt = schema.getAttribute('default');
n@1124 1833 if (attribute == null) {
n@1124 1834 attribute = defaultOpt;
n@1124 1835 }
n@1124 1836 var dataType = schema.getAttribute('type');
n@1124 1837 if (typeof dataType == "string") { dataType = dataType.substr(3);}
n@1124 1838 else {dataType = "string";}
n@1124 1839 if (attribute == null)
n@1124 1840 {
n@1124 1841 return attribute;
n@1124 1842 }
n@1124 1843 switch(dataType)
n@1124 1844 {
n@1124 1845 case "boolean":
n@1124 1846 if (attribute == 'true'){attribute = true;}else{attribute=false;}
n@1124 1847 break;
n@1124 1848 case "negativeInteger":
n@1124 1849 case "positiveInteger":
n@1124 1850 case "nonNegativeInteger":
n@1124 1851 case "nonPositiveInteger":
n@1124 1852 case "integer":
n@1124 1853 case "decimal":
n@1124 1854 case "short":
n@1124 1855 attribute = Number(attribute);
n@1124 1856 break;
n@1124 1857 case "string":
n@1124 1858 default:
n@1124 1859 attribute = String(attribute);
n@1124 1860 break;
n@1124 1861 }
n@1124 1862 return attribute;
n@769 1863 };
n@774 1864
n@769 1865 this.decode = function(projectXML) {
n@1124 1866 this.errors = [];
n@910 1867 // projectXML - DOM Parsed document
nicholas@1046 1868 this.projectXML = projectXML.childNodes[0];
n@910 1869 var setupNode = projectXML.getElementsByTagName('setup')[0];
n@1148 1870 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@1124 1871 // First decode the attributes
n@1148 1872 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@1124 1873 for (var i in attributes)
n@850 1874 {
n@1124 1875 if (isNaN(Number(i)) == true){break;}
n@1257 1876 var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
n@1124 1877 var projectAttr = setupNode.getAttribute(attributeName);
n@1257 1878 projectAttr = this.processAttribute(projectAttr,attributes[i],this.schema);
n@1124 1879 switch(typeof projectAttr)
n@795 1880 {
n@1124 1881 case "number":
n@1124 1882 case "boolean":
n@1124 1883 eval('this.'+attributeName+' = '+projectAttr);
n@1124 1884 break;
n@1124 1885 case "string":
n@1124 1886 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@1124 1887 break;
n@795 1888 }
n@1124 1889
n@769 1890 }
n@769 1891
n@1170 1892 this.metrics = new this.metricNode();
n@910 1893
n@1124 1894 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
n@1124 1895
n@1124 1896 // Now process the survey node options
n@1124 1897 var survey = setupNode.getElementsByTagName('survey');
n@1124 1898 for (var i in survey) {
n@1124 1899 if (isNaN(Number(i)) == true){break;}
n@1124 1900 var location = survey[i].getAttribute('location');
n@1124 1901 if (location == 'pre' || location == 'before')
n@1124 1902 {
n@1124 1903 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@1124 1904 else {
n@1124 1905 this.preTest = new this.surveyNode();
n@1170 1906 this.preTest.decode(this,survey[i]);
n@1124 1907 }
n@1124 1908 } else if (location == 'post' || location == 'after') {
n@1124 1909 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@1124 1910 else {
n@1124 1911 this.postTest = new this.surveyNode();
n@1170 1912 this.postTest.decode(this,survey[i]);
n@1124 1913 }
n@910 1914 }
n@910 1915 }
n@910 1916
n@1124 1917 var interfaceNode = setupNode.getElementsByTagName('interface');
n@1124 1918 if (interfaceNode.length > 1)
n@1124 1919 {
n@1124 1920 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
n@1124 1921 }
n@1124 1922 this.interfaces = new this.interfaceNode();
n@1124 1923 if (interfaceNode.length != 0)
n@1124 1924 {
n@1124 1925 interfaceNode = interfaceNode[0];
n@1148 1926 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
nicholas@886 1927 }
nicholas@886 1928
n@1124 1929 // Page tags
n@1124 1930 var pageTags = projectXML.getElementsByTagName('page');
n@1148 1931 var pageSchema = this.schema.getAllElementsByName('page')[0];
n@1124 1932 for (var i=0; i<pageTags.length; i++)
n@850 1933 {
n@1124 1934 var node = new this.page();
n@1124 1935 node.decode(this,pageTags[i],pageSchema);
n@1124 1936 this.pages.push(node);
n@850 1937 }
n@910 1938 };
n@910 1939
n@769 1940 this.encode = function()
n@769 1941 {
n@1172 1942 var RootDocument = document.implementation.createDocument(null,"waet");
n@1172 1943 var root = RootDocument.children[0];
n@1172 1944 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
n@1172 1945 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
n@1124 1946 // Build setup node
n@1172 1947 var setup = RootDocument.createElement("setup");
n@1172 1948 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@1172 1949 // First decode the attributes
n@1172 1950 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@1172 1951 for (var i=0; i<attributes.length; i++)
n@1172 1952 {
n@1172 1953 var name = attributes[i].getAttribute("name");
n@1172 1954 if (name == undefined) {
n@1172 1955 name = attributes[i].getAttribute("ref");
n@1172 1956 }
n@1172 1957 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@1172 1958 {
n@1172 1959 eval("setup.setAttribute('"+name+"',this."+name+")");
n@1172 1960 }
n@1172 1961 }
n@1172 1962 root.appendChild(setup);
n@1172 1963 // Survey node
n@1172 1964 setup.appendChild(this.preTest.encode(RootDocument));
n@1172 1965 setup.appendChild(this.postTest.encode(RootDocument));
n@1172 1966 setup.appendChild(this.metrics.encode(RootDocument));
n@1172 1967 setup.appendChild(this.interfaces.encode(RootDocument));
n@1172 1968 for (var page of this.pages)
n@1172 1969 {
n@1172 1970 root.appendChild(page.encode(RootDocument));
n@1172 1971 }
n@1172 1972 return RootDocument;
n@769 1973 };
n@769 1974
n@1124 1975 this.surveyNode = function() {
n@1124 1976 this.location = null;
n@910 1977 this.options = [];
n@1257 1978 this.parent = null;
n@1170 1979 this.schema = specification.schema.getAllElementsByName('survey')[0];
n@910 1980
n@769 1981 this.OptionNode = function() {
n@769 1982 this.type = undefined;
n@1170 1983 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
n@769 1984 this.id = undefined;
n@1248 1985 this.name = undefined;
n@769 1986 this.mandatory = undefined;
n@769 1987 this.statement = undefined;
n@769 1988 this.boxsize = undefined;
n@769 1989 this.options = [];
n@769 1990 this.min = undefined;
n@769 1991 this.max = undefined;
n@769 1992 this.step = undefined;
n@769 1993
n@1170 1994 this.decode = function(parent,child)
n@769 1995 {
n@1170 1996 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@1124 1997 for (var i in attributeMap){
n@1124 1998 if(isNaN(Number(i)) == true){break;}
n@1124 1999 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@1124 2000 var projectAttr = child.getAttribute(attributeName);
n@1257 2001 projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
n@1124 2002 switch(typeof projectAttr)
n@1124 2003 {
n@1124 2004 case "number":
n@1124 2005 case "boolean":
n@1124 2006 eval('this.'+attributeName+' = '+projectAttr);
n@1124 2007 break;
n@1124 2008 case "string":
n@1124 2009 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@1124 2010 break;
n@769 2011 }
n@1124 2012 }
n@1124 2013 this.statement = child.getElementsByTagName('statement')[0].textContent;
n@1124 2014 if (this.type == "checkbox" || this.type == "radio") {
n@1124 2015 var children = child.getElementsByTagName('option');
n@1124 2016 if (children.length == null) {
n@769 2017 console.log('Malformed' +child.nodeName+ 'entry');
n@769 2018 this.statement = 'Malformed' +child.nodeName+ 'entry';
n@769 2019 this.type = 'statement';
n@769 2020 } else {
n@769 2021 this.options = [];
n@1124 2022 for (var i in children)
n@1124 2023 {
n@1124 2024 if (isNaN(Number(i))==true){break;}
n@1124 2025 this.options.push({
n@1124 2026 name: children[i].getAttribute('name'),
n@1124 2027 text: children[i].textContent
n@1124 2028 });
n@769 2029 }
n@769 2030 }
n@1024 2031 }
n@769 2032 };
n@769 2033
n@1172 2034 this.exportXML = function(doc)
n@769 2035 {
n@1203 2036 var node = doc.createElement('surveyentry');
n@1124 2037 node.setAttribute('type',this.type);
n@1172 2038 var statement = doc.createElement('statement');
n@1124 2039 statement.textContent = this.statement;
n@1124 2040 node.appendChild(statement);
n@1254 2041 node.id = this.id;
n@1254 2042 if (this.name != undefined) { node.setAttribute("name",this.name);}
n@1254 2043 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
n@1254 2044 node.id = this.id;
n@1254 2045 if (this.name != undefined) {node.setAttribute("name",this.name);}
n@1254 2046 switch(this.type)
n@1254 2047 {
n@1248 2048 case "checkbox":
n@1248 2049 case "radio":
n@1248 2050 for (var i=0; i<this.options.length; i++)
n@1248 2051 {
n@1248 2052 var option = this.options[i];
n@1248 2053 var optionNode = doc.createElement("option");
n@1248 2054 optionNode.setAttribute("name",option.name);
n@1248 2055 optionNode.textContent = option.text;
n@1248 2056 node.appendChild(optionNode);
n@1248 2057 }
n@1254 2058 case "number":
n@1254 2059 if (this.min != undefined) {node.setAttribute("min", this.min);}
n@1254 2060 if (this.max != undefined) {node.setAttribute("max", this.max);}
n@1254 2061 case "question":
n@1254 2062 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
n@1254 2063 if (this.mandatory != undefined) {node.setAttribute("mandatory",this.mandatory);}
n@1254 2064 default:
n@1248 2065 break;
n@1248 2066 }
n@769 2067 return node;
n@769 2068 };
n@769 2069 };
n@1170 2070 this.decode = function(parent,xml) {
n@1257 2071 this.parent = parent;
n@1124 2072 this.location = xml.getAttribute('location');
n@1124 2073 if (this.location == 'before'){this.location = 'pre';}
n@1124 2074 else if (this.location == 'after'){this.location = 'post';}
n@1124 2075 for (var i in xml.children)
n@1124 2076 {
n@1124 2077 if(isNaN(Number(i))==true){break;}
n@769 2078 var node = new this.OptionNode();
n@1170 2079 node.decode(parent,xml.children[i]);
n@769 2080 this.options.push(node);
n@1124 2081 }
n@1124 2082 };
n@1172 2083 this.encode = function(doc) {
n@1172 2084 var node = doc.createElement('survey');
n@1124 2085 node.setAttribute('location',this.location);
n@1124 2086 for (var i=0; i<this.options.length; i++)
n@1124 2087 {
n@1172 2088 node.appendChild(this.options[i].exportXML(doc));
n@1124 2089 }
n@1124 2090 return node;
n@1124 2091 };
n@1124 2092 };
n@1124 2093
n@1124 2094 this.interfaceNode = function()
n@1124 2095 {
n@1124 2096 this.title = null;
n@1124 2097 this.name = null;
n@1124 2098 this.options = [];
n@1124 2099 this.scales = [];
n@1170 2100 this.schema = specification.schema.getAllElementsByName('interface')[1];
n@1124 2101
n@1170 2102 this.decode = function(parent,xml) {
n@1124 2103 this.name = xml.getAttribute('name');
n@1124 2104 var titleNode = xml.getElementsByTagName('title');
n@1124 2105 if (titleNode.length == 1)
n@1124 2106 {
n@1124 2107 this.title = titleNode[0].textContent;
n@1124 2108 }
n@1124 2109 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
n@1124 2110 // Extract interfaceoption node schema
n@1170 2111 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
n@1148 2112 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
n@1124 2113 for (var i=0; i<interfaceOptionNodes.length; i++)
n@1124 2114 {
n@1124 2115 var ioNode = interfaceOptionNodes[i];
n@1124 2116 var option = {};
n@1124 2117 for (var j=0; j<attributeMap.length; j++)
n@1124 2118 {
n@1124 2119 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
n@1124 2120 var projectAttr = ioNode.getAttribute(attributeName);
n@1257 2121 if(parent.processAttribute) {
n@1257 2122 parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
n@1257 2123 } else {
n@1257 2124 parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
n@1257 2125 }
n@1124 2126 switch(typeof projectAttr)
n@1124 2127 {
n@1124 2128 case "number":
n@1124 2129 case "boolean":
n@1124 2130 eval('option.'+attributeName+' = '+projectAttr);
n@1124 2131 break;
n@1124 2132 case "string":
n@1124 2133 eval('option.'+attributeName+' = "'+projectAttr+'"');
n@1124 2134 break;
n@1124 2135 }
n@1124 2136 }
n@1124 2137 this.options.push(option);
n@1124 2138 }
n@1124 2139
n@1124 2140 // Now the scales nodes
n@1124 2141 var scaleParent = xml.getElementsByTagName('scales');
n@1124 2142 if (scaleParent.length == 1) {
n@1124 2143 scaleParent = scaleParent[0];
n@1124 2144 for (var i=0; i<scaleParent.children.length; i++) {
n@1124 2145 var child = scaleParent.children[i];
n@1124 2146 this.scales.push({
n@1124 2147 text: child.textContent,
n@1124 2148 position: Number(child.getAttribute('position'))
n@1124 2149 });
n@769 2150 }
n@910 2151 }
n@910 2152 };
n@1124 2153
n@1172 2154 this.encode = function(doc) {
n@1172 2155 var node = doc.createElement("interface");
n@1172 2156 if (typeof name == "string")
n@1172 2157 node.setAttribute("name",this.name);
n@1172 2158 for (var option of this.options)
n@1172 2159 {
n@1172 2160 var child = doc.createElement("interfaceoption");
n@1172 2161 child.setAttribute("type",option.type);
n@1172 2162 child.setAttribute("name",option.name);
n@1172 2163 node.appendChild(child);
n@1172 2164 }
n@1172 2165 if (this.scales.length != 0) {
n@1172 2166 var scales = doc.createElement("scales");
n@1172 2167 for (var scale of this.scales)
n@1172 2168 {
n@1172 2169 var child = doc.createElement("scalelabel");
n@1172 2170 child.setAttribute("position",scale.position);
n@1172 2171 child.textContent = scale.text;
n@1172 2172 scales.appendChild(child);
n@1172 2173 }
n@1172 2174 node.appendChild(scales);
n@1172 2175 }
n@1172 2176 return node;
n@1124 2177 };
n@910 2178 };
n@910 2179
n@1170 2180 this.metricNode = function() {
n@1170 2181 this.enabled = [];
n@1170 2182 this.decode = function(parent, xml) {
n@1170 2183 var children = xml.getElementsByTagName('metricenable');
n@1170 2184 for (var i in children) {
n@1170 2185 if (isNaN(Number(i)) == true){break;}
n@1170 2186 this.enabled.push(children[i].textContent);
n@1170 2187 }
n@1170 2188 }
n@1172 2189 this.encode = function(doc) {
n@1172 2190 var node = doc.createElement('metric');
n@1170 2191 for (var i in this.enabled)
n@1170 2192 {
n@1170 2193 if (isNaN(Number(i)) == true){break;}
n@1172 2194 var child = doc.createElement('metricenable');
n@1170 2195 child.textContent = this.enabled[i];
n@1170 2196 node.appendChild(child);
n@1170 2197 }
n@1170 2198 return node;
n@1170 2199 }
n@1170 2200 }
n@1170 2201
n@1124 2202 this.page = function() {
n@769 2203 this.presentedId = undefined;
n@769 2204 this.id = undefined;
n@769 2205 this.hostURL = undefined;
n@769 2206 this.randomiseOrder = undefined;
n@769 2207 this.loop = undefined;
n@1124 2208 this.showElementComments = undefined;
n@769 2209 this.outsideReference = null;
n@795 2210 this.loudness = null;
n@1096 2211 this.label = null;
n@1124 2212 this.preTest = null;
n@1124 2213 this.postTest = null;
n@769 2214 this.interfaces = [];
n@769 2215 this.commentBoxPrefix = "Comment on track";
n@769 2216 this.audioElements = [];
n@769 2217 this.commentQuestions = [];
n@1170 2218 this.schema = specification.schema.getAllElementsByName("page")[0];
n@1257 2219 this.parent = null;
n@769 2220
n@769 2221 this.decode = function(parent,xml)
n@769 2222 {
n@1257 2223 this.parent = parent;
n@1148 2224 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@1124 2225 for (var i=0; i<attributeMap.length; i++)
n@795 2226 {
n@1124 2227 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@1124 2228 var projectAttr = xml.getAttribute(attributeName);
n@1257 2229 projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
n@1124 2230 switch(typeof projectAttr)
nicholas@758 2231 {
n@1124 2232 case "number":
n@1124 2233 case "boolean":
n@1124 2234 eval('this.'+attributeName+' = '+projectAttr);
n@1124 2235 break;
n@1124 2236 case "string":
n@1124 2237 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@1124 2238 break;
n@769 2239 }
n@769 2240 }
n@769 2241
n@1124 2242 // Get the Comment Box Prefix
n@1124 2243 var CBP = xml.getElementsByTagName('commentboxprefix');
n@1124 2244 if (CBP.length != 0) {
n@1124 2245 this.commentBoxPrefix = CBP[0].textContent;
n@804 2246 }
n@804 2247
n@1124 2248 // Now decode the interfaces
n@1124 2249 var interfaceNode = xml.getElementsByTagName('interface');
n@1124 2250 for (var i=0; i<interfaceNode.length; i++)
n@1124 2251 {
n@1124 2252 var node = new parent.interfaceNode();
n@1148 2253 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
n@1124 2254 this.interfaces.push(node);
n@1124 2255 }
n@774 2256
n@1124 2257 // Now process the survey node options
n@1124 2258 var survey = xml.getElementsByTagName('survey');
n@1148 2259 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
n@1124 2260 for (var i in survey) {
n@1124 2261 if (isNaN(Number(i)) == true){break;}
n@1124 2262 var location = survey[i].getAttribute('location');
n@1124 2263 if (location == 'pre' || location == 'before')
n@1124 2264 {
n@1124 2265 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@1124 2266 else {
n@1124 2267 this.preTest = new parent.surveyNode();
n@1124 2268 this.preTest.decode(parent,survey[i],surveySchema);
n@1124 2269 }
n@1124 2270 } else if (location == 'post' || location == 'after') {
n@1124 2271 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@1124 2272 else {
n@1124 2273 this.postTest = new parent.surveyNode();
n@1124 2274 this.postTest.decode(parent,survey[i],surveySchema);
n@1124 2275 }
n@1124 2276 }
n@1124 2277 }
n@1124 2278
n@1124 2279 // Now process the audioelement tags
n@1124 2280 var audioElements = xml.getElementsByTagName('audioelement');
n@1124 2281 for (var i=0; i<audioElements.length; i++)
n@1124 2282 {
n@1124 2283 var node = new this.audioElementNode();
n@1170 2284 node.decode(this,audioElements[i]);
n@1124 2285 this.audioElements.push(node);
n@1124 2286 }
n@1124 2287
n@1124 2288 // Now decode the commentquestions
n@1124 2289 var commentQuestions = xml.getElementsByTagName('commentquestion');
n@1124 2290 for (var i=0; i<commentQuestions.length; i++)
n@1124 2291 {
n@769 2292 var node = new this.commentQuestionNode();
n@1170 2293 node.decode(parent,commentQuestions[i]);
n@769 2294 this.commentQuestions.push(node);
n@910 2295 }
n@910 2296 };
n@910 2297
n@769 2298 this.encode = function(root)
n@769 2299 {
n@1172 2300 var AHNode = root.createElement("page");
n@1172 2301 // First decode the attributes
n@1172 2302 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@1172 2303 for (var i=0; i<attributes.length; i++)
n@1172 2304 {
n@1172 2305 var name = attributes[i].getAttribute("name");
n@1172 2306 if (name == undefined) {
n@1172 2307 name = attributes[i].getAttribute("ref");
n@1172 2308 }
n@1172 2309 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@1172 2310 {
n@1172 2311 eval("AHNode.setAttribute('"+name+"',this."+name+")");
n@1172 2312 }
n@1172 2313 }
n@795 2314 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
n@1172 2315 // <commentboxprefix>
n@1172 2316 var commentboxprefix = root.createElement("commentboxprefix");
n@1172 2317 commentboxprefix.textContent = this.commentBoxPrefix;
n@1172 2318 AHNode.appendChild(commentboxprefix);
n@1172 2319
n@769 2320 for (var i=0; i<this.interfaces.length; i++)
n@767 2321 {
n@769 2322 AHNode.appendChild(this.interfaces[i].encode(root));
n@769 2323 }
nicholas@888 2324
n@769 2325 for (var i=0; i<this.audioElements.length; i++) {
n@769 2326 AHNode.appendChild(this.audioElements[i].encode(root));
n@769 2327 }
n@769 2328 // Create <CommentQuestion>
n@769 2329 for (var i=0; i<this.commentQuestions.length; i++)
n@818 2330 {
n@1172 2331 AHNode.appendChild(this.commentQuestions[i].encode(root));
n@769 2332 }
n@769 2333
n@1172 2334 AHNode.appendChild(this.preTest.encode(root));
n@1172 2335 AHNode.appendChild(this.postTest.encode(root));
n@769 2336 return AHNode;
n@769 2337 };
n@769 2338
n@1124 2339 this.commentQuestionNode = function() {
n@1124 2340 this.id = null;
n@1248 2341 this.name = undefined;
n@1124 2342 this.type = undefined;
n@769 2343 this.options = [];
n@1124 2344 this.statement = undefined;
n@1170 2345 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
n@1170 2346 this.decode = function(parent,xml)
n@769 2347 {
n@1124 2348 this.id = xml.id;
n@1248 2349 this.name = xml.getAttribute('name');
n@1124 2350 this.type = xml.getAttribute('type');
n@1124 2351 this.statement = xml.getElementsByTagName('statement')[0].textContent;
n@1124 2352 var optNodes = xml.getElementsByTagName('option');
n@1124 2353 for (var i=0; i<optNodes.length; i++)
n@1124 2354 {
n@1124 2355 var optNode = optNodes[i];
n@1124 2356 this.options.push({
n@1124 2357 name: optNode.getAttribute('name'),
n@1124 2358 text: optNode.textContent
n@1124 2359 });
n@769 2360 }
n@769 2361 };
n@1124 2362
n@769 2363 this.encode = function(root)
n@769 2364 {
n@1172 2365 var node = root.createElement("commentquestion");
n@1172 2366 node.id = this.id;
n@1172 2367 node.setAttribute("type",this.type);
n@1248 2368 if (this.name != undefined){node.setAttribute("name",this.name);}
n@1172 2369 var statement = root.createElement("statement");
n@1172 2370 statement.textContent = this.statement;
n@1172 2371 node.appendChild(statement);
n@1172 2372 for (var option of this.options)
n@1172 2373 {
n@1172 2374 var child = root.createElement("option");
n@1172 2375 child.setAttribute("name",option.name);
n@1172 2376 child.textContent = option.text;
n@1172 2377 node.appendChild(child);
n@1172 2378 }
n@1172 2379 return node;
n@769 2380 };
n@769 2381 };
n@769 2382
n@769 2383 this.audioElementNode = function() {
n@769 2384 this.url = null;
n@769 2385 this.id = null;
n@1248 2386 this.name = null;
n@769 2387 this.parent = null;
n@1124 2388 this.type = null;
n@1110 2389 this.marker = null;
n@769 2390 this.enforce = false;
n@1222 2391 this.gain = 0.0;
n@1170 2392 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
n@1124 2393 this.parent = null;
n@769 2394 this.decode = function(parent,xml)
n@769 2395 {
n@769 2396 this.parent = parent;
n@1148 2397 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@1124 2398 for (var i=0; i<attributeMap.length; i++)
n@789 2399 {
n@1124 2400 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@1124 2401 var projectAttr = xml.getAttribute(attributeName);
n@1257 2402 projectAttr = parent.parent.processAttribute(projectAttr,attributeMap[i],parent.parent.schema);
n@1124 2403 switch(typeof projectAttr)
n@769 2404 {
n@1124 2405 case "number":
n@1124 2406 case "boolean":
n@1124 2407 eval('this.'+attributeName+' = '+projectAttr);
n@1124 2408 break;
n@1124 2409 case "string":
n@1124 2410 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@1124 2411 break;
n@818 2412 }
n@818 2413 }
n@1124 2414
n@769 2415 };
n@769 2416 this.encode = function(root)
n@769 2417 {
n@1172 2418 var AENode = root.createElement("audioelement");
n@1172 2419 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@1172 2420 for (var i=0; i<attributes.length; i++)
n@1172 2421 {
n@1172 2422 var name = attributes[i].getAttribute("name");
n@1172 2423 if (name == undefined) {
n@1172 2424 name = attributes[i].getAttribute("ref");
n@1172 2425 }
n@1172 2426 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@1172 2427 {
n@1172 2428 eval("AENode.setAttribute('"+name+"',this."+name+")");
n@1172 2429 }
n@1172 2430 }
n@769 2431 return AENode;
n@769 2432 };
n@910 2433 };
n@910 2434 };
n@910 2435 }
n@769 2436
n@912 2437 function Interface(specificationObject) {
n@910 2438 // This handles the bindings between the interface and the audioEngineContext;
n@912 2439 this.specification = specificationObject;
n@912 2440 this.insertPoint = document.getElementById("topLevelBody");
n@910 2441
n@1124 2442 this.newPage = function(audioHolderObject,store)
n@770 2443 {
n@1169 2444 audioEngineContext.newTestPage(audioHolderObject,store);
n@1209 2445 interfaceContext.commentBoxes.deleteCommentBoxes();
n@770 2446 interfaceContext.deleteCommentQuestions();
n@1124 2447 loadTest(audioHolderObject,store);
n@770 2448 };
n@770 2449
n@912 2450 // Bounded by interface!!
n@912 2451 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
n@912 2452 // For example, APE returns the slider position normalised in a <value> tag.
n@912 2453 this.interfaceObjects = [];
n@912 2454 this.interfaceObject = function(){};
n@912 2455
n@855 2456 this.resizeWindow = function(event)
n@855 2457 {
n@784 2458 popup.resize(event);
n@855 2459 for(var i=0; i<this.commentBoxes.length; i++)
n@855 2460 {this.commentBoxes[i].resize();}
n@855 2461 for(var i=0; i<this.commentQuestions.length; i++)
n@855 2462 {this.commentQuestions[i].resize();}
n@855 2463 try
n@855 2464 {
n@855 2465 resizeWindow(event);
n@855 2466 }
n@855 2467 catch(err)
n@855 2468 {
n@855 2469 console.log("Warning - Interface does not have Resize option");
n@855 2470 console.log(err);
n@855 2471 }
n@855 2472 };
n@855 2473
n@828 2474 this.returnNavigator = function()
n@828 2475 {
n@1162 2476 var node = storage.document.createElement("navigator");
n@1162 2477 var platform = storage.document.createElement("platform");
n@828 2478 platform.textContent = navigator.platform;
n@1162 2479 var vendor = storage.document.createElement("vendor");
n@828 2480 vendor.textContent = navigator.vendor;
n@1162 2481 var userAgent = storage.document.createElement("uagent");
n@828 2482 userAgent.textContent = navigator.userAgent;
n@1162 2483 var screen = storage.document.createElement("window");
n@1162 2484 screen.setAttribute('innerWidth',window.innerWidth);
n@1162 2485 screen.setAttribute('innerHeight',window.innerHeight);
n@828 2486 node.appendChild(platform);
n@828 2487 node.appendChild(vendor);
n@828 2488 node.appendChild(userAgent);
n@1162 2489 node.appendChild(screen);
n@828 2490 return node;
n@828 2491 };
n@828 2492
n@1209 2493 this.commentBoxes = new function() {
n@1209 2494 this.boxes = [];
n@1209 2495 this.injectPoint = null;
n@1209 2496 this.elementCommentBox = function(audioObject) {
n@1209 2497 var element = audioObject.specification;
n@1209 2498 this.audioObject = audioObject;
n@1209 2499 this.id = audioObject.id;
n@1209 2500 var audioHolderObject = audioObject.specification.parent;
n@1209 2501 // Create document objects to hold the comment boxes
n@1209 2502 this.trackComment = document.createElement('div');
n@1209 2503 this.trackComment.className = 'comment-div';
n@1209 2504 this.trackComment.id = 'comment-div-'+audioObject.id;
n@1209 2505 // Create a string next to each comment asking for a comment
n@1209 2506 this.trackString = document.createElement('span');
n@1209 2507 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
n@1209 2508 // Create the HTML5 comment box 'textarea'
n@1209 2509 this.trackCommentBox = document.createElement('textarea');
n@1209 2510 this.trackCommentBox.rows = '4';
n@1209 2511 this.trackCommentBox.cols = '100';
n@1209 2512 this.trackCommentBox.name = 'trackComment'+audioObject.id;
n@1209 2513 this.trackCommentBox.className = 'trackComment';
n@1209 2514 var br = document.createElement('br');
n@1209 2515 // Add to the holder.
n@1209 2516 this.trackComment.appendChild(this.trackString);
n@1209 2517 this.trackComment.appendChild(br);
n@1209 2518 this.trackComment.appendChild(this.trackCommentBox);
n@1209 2519
n@1209 2520 this.exportXMLDOM = function() {
n@1209 2521 var root = document.createElement('comment');
n@1209 2522 var question = document.createElement('question');
n@1209 2523 question.textContent = this.trackString.textContent;
n@1209 2524 var response = document.createElement('response');
n@1209 2525 response.textContent = this.trackCommentBox.value;
n@1209 2526 console.log("Comment frag-"+this.id+": "+response.textContent);
n@1209 2527 root.appendChild(question);
n@1209 2528 root.appendChild(response);
n@1209 2529 return root;
n@1209 2530 };
n@1209 2531 this.resize = function()
n@1209 2532 {
n@1209 2533 var boxwidth = (window.innerWidth-100)/2;
n@1209 2534 if (boxwidth >= 600)
n@1209 2535 {
n@1209 2536 boxwidth = 600;
n@1209 2537 }
n@1209 2538 else if (boxwidth < 400)
n@1209 2539 {
n@1209 2540 boxwidth = 400;
n@1209 2541 }
n@1209 2542 this.trackComment.style.width = boxwidth+"px";
n@1209 2543 this.trackCommentBox.style.width = boxwidth-6+"px";
n@1209 2544 };
n@1209 2545 this.resize();
n@1209 2546 };
n@1209 2547 this.createCommentBox = function(audioObject) {
n@1209 2548 var node = new this.elementCommentBox(audioObject);
n@1209 2549 this.boxes.push(node);
n@1209 2550 audioObject.commentDOM = node;
n@1209 2551 return node;
n@1209 2552 };
n@1209 2553 this.sortCommentBoxes = function() {
n@1209 2554 this.boxes.sort(function(a,b){return a.id - b.id;});
n@1209 2555 };
n@1209 2556
n@1209 2557 this.showCommentBoxes = function(inject, sort) {
n@1209 2558 this.injectPoint = inject;
n@1209 2559 if (sort) {this.sortCommentBoxes();}
n@1209 2560 for (var box of this.boxes) {
n@1209 2561 inject.appendChild(box.trackComment);
n@1209 2562 }
n@1209 2563 };
n@1209 2564
n@1209 2565 this.deleteCommentBoxes = function() {
n@1209 2566 if (this.injectPoint != null) {
n@1209 2567 for (var box of this.boxes) {
n@1209 2568 this.injectPoint.removeChild(box.trackComment);
n@1209 2569 }
n@1209 2570 this.injectPoint = null;
n@1209 2571 }
n@1209 2572 this.boxes = [];
n@1209 2573 };
n@1209 2574 }
n@912 2575
n@1026 2576 this.commentQuestions = [];
n@1026 2577
n@1026 2578 this.commentBox = function(commentQuestion) {
n@1026 2579 this.specification = commentQuestion;
n@1026 2580 // Create document objects to hold the comment boxes
n@1026 2581 this.holder = document.createElement('div');
n@1026 2582 this.holder.className = 'comment-div';
n@1026 2583 // Create a string next to each comment asking for a comment
n@1026 2584 this.string = document.createElement('span');
n@1124 2585 this.string.innerHTML = commentQuestion.statement;
n@1026 2586 // Create the HTML5 comment box 'textarea'
n@1026 2587 this.textArea = document.createElement('textarea');
n@1026 2588 this.textArea.rows = '4';
n@1026 2589 this.textArea.cols = '100';
n@1026 2590 this.textArea.className = 'trackComment';
n@1026 2591 var br = document.createElement('br');
n@1026 2592 // Add to the holder.
n@1026 2593 this.holder.appendChild(this.string);
n@1026 2594 this.holder.appendChild(br);
n@1026 2595 this.holder.appendChild(this.textArea);
n@1026 2596
n@1189 2597 this.exportXMLDOM = function(storePoint) {
n@1189 2598 var root = storePoint.parent.document.createElement('comment');
n@1026 2599 root.id = this.specification.id;
n@1026 2600 root.setAttribute('type',this.specification.type);
BrechtDeMan@1053 2601 console.log("Question: "+this.string.textContent);
BrechtDeMan@1053 2602 console.log("Response: "+root.textContent);
n@1189 2603 var question = storePoint.parent.document.createElement('question');
n@1189 2604 question.textContent = this.string.textContent;
n@1189 2605 var response = storePoint.parent.document.createElement('response');
n@1189 2606 response.textContent = this.textArea.value;
n@1189 2607 root.appendChild(question);
n@1189 2608 root.appendChild(response);
n@1189 2609 storePoint.XMLDOM.appendChild(root);
n@1026 2610 return root;
n@1026 2611 };
n@855 2612 this.resize = function()
n@855 2613 {
n@855 2614 var boxwidth = (window.innerWidth-100)/2;
n@855 2615 if (boxwidth >= 600)
n@855 2616 {
n@855 2617 boxwidth = 600;
n@855 2618 }
n@855 2619 else if (boxwidth < 400)
n@855 2620 {
n@855 2621 boxwidth = 400;
n@855 2622 }
n@855 2623 this.holder.style.width = boxwidth+"px";
n@855 2624 this.textArea.style.width = boxwidth-6+"px";
n@855 2625 };
n@855 2626 this.resize();
n@1026 2627 };
n@1026 2628
n@1026 2629 this.radioBox = function(commentQuestion) {
n@1026 2630 this.specification = commentQuestion;
n@1026 2631 // Create document objects to hold the comment boxes
n@1026 2632 this.holder = document.createElement('div');
n@1026 2633 this.holder.className = 'comment-div';
n@1026 2634 // Create a string next to each comment asking for a comment
n@1026 2635 this.string = document.createElement('span');
n@1026 2636 this.string.innerHTML = commentQuestion.statement;
n@1026 2637 var br = document.createElement('br');
n@1026 2638 // Add to the holder.
n@1026 2639 this.holder.appendChild(this.string);
n@1026 2640 this.holder.appendChild(br);
n@1026 2641 this.options = [];
n@1026 2642 this.inputs = document.createElement('div');
n@1026 2643 this.span = document.createElement('div');
n@1026 2644 this.inputs.align = 'center';
n@1026 2645 this.inputs.style.marginLeft = '12px';
n@1026 2646 this.span.style.marginLeft = '12px';
n@1026 2647 this.span.align = 'center';
n@1026 2648 this.span.style.marginTop = '15px';
n@1026 2649
n@1026 2650 var optCount = commentQuestion.options.length;
n@1124 2651 for (var optNode of commentQuestion.options)
n@1026 2652 {
n@1026 2653 var div = document.createElement('div');
n@854 2654 div.style.width = '80px';
n@1026 2655 div.style.float = 'left';
n@1026 2656 var input = document.createElement('input');
n@1026 2657 input.type = 'radio';
n@1026 2658 input.name = commentQuestion.id;
n@1124 2659 input.setAttribute('setvalue',optNode.name);
n@1026 2660 input.className = 'comment-radio';
n@1026 2661 div.appendChild(input);
n@1026 2662 this.inputs.appendChild(div);
n@1026 2663
n@1026 2664
n@1026 2665 div = document.createElement('div');
n@854 2666 div.style.width = '80px';
n@1026 2667 div.style.float = 'left';
n@1026 2668 div.align = 'center';
n@1026 2669 var span = document.createElement('span');
n@1124 2670 span.textContent = optNode.text;
n@1026 2671 span.className = 'comment-radio-span';
n@1026 2672 div.appendChild(span);
n@1026 2673 this.span.appendChild(div);
n@1026 2674 this.options.push(input);
n@1026 2675 }
n@1026 2676 this.holder.appendChild(this.span);
n@1026 2677 this.holder.appendChild(this.inputs);
n@1026 2678
n@1189 2679 this.exportXMLDOM = function(storePoint) {
n@1189 2680 var root = storePoint.parent.document.createElement('comment');
n@1026 2681 root.id = this.specification.id;
n@1026 2682 root.setAttribute('type',this.specification.type);
n@1026 2683 var question = document.createElement('question');
n@1026 2684 question.textContent = this.string.textContent;
n@1026 2685 var response = document.createElement('response');
n@1026 2686 var i=0;
n@1026 2687 while(this.options[i].checked == false) {
n@1026 2688 i++;
n@1026 2689 if (i >= this.options.length) {
n@1026 2690 break;
n@1026 2691 }
n@1026 2692 }
n@1026 2693 if (i >= this.options.length) {
n@1026 2694 response.textContent = 'null';
n@1026 2695 } else {
n@1026 2696 response.textContent = this.options[i].getAttribute('setvalue');
n@1026 2697 response.setAttribute('number',i);
n@1026 2698 }
n@902 2699 console.log('Comment: '+question.textContent);
n@902 2700 console.log('Response: '+response.textContent);
n@1026 2701 root.appendChild(question);
n@1026 2702 root.appendChild(response);
n@1189 2703 storePoint.XMLDOM.appendChild(root);
n@1026 2704 return root;
n@1026 2705 };
n@855 2706 this.resize = function()
n@855 2707 {
n@855 2708 var boxwidth = (window.innerWidth-100)/2;
n@855 2709 if (boxwidth >= 600)
n@855 2710 {
n@855 2711 boxwidth = 600;
n@855 2712 }
n@855 2713 else if (boxwidth < 400)
n@855 2714 {
n@855 2715 boxwidth = 400;
n@855 2716 }
n@855 2717 this.holder.style.width = boxwidth+"px";
n@855 2718 var text = this.holder.children[2];
n@855 2719 var options = this.holder.children[3];
n@855 2720 var optCount = options.children.length;
n@855 2721 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@855 2722 var options = options.firstChild;
n@855 2723 var text = text.firstChild;
n@855 2724 options.style.marginRight = spanMargin;
n@855 2725 options.style.marginLeft = spanMargin;
n@855 2726 text.style.marginRight = spanMargin;
n@855 2727 text.style.marginLeft = spanMargin;
n@855 2728 while(options.nextSibling != undefined)
n@855 2729 {
n@855 2730 options = options.nextSibling;
n@855 2731 text = text.nextSibling;
n@855 2732 options.style.marginRight = spanMargin;
n@855 2733 options.style.marginLeft = spanMargin;
n@855 2734 text.style.marginRight = spanMargin;
n@855 2735 text.style.marginLeft = spanMargin;
n@855 2736 }
n@855 2737 };
n@855 2738 this.resize();
n@1026 2739 };
n@1026 2740
n@902 2741 this.checkboxBox = function(commentQuestion) {
n@902 2742 this.specification = commentQuestion;
n@902 2743 // Create document objects to hold the comment boxes
n@902 2744 this.holder = document.createElement('div');
n@902 2745 this.holder.className = 'comment-div';
n@902 2746 // Create a string next to each comment asking for a comment
n@902 2747 this.string = document.createElement('span');
n@902 2748 this.string.innerHTML = commentQuestion.statement;
n@902 2749 var br = document.createElement('br');
n@902 2750 // Add to the holder.
n@902 2751 this.holder.appendChild(this.string);
n@902 2752 this.holder.appendChild(br);
n@902 2753 this.options = [];
n@902 2754 this.inputs = document.createElement('div');
n@902 2755 this.span = document.createElement('div');
n@902 2756 this.inputs.align = 'center';
n@902 2757 this.inputs.style.marginLeft = '12px';
n@902 2758 this.span.style.marginLeft = '12px';
n@902 2759 this.span.align = 'center';
n@902 2760 this.span.style.marginTop = '15px';
n@902 2761
n@902 2762 var optCount = commentQuestion.options.length;
n@902 2763 for (var i=0; i<optCount; i++)
n@902 2764 {
n@902 2765 var div = document.createElement('div');
n@854 2766 div.style.width = '80px';
n@902 2767 div.style.float = 'left';
n@902 2768 var input = document.createElement('input');
n@902 2769 input.type = 'checkbox';
n@902 2770 input.name = commentQuestion.id;
n@902 2771 input.setAttribute('setvalue',commentQuestion.options[i].name);
n@902 2772 input.className = 'comment-radio';
n@902 2773 div.appendChild(input);
n@902 2774 this.inputs.appendChild(div);
n@902 2775
n@902 2776
n@902 2777 div = document.createElement('div');
n@854 2778 div.style.width = '80px';
n@902 2779 div.style.float = 'left';
n@902 2780 div.align = 'center';
n@902 2781 var span = document.createElement('span');
n@902 2782 span.textContent = commentQuestion.options[i].text;
n@902 2783 span.className = 'comment-radio-span';
n@902 2784 div.appendChild(span);
n@902 2785 this.span.appendChild(div);
n@902 2786 this.options.push(input);
n@902 2787 }
n@902 2788 this.holder.appendChild(this.span);
n@902 2789 this.holder.appendChild(this.inputs);
n@902 2790
n@1189 2791 this.exportXMLDOM = function(storePoint) {
n@1189 2792 var root = storePoint.parent.document.createElement('comment');
n@902 2793 root.id = this.specification.id;
n@902 2794 root.setAttribute('type',this.specification.type);
n@902 2795 var question = document.createElement('question');
n@902 2796 question.textContent = this.string.textContent;
n@902 2797 root.appendChild(question);
n@902 2798 console.log('Comment: '+question.textContent);
n@902 2799 for (var i=0; i<this.options.length; i++) {
n@902 2800 var response = document.createElement('response');
n@902 2801 response.textContent = this.options[i].checked;
n@902 2802 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
n@902 2803 root.appendChild(response);
n@902 2804 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
n@902 2805 }
n@1189 2806 storePoint.XMLDOM.appendChild(root);
n@902 2807 return root;
n@902 2808 };
n@855 2809 this.resize = function()
n@855 2810 {
n@855 2811 var boxwidth = (window.innerWidth-100)/2;
n@855 2812 if (boxwidth >= 600)
n@855 2813 {
n@855 2814 boxwidth = 600;
n@855 2815 }
n@855 2816 else if (boxwidth < 400)
n@855 2817 {
n@855 2818 boxwidth = 400;
n@855 2819 }
n@855 2820 this.holder.style.width = boxwidth+"px";
n@855 2821 var text = this.holder.children[2];
n@855 2822 var options = this.holder.children[3];
n@855 2823 var optCount = options.children.length;
n@855 2824 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@855 2825 var options = options.firstChild;
n@855 2826 var text = text.firstChild;
n@855 2827 options.style.marginRight = spanMargin;
n@855 2828 options.style.marginLeft = spanMargin;
n@855 2829 text.style.marginRight = spanMargin;
n@855 2830 text.style.marginLeft = spanMargin;
n@855 2831 while(options.nextSibling != undefined)
n@855 2832 {
n@855 2833 options = options.nextSibling;
n@855 2834 text = text.nextSibling;
n@855 2835 options.style.marginRight = spanMargin;
n@855 2836 options.style.marginLeft = spanMargin;
n@855 2837 text.style.marginRight = spanMargin;
n@855 2838 text.style.marginLeft = spanMargin;
n@855 2839 }
n@855 2840 };
n@855 2841 this.resize();
n@902 2842 };
nicholas@900 2843
n@1026 2844 this.createCommentQuestion = function(element) {
n@1026 2845 var node;
n@1124 2846 if (element.type == 'question') {
n@1026 2847 node = new this.commentBox(element);
n@1026 2848 } else if (element.type == 'radio') {
n@1026 2849 node = new this.radioBox(element);
n@902 2850 } else if (element.type == 'checkbox') {
n@902 2851 node = new this.checkboxBox(element);
n@1026 2852 }
n@1026 2853 this.commentQuestions.push(node);
n@1026 2854 return node;
n@1026 2855 };
n@894 2856
nicholas@1045 2857 this.deleteCommentQuestions = function()
nicholas@1045 2858 {
nicholas@1045 2859 this.commentQuestions = [];
nicholas@1045 2860 };
nicholas@1045 2861
n@894 2862 this.playhead = new function()
n@894 2863 {
n@894 2864 this.object = document.createElement('div');
n@894 2865 this.object.className = 'playhead';
n@894 2866 this.object.align = 'left';
n@894 2867 var curTime = document.createElement('div');
n@894 2868 curTime.style.width = '50px';
n@894 2869 this.curTimeSpan = document.createElement('span');
n@894 2870 this.curTimeSpan.textContent = '00:00';
n@894 2871 curTime.appendChild(this.curTimeSpan);
n@894 2872 this.object.appendChild(curTime);
n@894 2873 this.scrubberTrack = document.createElement('div');
n@894 2874 this.scrubberTrack.className = 'playhead-scrub-track';
n@894 2875
n@894 2876 this.scrubberHead = document.createElement('div');
n@894 2877 this.scrubberHead.id = 'playhead-scrubber';
n@894 2878 this.scrubberTrack.appendChild(this.scrubberHead);
n@894 2879 this.object.appendChild(this.scrubberTrack);
n@894 2880
n@894 2881 this.timePerPixel = 0;
n@894 2882 this.maxTime = 0;
n@894 2883
n@897 2884 this.playbackObject;
n@897 2885
n@897 2886 this.setTimePerPixel = function(audioObject) {
n@894 2887 //maxTime must be in seconds
n@897 2888 this.playbackObject = audioObject;
n@773 2889 this.maxTime = audioObject.buffer.buffer.duration;
n@894 2890 var width = 490; //500 - 10, 5 each side of the tracker head
n@897 2891 this.timePerPixel = this.maxTime/490;
n@897 2892 if (this.maxTime < 60) {
n@894 2893 this.curTimeSpan.textContent = '0.00';
n@894 2894 } else {
n@894 2895 this.curTimeSpan.textContent = '00:00';
n@894 2896 }
n@894 2897 };
n@894 2898
n@897 2899 this.update = function() {
n@894 2900 // Update the playhead position, startPlay must be called
n@894 2901 if (this.timePerPixel > 0) {
n@897 2902 var time = this.playbackObject.getCurrentPosition();
n@1167 2903 if (time > 0 && time < this.maxTime) {
nicholas@860 2904 var width = 490;
nicholas@860 2905 var pix = Math.floor(time/this.timePerPixel);
nicholas@860 2906 this.scrubberHead.style.left = pix+'px';
nicholas@860 2907 if (this.maxTime > 60.0) {
nicholas@860 2908 var secs = time%60;
nicholas@860 2909 var mins = Math.floor((time-secs)/60);
nicholas@860 2910 secs = secs.toString();
nicholas@860 2911 secs = secs.substr(0,2);
nicholas@860 2912 mins = mins.toString();
nicholas@860 2913 this.curTimeSpan.textContent = mins+':'+secs;
nicholas@860 2914 } else {
nicholas@860 2915 time = time.toString();
nicholas@860 2916 this.curTimeSpan.textContent = time.substr(0,4);
nicholas@860 2917 }
n@894 2918 } else {
nicholas@860 2919 this.scrubberHead.style.left = '0px';
nicholas@860 2920 if (this.maxTime < 60) {
nicholas@860 2921 this.curTimeSpan.textContent = '0.00';
nicholas@860 2922 } else {
nicholas@860 2923 this.curTimeSpan.textContent = '00:00';
nicholas@860 2924 }
n@894 2925 }
n@894 2926 }
n@894 2927 };
n@897 2928
n@897 2929 this.interval = undefined;
n@897 2930
n@897 2931 this.start = function() {
n@897 2932 if (this.playbackObject != undefined && this.interval == undefined) {
nicholas@860 2933 if (this.maxTime < 60) {
nicholas@860 2934 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nicholas@860 2935 } else {
nicholas@860 2936 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nicholas@860 2937 }
n@897 2938 }
n@897 2939 };
n@897 2940 this.stop = function() {
n@897 2941 clearInterval(this.interval);
n@897 2942 this.interval = undefined;
n@1193 2943 this.scrubberHead.style.left = '0px';
nicholas@860 2944 if (this.maxTime < 60) {
nicholas@860 2945 this.curTimeSpan.textContent = '0.00';
nicholas@860 2946 } else {
nicholas@860 2947 this.curTimeSpan.textContent = '00:00';
nicholas@860 2948 }
n@897 2949 };
n@894 2950 };
n@1154 2951
n@1154 2952 this.volume = new function()
n@1154 2953 {
n@1154 2954 // An in-built volume module which can be viewed on page
n@1154 2955 // Includes trackers on page-by-page data
n@1154 2956 // Volume does NOT reset to 0dB on each page load
n@1154 2957 this.valueLin = 1.0;
n@1154 2958 this.valueDB = 0.0;
n@1154 2959 this.object = document.createElement('div');
n@1154 2960 this.object.id = 'master-volume-holder';
n@1154 2961 this.slider = document.createElement('input');
n@1154 2962 this.slider.id = 'master-volume-control';
n@1154 2963 this.slider.type = 'range';
n@1154 2964 this.valueText = document.createElement('span');
n@1154 2965 this.valueText.id = 'master-volume-feedback';
n@1154 2966 this.valueText.textContent = '0dB';
n@1154 2967
n@1154 2968 this.slider.min = -60;
n@1154 2969 this.slider.max = 12;
n@1154 2970 this.slider.value = 0;
n@1154 2971 this.slider.step = 1;
n@1154 2972 this.slider.onmousemove = function(event)
n@1154 2973 {
n@1154 2974 interfaceContext.volume.valueDB = event.currentTarget.value;
n@1154 2975 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
n@1154 2976 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
n@1154 2977 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
n@1154 2978 }
n@1154 2979 this.slider.onmouseup = function(event)
n@1154 2980 {
n@1192 2981 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
n@1154 2982 if (storePoint.length == 0)
n@1154 2983 {
n@1154 2984 storePoint = storage.document.createElement('metricresult');
n@1154 2985 storePoint.setAttribute('name','volumeTracker');
n@1192 2986 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
n@1154 2987 }
n@1154 2988 else {
n@1154 2989 storePoint = storePoint[0];
n@1154 2990 }
n@1154 2991 var node = storage.document.createElement('movement');
n@1154 2992 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
n@1154 2993 node.setAttribute('volume',interfaceContext.volume.valueDB);
n@1154 2994 node.setAttribute('format','dBFS');
n@1154 2995 storePoint.appendChild(node);
n@1154 2996 }
n@1154 2997
n@1155 2998 var title = document.createElement('div');
n@1155 2999 title.innerHTML = '<span>Master Volume Control</span>';
n@1155 3000 title.style.fontSize = '0.75em';
n@1155 3001 title.style.width = "100%";
n@1155 3002 title.align = 'center';
n@1155 3003 this.object.appendChild(title);
n@1155 3004
n@1154 3005 this.object.appendChild(this.slider);
n@1154 3006 this.object.appendChild(this.valueText);
n@1154 3007 }
nicholas@1043 3008 // Global Checkers
nicholas@1043 3009 // These functions will help enforce the checkers
nicholas@1043 3010 this.checkHiddenAnchor = function()
nicholas@1043 3011 {
n@1124 3012 for (var ao of audioEngineContext.audioObjects)
nicholas@1043 3013 {
n@1124 3014 if (ao.specification.type == "anchor")
nicholas@1043 3015 {
n@1125 3016 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
n@1124 3017 // Anchor is not set below
n@1124 3018 console.log('Anchor node not below marker value');
n@1124 3019 alert('Please keep listening');
n@1167 3020 this.storeErrorNode('Anchor node not below marker value');
n@1124 3021 return false;
n@1124 3022 }
nicholas@1043 3023 }
nicholas@1043 3024 }
nicholas@1043 3025 return true;
nicholas@1043 3026 };
nicholas@1043 3027
nicholas@1043 3028 this.checkHiddenReference = function()
nicholas@1043 3029 {
n@1124 3030 for (var ao of audioEngineContext.audioObjects)
nicholas@1043 3031 {
n@1124 3032 if (ao.specification.type == "reference")
nicholas@1043 3033 {
n@1125 3034 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
n@1124 3035 // Anchor is not set below
n@1167 3036 console.log('Reference node not above marker value');
n@1167 3037 this.storeErrorNode('Reference node not above marker value');
n@1124 3038 alert('Please keep listening');
n@1124 3039 return false;
n@1124 3040 }
nicholas@1043 3041 }
nicholas@1043 3042 }
nicholas@1043 3043 return true;
nicholas@1043 3044 };
n@837 3045
n@837 3046 this.checkFragmentsFullyPlayed = function ()
n@837 3047 {
n@837 3048 // Checks the entire file has been played back
n@837 3049 // NOTE ! This will return true IF playback is Looped!!!
n@837 3050 if (audioEngineContext.loopPlayback)
n@837 3051 {
n@837 3052 console.log("WARNING - Looped source: Cannot check fragments are fully played");
n@837 3053 return true;
n@837 3054 }
n@837 3055 var check_pass = true;
n@837 3056 var error_obj = [];
n@837 3057 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
n@837 3058 {
n@837 3059 var object = audioEngineContext.audioObjects[i];
nicholas@756 3060 var time = object.buffer.buffer.duration;
n@837 3061 var metric = object.metric;
n@837 3062 var passed = false;
n@837 3063 for (var j=0; j<metric.listenTracker.length; j++)
n@837 3064 {
n@837 3065 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
n@837 3066 var start_time = Number(bt[0].getAttribute('start'));
n@837 3067 var stop_time = Number(bt[0].getAttribute('stop'));
n@837 3068 var delta = stop_time - start_time;
n@837 3069 if (delta >= time)
n@837 3070 {
n@837 3071 passed = true;
n@837 3072 break;
n@837 3073 }
n@837 3074 }
n@837 3075 if (passed == false)
n@837 3076 {
n@837 3077 check_pass = false;
n@1091 3078 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
n@1091 3079 error_obj.push(object.interfaceDOM.getPresentedId());
n@837 3080 }
n@837 3081 }
n@837 3082 if (check_pass == false)
n@837 3083 {
nicholas@756 3084 var str_start = "You have not completely listened to fragments ";
n@837 3085 for (var i=0; i<error_obj.length; i++)
n@837 3086 {
n@837 3087 str_start += error_obj[i];
n@837 3088 if (i != error_obj.length-1)
n@837 3089 {
n@837 3090 str_start += ', ';
n@837 3091 }
n@837 3092 }
n@837 3093 str_start += ". Please keep listening";
n@837 3094 console.log("[ALERT]: "+str_start);
n@1167 3095 this.storeErrorNode("[ALERT]: "+str_start);
n@837 3096 alert(str_start);
n@837 3097 }
n@837 3098 };
nicholas@762 3099 this.checkAllMoved = function()
nicholas@762 3100 {
nicholas@762 3101 var str = "You have not moved ";
nicholas@762 3102 var failed = [];
n@1140 3103 for (var ao of audioEngineContext.audioObjects)
nicholas@762 3104 {
n@1140 3105 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
nicholas@762 3106 {
n@1140 3107 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@762 3108 }
nicholas@762 3109 }
nicholas@762 3110 if (failed.length == 0)
nicholas@762 3111 {
nicholas@762 3112 return true;
nicholas@762 3113 } else if (failed.length == 1)
nicholas@762 3114 {
nicholas@762 3115 str += 'track '+failed[0];
nicholas@762 3116 } else {
nicholas@762 3117 str += 'tracks ';
nicholas@762 3118 for (var i=0; i<failed.length-1; i++)
nicholas@762 3119 {
nicholas@762 3120 str += failed[i]+', ';
nicholas@762 3121 }
nicholas@762 3122 str += 'and '+failed[i];
nicholas@762 3123 }
nicholas@762 3124 str +='.';
nicholas@762 3125 alert(str);
nicholas@762 3126 console.log(str);
n@1167 3127 this.storeErrorNode(str);
nicholas@762 3128 return false;
nicholas@762 3129 };
nicholas@762 3130 this.checkAllPlayed = function()
nicholas@762 3131 {
nicholas@762 3132 var str = "You have not played ";
nicholas@762 3133 var failed = [];
n@1140 3134 for (var ao of audioEngineContext.audioObjects)
nicholas@762 3135 {
n@1140 3136 if(ao.metric.wasListenedTo == false)
nicholas@762 3137 {
n@1140 3138 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@762 3139 }
nicholas@762 3140 }
nicholas@762 3141 if (failed.length == 0)
nicholas@762 3142 {
nicholas@762 3143 return true;
nicholas@762 3144 } else if (failed.length == 1)
nicholas@762 3145 {
nicholas@762 3146 str += 'track '+failed[0];
nicholas@762 3147 } else {
nicholas@762 3148 str += 'tracks ';
nicholas@762 3149 for (var i=0; i<failed.length-1; i++)
nicholas@762 3150 {
nicholas@762 3151 str += failed[i]+', ';
nicholas@762 3152 }
nicholas@762 3153 str += 'and '+failed[i];
nicholas@762 3154 }
nicholas@762 3155 str +='.';
nicholas@762 3156 alert(str);
nicholas@762 3157 console.log(str);
n@1167 3158 this.storeErrorNode(str);
nicholas@762 3159 return false;
nicholas@762 3160 };
n@1167 3161
n@1167 3162 this.storeErrorNode = function(errorMessage)
n@1167 3163 {
n@1167 3164 var time = audioEngineContext.timer.getTestTime();
n@1167 3165 var node = storage.document.createElement('error');
n@1167 3166 node.setAttribute('time',time);
n@1167 3167 node.textContent = errorMessage;
n@1167 3168 testState.currentStore.XMLDOM.appendChild(node);
n@1167 3169 };
n@1124 3170 }
n@1124 3171
n@1124 3172 function Storage()
n@1124 3173 {
n@1124 3174 // Holds results in XML format until ready for collection
n@1124 3175 this.globalPreTest = null;
n@1124 3176 this.globalPostTest = null;
n@1124 3177 this.testPages = [];
n@1095 3178 this.document = null;
n@1095 3179 this.root = null;
n@1124 3180 this.state = 0;
n@1124 3181
n@1095 3182 this.initialise = function(existingStore)
n@1124 3183 {
n@1095 3184 if (existingStore == undefined) {
n@1239 3185 // We need to get the sessionKey
n@1239 3186 this.SessionKey.generateKey();
n@1095 3187 this.document = document.implementation.createDocument(null,"waetresult");
n@1095 3188 this.root = this.document.childNodes[0];
n@1242 3189 var projectDocument = specification.projectXML;
n@1242 3190 projectDocument.setAttribute('file-name',url);
n@1242 3191 this.root.appendChild(projectDocument);
n@1242 3192 this.root.appendChild(returnDateNode());
n@1242 3193 this.root.appendChild(interfaceContext.returnNavigator());
n@1239 3194 } else {
n@1095 3195 this.document = existingStore;
n@1095 3196 this.root = existingStore.children[0];
n@1095 3197 this.SessionKey.key = this.root.getAttribute("key");
n@1239 3198 }
n@1242 3199 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
n@1095 3200 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
n@1124 3201 };
n@1239 3202
n@1239 3203 this.SessionKey = {
n@1239 3204 key: null,
n@1239 3205 request: new XMLHttpRequest(),
n@1239 3206 parent: this,
n@1239 3207 handleEvent: function() {
n@1239 3208 var parse = new DOMParser();
n@1239 3209 var xml = parse.parseFromString(this.request.response,"text/xml");
n@1239 3210 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
n@1239 3211 this.key = xml.getAllElementsByTagName("key")[0].textContent;
n@1242 3212 this.parent.root.setAttribute("key",this.key);
n@1242 3213 this.parent.root.setAttribute("state","empty");
n@1239 3214 } else {
n@1239 3215 this.generateKey();
n@1239 3216 }
n@1239 3217 },
n@1239 3218 generateKey: function() {
n@1239 3219 var temp_key = randomString(32);
n@1239 3220 this.request.open("GET","keygen.php?key="+temp_key,true);
n@1239 3221 this.request.addEventListener("load",this);
n@1239 3222 this.request.send();
n@1241 3223 },
n@1242 3224 update: function() {
n@1242 3225 this.parent.root.setAttribute("state","update");
n@1242 3226 var xmlhttp = new XMLHttpRequest();
n@1242 3227 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
n@1242 3228 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
n@1242 3229 xmlhttp.onerror = function(){
n@1242 3230 console.log('Error updating file to server!');
n@1242 3231 };
n@1242 3232 var hold = document.createElement("div");
n@1242 3233 var clone = this.parent.root.cloneNode(true);
n@1242 3234 hold.appendChild(clone);
n@1242 3235 xmlhttp.onload = function() {
n@1242 3236 if (this.status >= 300) {
n@1242 3237 console.log("WARNING - Could not update at this time");
n@1242 3238 } else {
n@1242 3239 var parser = new DOMParser();
n@1242 3240 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
n@1242 3241 var response = xmlDoc.getElementsByTagName('response')[0];
n@1242 3242 if (response.getAttribute("state") == "OK") {
n@1242 3243 var file = response.getElementsByTagName("file")[0];
n@1242 3244 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
n@1242 3245 } else {
n@1242 3246 var message = response.getElementsByTagName("message");
n@1242 3247 console.log("Intermediate save: Error! "+message.textContent);
n@1242 3248 }
n@1241 3249 }
n@1241 3250 }
n@1242 3251 xmlhttp.send([hold.innerHTML]);
n@1239 3252 }
n@1239 3253 }
n@1124 3254
n@1124 3255 this.createTestPageStore = function(specification)
n@1124 3256 {
n@1124 3257 var store = new this.pageNode(this,specification);
n@1124 3258 this.testPages.push(store);
n@1124 3259 return this.testPages[this.testPages.length-1];
n@1124 3260 };
n@1124 3261
n@1124 3262 this.surveyNode = function(parent,root,specification)
n@1124 3263 {
n@1124 3264 this.specification = specification;
n@1124 3265 this.parent = parent;
n@1095 3266 this.state = "empty";
n@1124 3267 this.XMLDOM = this.parent.document.createElement('survey');
n@1124 3268 this.XMLDOM.setAttribute('location',this.specification.location);
n@1095 3269 this.XMLDOM.setAttribute("state",this.state);
n@1124 3270 for (var optNode of this.specification.options)
n@1124 3271 {
n@1124 3272 if (optNode.type != 'statement')
n@1124 3273 {
n@1124 3274 var node = this.parent.document.createElement('surveyresult');
n@1095 3275 node.setAttribute("ref",optNode.id);
n@1124 3276 node.setAttribute('type',optNode.type);
n@1124 3277 this.XMLDOM.appendChild(node);
n@1124 3278 }
n@1124 3279 }
n@1124 3280 root.appendChild(this.XMLDOM);
n@1124 3281
n@1124 3282 this.postResult = function(node)
n@1124 3283 {
n@1124 3284 // From popup: node is the popupOption node containing both spec. and results
n@1124 3285 // ID is the position
n@1124 3286 if (node.specification.type == 'statement'){return;}
n@1095 3287 var surveyresult = this.XMLDOM.children[0];
n@1095 3288 while(surveyresult != null) {
n@1095 3289 if (surveyresult.getAttribute("ref") == node.specification.id)
n@1095 3290 {
n@1095 3291 break;
n@1095 3292 }
n@1095 3293 surveyresult = surveyresult.nextElementSibling;
n@1095 3294 }
n@1124 3295 switch(node.specification.type)
n@1124 3296 {
n@1124 3297 case "number":
n@1124 3298 case "question":
n@1124 3299 var child = this.parent.document.createElement('response');
n@1124 3300 child.textContent = node.response;
n@1124 3301 surveyresult.appendChild(child);
n@1124 3302 break;
n@1124 3303 case "radio":
n@1124 3304 var child = this.parent.document.createElement('response');
n@1124 3305 child.setAttribute('name',node.response.name);
n@1124 3306 child.textContent = node.response.text;
n@1124 3307 surveyresult.appendChild(child);
n@1124 3308 break;
n@1124 3309 case "checkbox":
n@1124 3310 for (var i=0; i<node.response.length; i++)
n@1124 3311 {
n@1124 3312 var checkNode = this.parent.document.createElement('response');
n@1147 3313 checkNode.setAttribute('name',node.response[i].name);
n@1147 3314 checkNode.setAttribute('checked',node.response[i].checked);
n@1126 3315 surveyresult.appendChild(checkNode);
n@1124 3316 }
n@1124 3317 break;
n@1124 3318 }
n@1124 3319 };
n@1095 3320 this.complete = function() {
n@1095 3321 this.state = "complete";
n@1095 3322 this.XMLDOM.setAttribute("state",this.state);
n@1095 3323 }
n@1124 3324 };
n@1124 3325
n@1124 3326 this.pageNode = function(parent,specification)
n@1124 3327 {
n@1124 3328 // Create one store per test page
n@1124 3329 this.specification = specification;
n@1124 3330 this.parent = parent;
n@1095 3331 this.state = "empty";
n@1124 3332 this.XMLDOM = this.parent.document.createElement('page');
n@1095 3333 this.XMLDOM.setAttribute('ref',specification.id);
n@1124 3334 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
n@1095 3335 this.XMLDOM.setAttribute("state",this.state);
n@1145 3336 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
n@1145 3337 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
n@1124 3338
n@1124 3339 // Add any page metrics
n@1124 3340 var page_metric = this.parent.document.createElement('metric');
n@1124 3341 this.XMLDOM.appendChild(page_metric);
n@1124 3342
n@1124 3343 // Add the audioelement
n@1124 3344 for (var element of this.specification.audioElements)
n@1124 3345 {
n@1124 3346 var aeNode = this.parent.document.createElement('audioelement');
n@1095 3347 aeNode.setAttribute('ref',element.id);
n@1095 3348 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
n@1124 3349 aeNode.setAttribute('type',element.type);
n@1124 3350 aeNode.setAttribute('url', element.url);
n@1124 3351 aeNode.setAttribute('gain', element.gain);
n@1124 3352 if (element.type == 'anchor' || element.type == 'reference')
n@1124 3353 {
n@1124 3354 if (element.marker > 0)
n@1124 3355 {
n@1124 3356 aeNode.setAttribute('marker',element.marker);
n@1124 3357 }
n@1124 3358 }
n@1124 3359 var ae_metric = this.parent.document.createElement('metric');
n@1124 3360 aeNode.appendChild(ae_metric);
n@1124 3361 this.XMLDOM.appendChild(aeNode);
n@1124 3362 }
n@1124 3363
n@1124 3364 this.parent.root.appendChild(this.XMLDOM);
n@1095 3365
n@1095 3366 this.complete = function() {
n@1095 3367 this.state = "complete";
n@1095 3368 this.XMLDOM.setAttribute("state","complete");
n@1095 3369 }
n@1124 3370 };
n@1242 3371 this.update = function() {
n@1242 3372 this.SessionKey.update();
n@1242 3373 }
n@1124 3374 this.finish = function()
n@1124 3375 {
n@1124 3376 if (this.state == 0)
n@1124 3377 {
n@1242 3378 this.update();
n@1124 3379 }
n@1124 3380 this.state = 1;
n@1124 3381 return this.root;
n@1124 3382 };
n@1124 3383 }