annotate core.js @ 2178:c5e9c8eb1852

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