annotate core.js @ 1257:3600fff74af9

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