annotate core.js @ 1096:9820063ea96a

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