annotate core.js @ 1277:c3666ac704ba

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