annotate core.js @ 1283:b24d861c96b3

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