annotate core.js @ 1259:96a830d58da7

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