annotate core.js @ 1270:aebb9754d491

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