annotate core.js @ 2169:b1675f657e3c

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