annotate core.js @ 2175:d90cbba6299b

Hotfix. date time node in results returned function reference for minutes.
author Nicholas Jillings <nickjillings@users.noreply.github.com>
date Tue, 22 Mar 2016 11:31:51 +0000
parents 4f769a43d8ed
children a7ad6fc6a3b2
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@1580 1768 function Specification() {
nickjillings@1580 1769 // Handles the decoding of the project specification XML into a simple JavaScript Object.
nickjillings@1580 1770
nickjillings@1324 1771 this.interface = null;
nickjillings@1373 1772 this.projectReturn = "null";
nickjillings@1324 1773 this.randomiseOrder = null;
nickjillings@1324 1774 this.testPages = null;
nickjillings@1324 1775 this.pages = [];
nickjillings@1324 1776 this.metrics = null;
nickjillings@1324 1777 this.interfaces = null;
nickjillings@1324 1778 this.loudness = null;
nickjillings@1324 1779 this.errors = [];
nickjillings@1324 1780 this.schema = null;
nickjillings@1318 1781
nickjillings@2165 1782 this.processAttribute = function(attribute,schema,schemaRoot)
nickjillings@1406 1783 {
nickjillings@1324 1784 // attribute is the string returned from getAttribute on the XML
nickjillings@1324 1785 // schema is the <xs:attribute> node
nickjillings@1324 1786 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
nickjillings@1406 1787 {
nickjillings@2165 1788 schema = schemaRoot.getAllElementsByName(schema.getAttribute('ref'))[0];
nickjillings@1324 1789 }
nickjillings@1324 1790 var defaultOpt = schema.getAttribute('default');
nickjillings@1324 1791 if (attribute == null) {
nickjillings@1324 1792 attribute = defaultOpt;
nickjillings@1324 1793 }
nickjillings@1324 1794 var dataType = schema.getAttribute('type');
nickjillings@1324 1795 if (typeof dataType == "string") { dataType = dataType.substr(3);}
nickjillings@1324 1796 else {dataType = "string";}
nickjillings@1324 1797 if (attribute == null)
nickjillings@1324 1798 {
nickjillings@1324 1799 return attribute;
nickjillings@1324 1800 }
nickjillings@1324 1801 switch(dataType)
nickjillings@1324 1802 {
nickjillings@1324 1803 case "boolean":
nickjillings@1324 1804 if (attribute == 'true'){attribute = true;}else{attribute=false;}
nickjillings@1324 1805 break;
nickjillings@1324 1806 case "negativeInteger":
nickjillings@1324 1807 case "positiveInteger":
nickjillings@1324 1808 case "nonNegativeInteger":
nickjillings@1324 1809 case "nonPositiveInteger":
nickjillings@1324 1810 case "integer":
nickjillings@1324 1811 case "decimal":
nickjillings@1324 1812 case "short":
nickjillings@1324 1813 attribute = Number(attribute);
nickjillings@1324 1814 break;
nickjillings@1324 1815 case "string":
nickjillings@1324 1816 default:
nickjillings@1324 1817 attribute = String(attribute);
nickjillings@1324 1818 break;
nickjillings@1324 1819 }
nickjillings@1324 1820 return attribute;
nickjillings@1406 1821 };
nickjillings@1411 1822
nickjillings@1406 1823 this.decode = function(projectXML) {
nickjillings@1324 1824 this.errors = [];
nickjillings@1580 1825 // projectXML - DOM Parsed document
nickjillings@2052 1826 this.projectXML = projectXML.childNodes[0];
nickjillings@1580 1827 var setupNode = projectXML.getElementsByTagName('setup')[0];
nickjillings@1348 1828 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
nickjillings@1324 1829 // First decode the attributes
nickjillings@1348 1830 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
nickjillings@1324 1831 for (var i in attributes)
nickjillings@1520 1832 {
nickjillings@1324 1833 if (isNaN(Number(i)) == true){break;}
nickjillings@2165 1834 var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
nickjillings@1324 1835 var projectAttr = setupNode.getAttribute(attributeName);
nickjillings@2165 1836 projectAttr = this.processAttribute(projectAttr,attributes[i],this.schema);
nickjillings@1324 1837 switch(typeof projectAttr)
nickjillings@1432 1838 {
nickjillings@1324 1839 case "number":
nickjillings@1324 1840 case "boolean":
nickjillings@1324 1841 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 1842 break;
nickjillings@1324 1843 case "string":
nickjillings@1324 1844 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 1845 break;
nickjillings@1432 1846 }
nickjillings@1324 1847
nickjillings@1406 1848 }
nickjillings@1406 1849
nickjillings@1370 1850 this.metrics = new this.metricNode();
nickjillings@1580 1851
nickjillings@1324 1852 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
nickjillings@1324 1853
nickjillings@1324 1854 // Now process the survey node options
nickjillings@1324 1855 var survey = setupNode.getElementsByTagName('survey');
nickjillings@1324 1856 for (var i in survey) {
nickjillings@1324 1857 if (isNaN(Number(i)) == true){break;}
nickjillings@1324 1858 var location = survey[i].getAttribute('location');
nickjillings@1324 1859 if (location == 'pre' || location == 'before')
nickjillings@1324 1860 {
nickjillings@1324 1861 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
nickjillings@1324 1862 else {
nickjillings@1324 1863 this.preTest = new this.surveyNode();
nickjillings@1370 1864 this.preTest.decode(this,survey[i]);
nickjillings@1324 1865 }
nickjillings@1324 1866 } else if (location == 'post' || location == 'after') {
nickjillings@1324 1867 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
nickjillings@1324 1868 else {
nickjillings@1324 1869 this.postTest = new this.surveyNode();
nickjillings@1370 1870 this.postTest.decode(this,survey[i]);
nickjillings@1324 1871 }
nickjillings@1580 1872 }
nickjillings@1580 1873 }
nickjillings@1580 1874
nickjillings@1324 1875 var interfaceNode = setupNode.getElementsByTagName('interface');
nickjillings@1324 1876 if (interfaceNode.length > 1)
nickjillings@1324 1877 {
nickjillings@1324 1878 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
nickjillings@1324 1879 }
nickjillings@1324 1880 this.interfaces = new this.interfaceNode();
nickjillings@1324 1881 if (interfaceNode.length != 0)
nickjillings@1324 1882 {
nickjillings@1324 1883 interfaceNode = interfaceNode[0];
nickjillings@1348 1884 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
nickjillings@1556 1885 }
nickjillings@1556 1886
nickjillings@1324 1887 // Page tags
nickjillings@1324 1888 var pageTags = projectXML.getElementsByTagName('page');
nickjillings@1348 1889 var pageSchema = this.schema.getAllElementsByName('page')[0];
nickjillings@1324 1890 for (var i=0; i<pageTags.length; i++)
nickjillings@1520 1891 {
nickjillings@1324 1892 var node = new this.page();
nickjillings@1324 1893 node.decode(this,pageTags[i],pageSchema);
nickjillings@1324 1894 this.pages.push(node);
nickjillings@1520 1895 }
nickjillings@1580 1896 };
nickjillings@1580 1897
nickjillings@1406 1898 this.encode = function()
nickjillings@1406 1899 {
nickjillings@1372 1900 var RootDocument = document.implementation.createDocument(null,"waet");
nickjillings@1372 1901 var root = RootDocument.children[0];
nickjillings@1372 1902 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
nickjillings@1372 1903 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
nickjillings@1324 1904 // Build setup node
nickjillings@1372 1905 var setup = RootDocument.createElement("setup");
nickjillings@1372 1906 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
nickjillings@1372 1907 // First decode the attributes
nickjillings@1372 1908 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
nickjillings@1372 1909 for (var i=0; i<attributes.length; i++)
nickjillings@1372 1910 {
nickjillings@1372 1911 var name = attributes[i].getAttribute("name");
nickjillings@1372 1912 if (name == undefined) {
nickjillings@1372 1913 name = attributes[i].getAttribute("ref");
nickjillings@1372 1914 }
nickjillings@1372 1915 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
nickjillings@1372 1916 {
nickjillings@1372 1917 eval("setup.setAttribute('"+name+"',this."+name+")");
nickjillings@1372 1918 }
nickjillings@1372 1919 }
nickjillings@1372 1920 root.appendChild(setup);
nickjillings@1372 1921 // Survey node
nickjillings@1372 1922 setup.appendChild(this.preTest.encode(RootDocument));
nickjillings@1372 1923 setup.appendChild(this.postTest.encode(RootDocument));
nickjillings@1372 1924 setup.appendChild(this.metrics.encode(RootDocument));
nickjillings@1372 1925 setup.appendChild(this.interfaces.encode(RootDocument));
nickjillings@1372 1926 for (var page of this.pages)
nickjillings@1372 1927 {
nickjillings@1372 1928 root.appendChild(page.encode(RootDocument));
nickjillings@1372 1929 }
nickjillings@1372 1930 return RootDocument;
nickjillings@1406 1931 };
nickjillings@1406 1932
nickjillings@1324 1933 this.surveyNode = function() {
nickjillings@1324 1934 this.location = null;
nickjillings@1580 1935 this.options = [];
nickjillings@2165 1936 this.parent = null;
nickjillings@1370 1937 this.schema = specification.schema.getAllElementsByName('survey')[0];
nickjillings@1580 1938
nickjillings@1406 1939 this.OptionNode = function() {
nickjillings@1406 1940 this.type = undefined;
nickjillings@1370 1941 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
nickjillings@1406 1942 this.id = undefined;
nickjillings@2156 1943 this.name = undefined;
nickjillings@1406 1944 this.mandatory = undefined;
nickjillings@1406 1945 this.statement = undefined;
nickjillings@1406 1946 this.boxsize = undefined;
nickjillings@1406 1947 this.options = [];
nickjillings@1406 1948 this.min = undefined;
nickjillings@1406 1949 this.max = undefined;
nickjillings@1406 1950 this.step = undefined;
nickjillings@1406 1951
nickjillings@1370 1952 this.decode = function(parent,child)
nickjillings@1406 1953 {
nickjillings@1370 1954 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 1955 for (var i in attributeMap){
nickjillings@1324 1956 if(isNaN(Number(i)) == true){break;}
nickjillings@1324 1957 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
nickjillings@1324 1958 var projectAttr = child.getAttribute(attributeName);
nickjillings@2165 1959 projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
nickjillings@1324 1960 switch(typeof projectAttr)
nickjillings@1324 1961 {
nickjillings@1324 1962 case "number":
nickjillings@1324 1963 case "boolean":
nickjillings@1324 1964 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 1965 break;
nickjillings@1324 1966 case "string":
nickjillings@1324 1967 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 1968 break;
nickjillings@1406 1969 }
nickjillings@1324 1970 }
nickjillings@1324 1971 this.statement = child.getElementsByTagName('statement')[0].textContent;
nickjillings@1324 1972 if (this.type == "checkbox" || this.type == "radio") {
nickjillings@1324 1973 var children = child.getElementsByTagName('option');
nickjillings@1324 1974 if (children.length == null) {
nickjillings@1406 1975 console.log('Malformed' +child.nodeName+ 'entry');
nickjillings@1406 1976 this.statement = 'Malformed' +child.nodeName+ 'entry';
nickjillings@1406 1977 this.type = 'statement';
nickjillings@1406 1978 } else {
nickjillings@1406 1979 this.options = [];
nickjillings@1324 1980 for (var i in children)
nickjillings@1324 1981 {
nickjillings@1324 1982 if (isNaN(Number(i))==true){break;}
nickjillings@1324 1983 this.options.push({
nickjillings@1324 1984 name: children[i].getAttribute('name'),
nickjillings@1324 1985 text: children[i].textContent
nickjillings@1324 1986 });
nickjillings@1406 1987 }
nickjillings@1406 1988 }
nickjillings@2030 1989 }
nickjillings@1406 1990 };
nickjillings@1406 1991
nickjillings@1372 1992 this.exportXML = function(doc)
nickjillings@1406 1993 {
nickjillings@2111 1994 var node = doc.createElement('surveyentry');
nickjillings@1324 1995 node.setAttribute('type',this.type);
nickjillings@1372 1996 var statement = doc.createElement('statement');
nickjillings@1324 1997 statement.textContent = this.statement;
nickjillings@1324 1998 node.appendChild(statement);
nickjillings@2162 1999 node.id = this.id;
nickjillings@2162 2000 if (this.name != undefined) { node.setAttribute("name",this.name);}
nickjillings@2162 2001 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
nickjillings@2162 2002 node.id = this.id;
nickjillings@2162 2003 if (this.name != undefined) {node.setAttribute("name",this.name);}
nickjillings@2162 2004 switch(this.type)
nickjillings@2162 2005 {
nickjillings@2156 2006 case "checkbox":
nickjillings@2156 2007 case "radio":
nickjillings@2156 2008 for (var i=0; i<this.options.length; i++)
nickjillings@2156 2009 {
nickjillings@2156 2010 var option = this.options[i];
nickjillings@2156 2011 var optionNode = doc.createElement("option");
nickjillings@2156 2012 optionNode.setAttribute("name",option.name);
nickjillings@2156 2013 optionNode.textContent = option.text;
nickjillings@2156 2014 node.appendChild(optionNode);
nickjillings@2156 2015 }
nickjillings@2162 2016 case "number":
nickjillings@2162 2017 if (this.min != undefined) {node.setAttribute("min", this.min);}
nickjillings@2162 2018 if (this.max != undefined) {node.setAttribute("max", this.max);}
nickjillings@2162 2019 case "question":
nickjillings@2162 2020 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
nickjillings@2162 2021 if (this.mandatory != undefined) {node.setAttribute("mandatory",this.mandatory);}
nickjillings@2162 2022 default:
nickjillings@2156 2023 break;
nickjillings@2156 2024 }
nickjillings@1406 2025 return node;
nickjillings@1406 2026 };
nickjillings@1406 2027 };
nickjillings@1370 2028 this.decode = function(parent,xml) {
nickjillings@2165 2029 this.parent = parent;
nickjillings@1324 2030 this.location = xml.getAttribute('location');
nickjillings@1324 2031 if (this.location == 'before'){this.location = 'pre';}
nickjillings@1324 2032 else if (this.location == 'after'){this.location = 'post';}
nickjillings@1324 2033 for (var i in xml.children)
nickjillings@1324 2034 {
nickjillings@1324 2035 if(isNaN(Number(i))==true){break;}
nickjillings@1406 2036 var node = new this.OptionNode();
nickjillings@1370 2037 node.decode(parent,xml.children[i]);
nickjillings@1406 2038 this.options.push(node);
nickjillings@1324 2039 }
nickjillings@1324 2040 };
nickjillings@1372 2041 this.encode = function(doc) {
nickjillings@1372 2042 var node = doc.createElement('survey');
nickjillings@1324 2043 node.setAttribute('location',this.location);
nickjillings@1324 2044 for (var i=0; i<this.options.length; i++)
nickjillings@1324 2045 {
nickjillings@1372 2046 node.appendChild(this.options[i].exportXML(doc));
nickjillings@1324 2047 }
nickjillings@1324 2048 return node;
nickjillings@1324 2049 };
nickjillings@1324 2050 };
nickjillings@1324 2051
nickjillings@1324 2052 this.interfaceNode = function()
nickjillings@1324 2053 {
nickjillings@1324 2054 this.title = null;
nickjillings@1324 2055 this.name = null;
nickjillings@1324 2056 this.options = [];
nickjillings@1324 2057 this.scales = [];
nickjillings@1370 2058 this.schema = specification.schema.getAllElementsByName('interface')[1];
nickjillings@1324 2059
nickjillings@1370 2060 this.decode = function(parent,xml) {
nickjillings@1324 2061 this.name = xml.getAttribute('name');
nickjillings@1324 2062 var titleNode = xml.getElementsByTagName('title');
nickjillings@1324 2063 if (titleNode.length == 1)
nickjillings@1324 2064 {
nickjillings@1324 2065 this.title = titleNode[0].textContent;
nickjillings@1324 2066 }
nickjillings@1324 2067 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
nickjillings@1324 2068 // Extract interfaceoption node schema
nickjillings@1370 2069 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
nickjillings@1348 2070 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 2071 for (var i=0; i<interfaceOptionNodes.length; i++)
nickjillings@1324 2072 {
nickjillings@1324 2073 var ioNode = interfaceOptionNodes[i];
nickjillings@1324 2074 var option = {};
nickjillings@1324 2075 for (var j=0; j<attributeMap.length; j++)
nickjillings@1324 2076 {
nickjillings@1324 2077 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
nickjillings@1324 2078 var projectAttr = ioNode.getAttribute(attributeName);
nickjillings@2165 2079 if(parent.processAttribute) {
nickjillings@2165 2080 parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
nickjillings@2165 2081 } else {
nickjillings@2165 2082 parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
nickjillings@2165 2083 }
nickjillings@1324 2084 switch(typeof projectAttr)
nickjillings@1324 2085 {
nickjillings@1324 2086 case "number":
nickjillings@1324 2087 case "boolean":
nickjillings@1324 2088 eval('option.'+attributeName+' = '+projectAttr);
nickjillings@1324 2089 break;
nickjillings@1324 2090 case "string":
nickjillings@1324 2091 eval('option.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 2092 break;
nickjillings@1324 2093 }
nickjillings@1324 2094 }
nickjillings@1324 2095 this.options.push(option);
nickjillings@1324 2096 }
nickjillings@1324 2097
nickjillings@1324 2098 // Now the scales nodes
nickjillings@1324 2099 var scaleParent = xml.getElementsByTagName('scales');
nickjillings@1324 2100 if (scaleParent.length == 1) {
nickjillings@1324 2101 scaleParent = scaleParent[0];
nickjillings@1324 2102 for (var i=0; i<scaleParent.children.length; i++) {
nickjillings@1324 2103 var child = scaleParent.children[i];
nickjillings@1324 2104 this.scales.push({
nickjillings@1324 2105 text: child.textContent,
nickjillings@1324 2106 position: Number(child.getAttribute('position'))
nickjillings@1324 2107 });
nickjillings@1406 2108 }
nickjillings@1580 2109 }
nickjillings@1580 2110 };
nickjillings@1324 2111
nickjillings@1372 2112 this.encode = function(doc) {
nickjillings@1372 2113 var node = doc.createElement("interface");
nickjillings@1372 2114 if (typeof name == "string")
nickjillings@1372 2115 node.setAttribute("name",this.name);
nickjillings@1372 2116 for (var option of this.options)
nickjillings@1372 2117 {
nickjillings@1372 2118 var child = doc.createElement("interfaceoption");
nickjillings@1372 2119 child.setAttribute("type",option.type);
nickjillings@1372 2120 child.setAttribute("name",option.name);
nickjillings@1372 2121 node.appendChild(child);
nickjillings@1372 2122 }
nickjillings@1372 2123 if (this.scales.length != 0) {
nickjillings@1372 2124 var scales = doc.createElement("scales");
nickjillings@1372 2125 for (var scale of this.scales)
nickjillings@1372 2126 {
nickjillings@1372 2127 var child = doc.createElement("scalelabel");
nickjillings@1372 2128 child.setAttribute("position",scale.position);
nickjillings@1372 2129 child.textContent = scale.text;
nickjillings@1372 2130 scales.appendChild(child);
nickjillings@1372 2131 }
nickjillings@1372 2132 node.appendChild(scales);
nickjillings@1372 2133 }
nickjillings@1372 2134 return node;
nickjillings@1324 2135 };
nickjillings@1580 2136 };
nickjillings@1580 2137
nickjillings@1370 2138 this.metricNode = function() {
nickjillings@1370 2139 this.enabled = [];
nickjillings@1370 2140 this.decode = function(parent, xml) {
nickjillings@1370 2141 var children = xml.getElementsByTagName('metricenable');
nickjillings@1370 2142 for (var i in children) {
nickjillings@1370 2143 if (isNaN(Number(i)) == true){break;}
nickjillings@1370 2144 this.enabled.push(children[i].textContent);
nickjillings@1370 2145 }
nickjillings@1370 2146 }
nickjillings@1372 2147 this.encode = function(doc) {
nickjillings@1372 2148 var node = doc.createElement('metric');
nickjillings@1370 2149 for (var i in this.enabled)
nickjillings@1370 2150 {
nickjillings@1370 2151 if (isNaN(Number(i)) == true){break;}
nickjillings@1372 2152 var child = doc.createElement('metricenable');
nickjillings@1370 2153 child.textContent = this.enabled[i];
nickjillings@1370 2154 node.appendChild(child);
nickjillings@1370 2155 }
nickjillings@1370 2156 return node;
nickjillings@1370 2157 }
nickjillings@1370 2158 }
nickjillings@1370 2159
nickjillings@1324 2160 this.page = function() {
nickjillings@1406 2161 this.presentedId = undefined;
nickjillings@1406 2162 this.id = undefined;
nickjillings@1406 2163 this.hostURL = undefined;
nickjillings@1406 2164 this.randomiseOrder = undefined;
nickjillings@1406 2165 this.loop = undefined;
nickjillings@1324 2166 this.showElementComments = undefined;
nickjillings@1406 2167 this.outsideReference = null;
nickjillings@1432 2168 this.loudness = null;
nickjillings@1295 2169 this.label = null;
nickjillings@1324 2170 this.preTest = null;
nickjillings@1324 2171 this.postTest = null;
nickjillings@1406 2172 this.interfaces = [];
nickjillings@1406 2173 this.commentBoxPrefix = "Comment on track";
nickjillings@1406 2174 this.audioElements = [];
nickjillings@1406 2175 this.commentQuestions = [];
nickjillings@1370 2176 this.schema = specification.schema.getAllElementsByName("page")[0];
nickjillings@2165 2177 this.parent = null;
nickjillings@1406 2178
nickjillings@1406 2179 this.decode = function(parent,xml)
nickjillings@1406 2180 {
nickjillings@2165 2181 this.parent = parent;
nickjillings@1348 2182 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 2183 for (var i=0; i<attributeMap.length; i++)
nickjillings@1432 2184 {
nickjillings@1324 2185 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
nickjillings@1324 2186 var projectAttr = xml.getAttribute(attributeName);
nickjillings@2165 2187 projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
nickjillings@1324 2188 switch(typeof projectAttr)
nickjillings@1395 2189 {
nickjillings@1324 2190 case "number":
nickjillings@1324 2191 case "boolean":
nickjillings@1324 2192 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 2193 break;
nickjillings@1324 2194 case "string":
nickjillings@1324 2195 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 2196 break;
nickjillings@1406 2197 }
nickjillings@1406 2198 }
nickjillings@1406 2199
nickjillings@1324 2200 // Get the Comment Box Prefix
nickjillings@1324 2201 var CBP = xml.getElementsByTagName('commentboxprefix');
nickjillings@1324 2202 if (CBP.length != 0) {
nickjillings@1324 2203 this.commentBoxPrefix = CBP[0].textContent;
nickjillings@1441 2204 }
nickjillings@1441 2205
nickjillings@1324 2206 // Now decode the interfaces
nickjillings@1324 2207 var interfaceNode = xml.getElementsByTagName('interface');
nickjillings@1324 2208 for (var i=0; i<interfaceNode.length; i++)
nickjillings@1324 2209 {
nickjillings@1324 2210 var node = new parent.interfaceNode();
nickjillings@1348 2211 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
nickjillings@1324 2212 this.interfaces.push(node);
nickjillings@1324 2213 }
nickjillings@1411 2214
nickjillings@1324 2215 // Now process the survey node options
nickjillings@1324 2216 var survey = xml.getElementsByTagName('survey');
nickjillings@1348 2217 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
nickjillings@1324 2218 for (var i in survey) {
nickjillings@1324 2219 if (isNaN(Number(i)) == true){break;}
nickjillings@1324 2220 var location = survey[i].getAttribute('location');
nickjillings@1324 2221 if (location == 'pre' || location == 'before')
nickjillings@1324 2222 {
nickjillings@1324 2223 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
nickjillings@1324 2224 else {
nickjillings@1324 2225 this.preTest = new parent.surveyNode();
nickjillings@1324 2226 this.preTest.decode(parent,survey[i],surveySchema);
nickjillings@1324 2227 }
nickjillings@1324 2228 } else if (location == 'post' || location == 'after') {
nickjillings@1324 2229 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
nickjillings@1324 2230 else {
nickjillings@1324 2231 this.postTest = new parent.surveyNode();
nickjillings@1324 2232 this.postTest.decode(parent,survey[i],surveySchema);
nickjillings@1324 2233 }
nickjillings@1324 2234 }
nickjillings@1324 2235 }
nickjillings@1324 2236
nickjillings@1324 2237 // Now process the audioelement tags
nickjillings@1324 2238 var audioElements = xml.getElementsByTagName('audioelement');
nickjillings@1324 2239 for (var i=0; i<audioElements.length; i++)
nickjillings@1324 2240 {
nickjillings@1324 2241 var node = new this.audioElementNode();
nickjillings@1370 2242 node.decode(this,audioElements[i]);
nickjillings@1324 2243 this.audioElements.push(node);
nickjillings@1324 2244 }
nickjillings@1324 2245
nickjillings@1324 2246 // Now decode the commentquestions
nickjillings@1324 2247 var commentQuestions = xml.getElementsByTagName('commentquestion');
nickjillings@1324 2248 for (var i=0; i<commentQuestions.length; i++)
nickjillings@1324 2249 {
nickjillings@1406 2250 var node = new this.commentQuestionNode();
nickjillings@1370 2251 node.decode(parent,commentQuestions[i]);
nickjillings@1406 2252 this.commentQuestions.push(node);
nickjillings@1580 2253 }
nickjillings@1580 2254 };
nickjillings@1580 2255
nickjillings@1406 2256 this.encode = function(root)
nickjillings@1406 2257 {
nickjillings@1372 2258 var AHNode = root.createElement("page");
nickjillings@1372 2259 // First decode the attributes
nickjillings@1372 2260 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1372 2261 for (var i=0; i<attributes.length; i++)
nickjillings@1372 2262 {
nickjillings@1372 2263 var name = attributes[i].getAttribute("name");
nickjillings@1372 2264 if (name == undefined) {
nickjillings@1372 2265 name = attributes[i].getAttribute("ref");
nickjillings@1372 2266 }
nickjillings@1372 2267 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
nickjillings@1372 2268 {
nickjillings@1372 2269 eval("AHNode.setAttribute('"+name+"',this."+name+")");
nickjillings@1372 2270 }
nickjillings@1372 2271 }
nickjillings@1432 2272 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
nickjillings@1372 2273 // <commentboxprefix>
nickjillings@1372 2274 var commentboxprefix = root.createElement("commentboxprefix");
nickjillings@1372 2275 commentboxprefix.textContent = this.commentBoxPrefix;
nickjillings@1372 2276 AHNode.appendChild(commentboxprefix);
nickjillings@1372 2277
nickjillings@1406 2278 for (var i=0; i<this.interfaces.length; i++)
nickjillings@1404 2279 {
nickjillings@1406 2280 AHNode.appendChild(this.interfaces[i].encode(root));
nickjillings@1406 2281 }
nickjillings@1558 2282
nickjillings@1406 2283 for (var i=0; i<this.audioElements.length; i++) {
nickjillings@1406 2284 AHNode.appendChild(this.audioElements[i].encode(root));
nickjillings@1406 2285 }
nickjillings@1406 2286 // Create <CommentQuestion>
nickjillings@1406 2287 for (var i=0; i<this.commentQuestions.length; i++)
nickjillings@1455 2288 {
nickjillings@1372 2289 AHNode.appendChild(this.commentQuestions[i].encode(root));
nickjillings@1406 2290 }
nickjillings@1406 2291
nickjillings@1372 2292 AHNode.appendChild(this.preTest.encode(root));
nickjillings@1372 2293 AHNode.appendChild(this.postTest.encode(root));
nickjillings@1406 2294 return AHNode;
nickjillings@1406 2295 };
nickjillings@1406 2296
nickjillings@1324 2297 this.commentQuestionNode = function() {
nickjillings@1324 2298 this.id = null;
nickjillings@2156 2299 this.name = undefined;
nickjillings@1324 2300 this.type = undefined;
nickjillings@1406 2301 this.options = [];
nickjillings@1324 2302 this.statement = undefined;
nickjillings@1370 2303 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
nickjillings@1370 2304 this.decode = function(parent,xml)
nickjillings@1406 2305 {
nickjillings@1324 2306 this.id = xml.id;
nickjillings@2156 2307 this.name = xml.getAttribute('name');
nickjillings@1324 2308 this.type = xml.getAttribute('type');
nickjillings@1324 2309 this.statement = xml.getElementsByTagName('statement')[0].textContent;
nickjillings@1324 2310 var optNodes = xml.getElementsByTagName('option');
nickjillings@1324 2311 for (var i=0; i<optNodes.length; i++)
nickjillings@1324 2312 {
nickjillings@1324 2313 var optNode = optNodes[i];
nickjillings@1324 2314 this.options.push({
nickjillings@1324 2315 name: optNode.getAttribute('name'),
nickjillings@1324 2316 text: optNode.textContent
nickjillings@1324 2317 });
nickjillings@1406 2318 }
nickjillings@1406 2319 };
nickjillings@1324 2320
nickjillings@1406 2321 this.encode = function(root)
nickjillings@1406 2322 {
nickjillings@1372 2323 var node = root.createElement("commentquestion");
nickjillings@1372 2324 node.id = this.id;
nickjillings@1372 2325 node.setAttribute("type",this.type);
nickjillings@2156 2326 if (this.name != undefined){node.setAttribute("name",this.name);}
nickjillings@1372 2327 var statement = root.createElement("statement");
nickjillings@1372 2328 statement.textContent = this.statement;
nickjillings@1372 2329 node.appendChild(statement);
nickjillings@1372 2330 for (var option of this.options)
nickjillings@1372 2331 {
nickjillings@1372 2332 var child = root.createElement("option");
nickjillings@1372 2333 child.setAttribute("name",option.name);
nickjillings@1372 2334 child.textContent = option.text;
nickjillings@1372 2335 node.appendChild(child);
nickjillings@1372 2336 }
nickjillings@1372 2337 return node;
nickjillings@1406 2338 };
nickjillings@1406 2339 };
nickjillings@1406 2340
nickjillings@1406 2341 this.audioElementNode = function() {
nickjillings@1406 2342 this.url = null;
nickjillings@1406 2343 this.id = null;
nickjillings@2156 2344 this.name = null;
nickjillings@1406 2345 this.parent = null;
nickjillings@1324 2346 this.type = null;
nickjillings@1310 2347 this.marker = null;
nickjillings@1406 2348 this.enforce = false;
nickjillings@2130 2349 this.gain = 0.0;
nickjillings@1370 2350 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
nickjillings@1324 2351 this.parent = null;
nickjillings@1406 2352 this.decode = function(parent,xml)
nickjillings@1406 2353 {
nickjillings@1406 2354 this.parent = parent;
nickjillings@1348 2355 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1324 2356 for (var i=0; i<attributeMap.length; i++)
nickjillings@1426 2357 {
nickjillings@1324 2358 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
nickjillings@1324 2359 var projectAttr = xml.getAttribute(attributeName);
nickjillings@2165 2360 projectAttr = parent.parent.processAttribute(projectAttr,attributeMap[i],parent.parent.schema);
nickjillings@1324 2361 switch(typeof projectAttr)
nickjillings@1406 2362 {
nickjillings@1324 2363 case "number":
nickjillings@1324 2364 case "boolean":
nickjillings@1324 2365 eval('this.'+attributeName+' = '+projectAttr);
nickjillings@1324 2366 break;
nickjillings@1324 2367 case "string":
nickjillings@1324 2368 eval('this.'+attributeName+' = "'+projectAttr+'"');
nickjillings@1324 2369 break;
nickjillings@1455 2370 }
nickjillings@1455 2371 }
nickjillings@1324 2372
nickjillings@1406 2373 };
nickjillings@1406 2374 this.encode = function(root)
nickjillings@1406 2375 {
nickjillings@1372 2376 var AENode = root.createElement("audioelement");
nickjillings@1372 2377 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
nickjillings@1372 2378 for (var i=0; i<attributes.length; i++)
nickjillings@1372 2379 {
nickjillings@1372 2380 var name = attributes[i].getAttribute("name");
nickjillings@1372 2381 if (name == undefined) {
nickjillings@1372 2382 name = attributes[i].getAttribute("ref");
nickjillings@1372 2383 }
nickjillings@1372 2384 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
nickjillings@1372 2385 {
nickjillings@1372 2386 eval("AENode.setAttribute('"+name+"',this."+name+")");
nickjillings@1372 2387 }
nickjillings@1372 2388 }
nickjillings@1406 2389 return AENode;
nickjillings@1406 2390 };
nickjillings@1580 2391 };
nickjillings@1580 2392 };
nickjillings@1580 2393 }
nickjillings@1406 2394
nickjillings@1582 2395 function Interface(specificationObject) {
nickjillings@1580 2396 // This handles the bindings between the interface and the audioEngineContext;
nickjillings@1582 2397 this.specification = specificationObject;
nickjillings@1582 2398 this.insertPoint = document.getElementById("topLevelBody");
nickjillings@1580 2399
nickjillings@1324 2400 this.newPage = function(audioHolderObject,store)
nickjillings@1407 2401 {
nickjillings@1369 2402 audioEngineContext.newTestPage(audioHolderObject,store);
nickjillings@2117 2403 interfaceContext.commentBoxes.deleteCommentBoxes();
nickjillings@1407 2404 interfaceContext.deleteCommentQuestions();
nickjillings@1324 2405 loadTest(audioHolderObject,store);
nickjillings@1407 2406 };
nickjillings@1407 2407
nickjillings@1582 2408 // Bounded by interface!!
nickjillings@1582 2409 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
nickjillings@1582 2410 // For example, APE returns the slider position normalised in a <value> tag.
nickjillings@1582 2411 this.interfaceObjects = [];
nickjillings@1582 2412 this.interfaceObject = function(){};
nickjillings@1582 2413
nickjillings@1525 2414 this.resizeWindow = function(event)
nickjillings@1525 2415 {
nickjillings@1421 2416 popup.resize(event);
nickjillings@1525 2417 for(var i=0; i<this.commentBoxes.length; i++)
nickjillings@1525 2418 {this.commentBoxes[i].resize();}
nickjillings@1525 2419 for(var i=0; i<this.commentQuestions.length; i++)
nickjillings@1525 2420 {this.commentQuestions[i].resize();}
nickjillings@1525 2421 try
nickjillings@1525 2422 {
nickjillings@1525 2423 resizeWindow(event);
nickjillings@1525 2424 }
nickjillings@1525 2425 catch(err)
nickjillings@1525 2426 {
nickjillings@1525 2427 console.log("Warning - Interface does not have Resize option");
nickjillings@1525 2428 console.log(err);
nickjillings@1525 2429 }
nickjillings@1525 2430 };
nickjillings@1525 2431
nickjillings@1465 2432 this.returnNavigator = function()
nickjillings@1465 2433 {
nickjillings@1362 2434 var node = storage.document.createElement("navigator");
nickjillings@1362 2435 var platform = storage.document.createElement("platform");
nickjillings@1465 2436 platform.textContent = navigator.platform;
nickjillings@1362 2437 var vendor = storage.document.createElement("vendor");
nickjillings@1465 2438 vendor.textContent = navigator.vendor;
nickjillings@1362 2439 var userAgent = storage.document.createElement("uagent");
nickjillings@1465 2440 userAgent.textContent = navigator.userAgent;
nickjillings@1362 2441 var screen = storage.document.createElement("window");
nickjillings@1362 2442 screen.setAttribute('innerWidth',window.innerWidth);
nickjillings@1362 2443 screen.setAttribute('innerHeight',window.innerHeight);
nickjillings@1465 2444 node.appendChild(platform);
nickjillings@1465 2445 node.appendChild(vendor);
nickjillings@1465 2446 node.appendChild(userAgent);
nickjillings@1362 2447 node.appendChild(screen);
nickjillings@1465 2448 return node;
nickjillings@1465 2449 };
nickjillings@2170 2450
nickjillings@2170 2451 this.returnDateNode = function()
nickjillings@2170 2452 {
nickjillings@2170 2453 // Create an XML Node for the Date and Time a test was conducted
nickjillings@2170 2454 // Structure is
nickjillings@2170 2455 // <datetime>
nickjillings@2170 2456 // <date year="##" month="##" day="##">DD/MM/YY</date>
nickjillings@2170 2457 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
nickjillings@2170 2458 // </datetime>
nickjillings@2170 2459 var dateTime = new Date();
nickjillings@2170 2460 var hold = storage.document.createElement("datetime");
nickjillings@2170 2461 var date = storage.document.createElement("date");
nickjillings@2170 2462 var time = storage.document.createElement("time");
nickjillings@2170 2463 date.setAttribute('year',dateTime.getFullYear());
nickjillings@2170 2464 date.setAttribute('month',dateTime.getMonth()+1);
nickjillings@2170 2465 date.setAttribute('day',dateTime.getDate());
nickjillings@2170 2466 time.setAttribute('hour',dateTime.getHours());
nickjillings@2175 2467 time.setAttribute('minute',dateTime.getMinutes());
nickjillings@2170 2468 time.setAttribute('secs',dateTime.getSeconds());
nickjillings@2170 2469
nickjillings@2170 2470 hold.appendChild(date);
nickjillings@2170 2471 hold.appendChild(time);
nickjillings@2170 2472 return hold;
nickjillings@2170 2473
nickjillings@2170 2474 }
nickjillings@1465 2475
nickjillings@2117 2476 this.commentBoxes = new function() {
nickjillings@2117 2477 this.boxes = [];
nickjillings@2117 2478 this.injectPoint = null;
nickjillings@2117 2479 this.elementCommentBox = function(audioObject) {
nickjillings@2117 2480 var element = audioObject.specification;
nickjillings@2117 2481 this.audioObject = audioObject;
nickjillings@2117 2482 this.id = audioObject.id;
nickjillings@2117 2483 var audioHolderObject = audioObject.specification.parent;
nickjillings@2117 2484 // Create document objects to hold the comment boxes
nickjillings@2117 2485 this.trackComment = document.createElement('div');
nickjillings@2117 2486 this.trackComment.className = 'comment-div';
nickjillings@2117 2487 this.trackComment.id = 'comment-div-'+audioObject.id;
nickjillings@2117 2488 // Create a string next to each comment asking for a comment
nickjillings@2117 2489 this.trackString = document.createElement('span');
nickjillings@2117 2490 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
nickjillings@2117 2491 // Create the HTML5 comment box 'textarea'
nickjillings@2117 2492 this.trackCommentBox = document.createElement('textarea');
nickjillings@2117 2493 this.trackCommentBox.rows = '4';
nickjillings@2117 2494 this.trackCommentBox.cols = '100';
nickjillings@2117 2495 this.trackCommentBox.name = 'trackComment'+audioObject.id;
nickjillings@2117 2496 this.trackCommentBox.className = 'trackComment';
nickjillings@2117 2497 var br = document.createElement('br');
nickjillings@2117 2498 // Add to the holder.
nickjillings@2117 2499 this.trackComment.appendChild(this.trackString);
nickjillings@2117 2500 this.trackComment.appendChild(br);
nickjillings@2117 2501 this.trackComment.appendChild(this.trackCommentBox);
nickjillings@2117 2502
nickjillings@2117 2503 this.exportXMLDOM = function() {
nickjillings@2117 2504 var root = document.createElement('comment');
nickjillings@2117 2505 var question = document.createElement('question');
nickjillings@2117 2506 question.textContent = this.trackString.textContent;
nickjillings@2117 2507 var response = document.createElement('response');
nickjillings@2117 2508 response.textContent = this.trackCommentBox.value;
nickjillings@2117 2509 console.log("Comment frag-"+this.id+": "+response.textContent);
nickjillings@2117 2510 root.appendChild(question);
nickjillings@2117 2511 root.appendChild(response);
nickjillings@2117 2512 return root;
nickjillings@2117 2513 };
nickjillings@2117 2514 this.resize = function()
nickjillings@2117 2515 {
nickjillings@2117 2516 var boxwidth = (window.innerWidth-100)/2;
nickjillings@2117 2517 if (boxwidth >= 600)
nickjillings@2117 2518 {
nickjillings@2117 2519 boxwidth = 600;
nickjillings@2117 2520 }
nickjillings@2117 2521 else if (boxwidth < 400)
nickjillings@2117 2522 {
nickjillings@2117 2523 boxwidth = 400;
nickjillings@2117 2524 }
nickjillings@2117 2525 this.trackComment.style.width = boxwidth+"px";
nickjillings@2117 2526 this.trackCommentBox.style.width = boxwidth-6+"px";
nickjillings@2117 2527 };
nickjillings@2117 2528 this.resize();
nickjillings@2117 2529 };
nickjillings@2117 2530 this.createCommentBox = function(audioObject) {
nickjillings@2117 2531 var node = new this.elementCommentBox(audioObject);
nickjillings@2117 2532 this.boxes.push(node);
nickjillings@2117 2533 audioObject.commentDOM = node;
nickjillings@2117 2534 return node;
nickjillings@2117 2535 };
nickjillings@2117 2536 this.sortCommentBoxes = function() {
nickjillings@2117 2537 this.boxes.sort(function(a,b){return a.id - b.id;});
nickjillings@2117 2538 };
nickjillings@2117 2539
nickjillings@2117 2540 this.showCommentBoxes = function(inject, sort) {
nickjillings@2117 2541 this.injectPoint = inject;
nickjillings@2117 2542 if (sort) {this.sortCommentBoxes();}
nickjillings@2117 2543 for (var box of this.boxes) {
nickjillings@2117 2544 inject.appendChild(box.trackComment);
nickjillings@2117 2545 }
nickjillings@2117 2546 };
nickjillings@2117 2547
nickjillings@2117 2548 this.deleteCommentBoxes = function() {
nickjillings@2117 2549 if (this.injectPoint != null) {
nickjillings@2117 2550 for (var box of this.boxes) {
nickjillings@2117 2551 this.injectPoint.removeChild(box.trackComment);
nickjillings@2117 2552 }
nickjillings@2117 2553 this.injectPoint = null;
nickjillings@2117 2554 }
nickjillings@2117 2555 this.boxes = [];
nickjillings@2117 2556 };
nickjillings@2117 2557 }
nickjillings@1582 2558
nickjillings@2032 2559 this.commentQuestions = [];
nickjillings@2032 2560
nickjillings@2032 2561 this.commentBox = function(commentQuestion) {
nickjillings@2032 2562 this.specification = commentQuestion;
nickjillings@2032 2563 // Create document objects to hold the comment boxes
nickjillings@2032 2564 this.holder = document.createElement('div');
nickjillings@2032 2565 this.holder.className = 'comment-div';
nickjillings@2032 2566 // Create a string next to each comment asking for a comment
nickjillings@2032 2567 this.string = document.createElement('span');
nickjillings@1324 2568 this.string.innerHTML = commentQuestion.statement;
nickjillings@2032 2569 // Create the HTML5 comment box 'textarea'
nickjillings@2032 2570 this.textArea = document.createElement('textarea');
nickjillings@2032 2571 this.textArea.rows = '4';
nickjillings@2032 2572 this.textArea.cols = '100';
nickjillings@2032 2573 this.textArea.className = 'trackComment';
nickjillings@2032 2574 var br = document.createElement('br');
nickjillings@2032 2575 // Add to the holder.
nickjillings@2032 2576 this.holder.appendChild(this.string);
nickjillings@2032 2577 this.holder.appendChild(br);
nickjillings@2032 2578 this.holder.appendChild(this.textArea);
nickjillings@2032 2579
nickjillings@2097 2580 this.exportXMLDOM = function(storePoint) {
nickjillings@2097 2581 var root = storePoint.parent.document.createElement('comment');
nickjillings@2032 2582 root.id = this.specification.id;
nickjillings@2032 2583 root.setAttribute('type',this.specification.type);
b@2059 2584 console.log("Question: "+this.string.textContent);
b@2059 2585 console.log("Response: "+root.textContent);
nickjillings@2097 2586 var question = storePoint.parent.document.createElement('question');
nickjillings@2097 2587 question.textContent = this.string.textContent;
nickjillings@2097 2588 var response = storePoint.parent.document.createElement('response');
nickjillings@2097 2589 response.textContent = this.textArea.value;
nickjillings@2097 2590 root.appendChild(question);
nickjillings@2097 2591 root.appendChild(response);
nickjillings@2097 2592 storePoint.XMLDOM.appendChild(root);
nickjillings@2032 2593 return root;
nickjillings@2032 2594 };
nickjillings@1525 2595 this.resize = function()
nickjillings@1525 2596 {
nickjillings@1525 2597 var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525 2598 if (boxwidth >= 600)
nickjillings@1525 2599 {
nickjillings@1525 2600 boxwidth = 600;
nickjillings@1525 2601 }
nickjillings@1525 2602 else if (boxwidth < 400)
nickjillings@1525 2603 {
nickjillings@1525 2604 boxwidth = 400;
nickjillings@1525 2605 }
nickjillings@1525 2606 this.holder.style.width = boxwidth+"px";
nickjillings@1525 2607 this.textArea.style.width = boxwidth-6+"px";
nickjillings@1525 2608 };
nickjillings@1525 2609 this.resize();
nickjillings@2032 2610 };
nickjillings@2032 2611
nickjillings@2032 2612 this.radioBox = function(commentQuestion) {
nickjillings@2032 2613 this.specification = commentQuestion;
nickjillings@2032 2614 // Create document objects to hold the comment boxes
nickjillings@2032 2615 this.holder = document.createElement('div');
nickjillings@2032 2616 this.holder.className = 'comment-div';
nickjillings@2032 2617 // Create a string next to each comment asking for a comment
nickjillings@2032 2618 this.string = document.createElement('span');
nickjillings@2032 2619 this.string.innerHTML = commentQuestion.statement;
nickjillings@2032 2620 var br = document.createElement('br');
nickjillings@2032 2621 // Add to the holder.
nickjillings@2032 2622 this.holder.appendChild(this.string);
nickjillings@2032 2623 this.holder.appendChild(br);
nickjillings@2032 2624 this.options = [];
nickjillings@2032 2625 this.inputs = document.createElement('div');
nickjillings@2032 2626 this.span = document.createElement('div');
nickjillings@2032 2627 this.inputs.align = 'center';
nickjillings@2032 2628 this.inputs.style.marginLeft = '12px';
nickjillings@2032 2629 this.span.style.marginLeft = '12px';
nickjillings@2032 2630 this.span.align = 'center';
nickjillings@2032 2631 this.span.style.marginTop = '15px';
nickjillings@2032 2632
nickjillings@2032 2633 var optCount = commentQuestion.options.length;
nickjillings@1324 2634 for (var optNode of commentQuestion.options)
nickjillings@2032 2635 {
nickjillings@2032 2636 var div = document.createElement('div');
nickjillings@1524 2637 div.style.width = '80px';
nickjillings@2032 2638 div.style.float = 'left';
nickjillings@2032 2639 var input = document.createElement('input');
nickjillings@2032 2640 input.type = 'radio';
nickjillings@2032 2641 input.name = commentQuestion.id;
nickjillings@1324 2642 input.setAttribute('setvalue',optNode.name);
nickjillings@2032 2643 input.className = 'comment-radio';
nickjillings@2032 2644 div.appendChild(input);
nickjillings@2032 2645 this.inputs.appendChild(div);
nickjillings@2032 2646
nickjillings@2032 2647
nickjillings@2032 2648 div = document.createElement('div');
nickjillings@1524 2649 div.style.width = '80px';
nickjillings@2032 2650 div.style.float = 'left';
nickjillings@2032 2651 div.align = 'center';
nickjillings@2032 2652 var span = document.createElement('span');
nickjillings@1324 2653 span.textContent = optNode.text;
nickjillings@2032 2654 span.className = 'comment-radio-span';
nickjillings@2032 2655 div.appendChild(span);
nickjillings@2032 2656 this.span.appendChild(div);
nickjillings@2032 2657 this.options.push(input);
nickjillings@2032 2658 }
nickjillings@2032 2659 this.holder.appendChild(this.span);
nickjillings@2032 2660 this.holder.appendChild(this.inputs);
nickjillings@2032 2661
nickjillings@2097 2662 this.exportXMLDOM = function(storePoint) {
nickjillings@2097 2663 var root = storePoint.parent.document.createElement('comment');
nickjillings@2032 2664 root.id = this.specification.id;
nickjillings@2032 2665 root.setAttribute('type',this.specification.type);
nickjillings@2032 2666 var question = document.createElement('question');
nickjillings@2032 2667 question.textContent = this.string.textContent;
nickjillings@2032 2668 var response = document.createElement('response');
nickjillings@2032 2669 var i=0;
nickjillings@2032 2670 while(this.options[i].checked == false) {
nickjillings@2032 2671 i++;
nickjillings@2032 2672 if (i >= this.options.length) {
nickjillings@2032 2673 break;
nickjillings@2032 2674 }
nickjillings@2032 2675 }
nickjillings@2032 2676 if (i >= this.options.length) {
nickjillings@2032 2677 response.textContent = 'null';
nickjillings@2032 2678 } else {
nickjillings@2032 2679 response.textContent = this.options[i].getAttribute('setvalue');
nickjillings@2032 2680 response.setAttribute('number',i);
nickjillings@2032 2681 }
nickjillings@1572 2682 console.log('Comment: '+question.textContent);
nickjillings@1572 2683 console.log('Response: '+response.textContent);
nickjillings@2032 2684 root.appendChild(question);
nickjillings@2032 2685 root.appendChild(response);
nickjillings@2097 2686 storePoint.XMLDOM.appendChild(root);
nickjillings@2032 2687 return root;
nickjillings@2032 2688 };
nickjillings@1525 2689 this.resize = function()
nickjillings@1525 2690 {
nickjillings@1525 2691 var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525 2692 if (boxwidth >= 600)
nickjillings@1525 2693 {
nickjillings@1525 2694 boxwidth = 600;
nickjillings@1525 2695 }
nickjillings@1525 2696 else if (boxwidth < 400)
nickjillings@1525 2697 {
nickjillings@1525 2698 boxwidth = 400;
nickjillings@1525 2699 }
nickjillings@1525 2700 this.holder.style.width = boxwidth+"px";
nickjillings@1525 2701 var text = this.holder.children[2];
nickjillings@1525 2702 var options = this.holder.children[3];
nickjillings@1525 2703 var optCount = options.children.length;
nickjillings@1525 2704 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
nickjillings@1525 2705 var options = options.firstChild;
nickjillings@1525 2706 var text = text.firstChild;
nickjillings@1525 2707 options.style.marginRight = spanMargin;
nickjillings@1525 2708 options.style.marginLeft = spanMargin;
nickjillings@1525 2709 text.style.marginRight = spanMargin;
nickjillings@1525 2710 text.style.marginLeft = spanMargin;
nickjillings@1525 2711 while(options.nextSibling != undefined)
nickjillings@1525 2712 {
nickjillings@1525 2713 options = options.nextSibling;
nickjillings@1525 2714 text = text.nextSibling;
nickjillings@1525 2715 options.style.marginRight = spanMargin;
nickjillings@1525 2716 options.style.marginLeft = spanMargin;
nickjillings@1525 2717 text.style.marginRight = spanMargin;
nickjillings@1525 2718 text.style.marginLeft = spanMargin;
nickjillings@1525 2719 }
nickjillings@1525 2720 };
nickjillings@1525 2721 this.resize();
nickjillings@2032 2722 };
nickjillings@2032 2723
nickjillings@1572 2724 this.checkboxBox = function(commentQuestion) {
nickjillings@1572 2725 this.specification = commentQuestion;
nickjillings@1572 2726 // Create document objects to hold the comment boxes
nickjillings@1572 2727 this.holder = document.createElement('div');
nickjillings@1572 2728 this.holder.className = 'comment-div';
nickjillings@1572 2729 // Create a string next to each comment asking for a comment
nickjillings@1572 2730 this.string = document.createElement('span');
nickjillings@1572 2731 this.string.innerHTML = commentQuestion.statement;
nickjillings@1572 2732 var br = document.createElement('br');
nickjillings@1572 2733 // Add to the holder.
nickjillings@1572 2734 this.holder.appendChild(this.string);
nickjillings@1572 2735 this.holder.appendChild(br);
nickjillings@1572 2736 this.options = [];
nickjillings@1572 2737 this.inputs = document.createElement('div');
nickjillings@1572 2738 this.span = document.createElement('div');
nickjillings@1572 2739 this.inputs.align = 'center';
nickjillings@1572 2740 this.inputs.style.marginLeft = '12px';
nickjillings@1572 2741 this.span.style.marginLeft = '12px';
nickjillings@1572 2742 this.span.align = 'center';
nickjillings@1572 2743 this.span.style.marginTop = '15px';
nickjillings@1572 2744
nickjillings@1572 2745 var optCount = commentQuestion.options.length;
nickjillings@1572 2746 for (var i=0; i<optCount; i++)
nickjillings@1572 2747 {
nickjillings@1572 2748 var div = document.createElement('div');
nickjillings@1524 2749 div.style.width = '80px';
nickjillings@1572 2750 div.style.float = 'left';
nickjillings@1572 2751 var input = document.createElement('input');
nickjillings@1572 2752 input.type = 'checkbox';
nickjillings@1572 2753 input.name = commentQuestion.id;
nickjillings@1572 2754 input.setAttribute('setvalue',commentQuestion.options[i].name);
nickjillings@1572 2755 input.className = 'comment-radio';
nickjillings@1572 2756 div.appendChild(input);
nickjillings@1572 2757 this.inputs.appendChild(div);
nickjillings@1572 2758
nickjillings@1572 2759
nickjillings@1572 2760 div = document.createElement('div');
nickjillings@1524 2761 div.style.width = '80px';
nickjillings@1572 2762 div.style.float = 'left';
nickjillings@1572 2763 div.align = 'center';
nickjillings@1572 2764 var span = document.createElement('span');
nickjillings@1572 2765 span.textContent = commentQuestion.options[i].text;
nickjillings@1572 2766 span.className = 'comment-radio-span';
nickjillings@1572 2767 div.appendChild(span);
nickjillings@1572 2768 this.span.appendChild(div);
nickjillings@1572 2769 this.options.push(input);
nickjillings@1572 2770 }
nickjillings@1572 2771 this.holder.appendChild(this.span);
nickjillings@1572 2772 this.holder.appendChild(this.inputs);
nickjillings@1572 2773
nickjillings@2097 2774 this.exportXMLDOM = function(storePoint) {
nickjillings@2097 2775 var root = storePoint.parent.document.createElement('comment');
nickjillings@1572 2776 root.id = this.specification.id;
nickjillings@1572 2777 root.setAttribute('type',this.specification.type);
nickjillings@1572 2778 var question = document.createElement('question');
nickjillings@1572 2779 question.textContent = this.string.textContent;
nickjillings@1572 2780 root.appendChild(question);
nickjillings@1572 2781 console.log('Comment: '+question.textContent);
nickjillings@1572 2782 for (var i=0; i<this.options.length; i++) {
nickjillings@1572 2783 var response = document.createElement('response');
nickjillings@1572 2784 response.textContent = this.options[i].checked;
nickjillings@1572 2785 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
nickjillings@1572 2786 root.appendChild(response);
nickjillings@1572 2787 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
nickjillings@1572 2788 }
nickjillings@2097 2789 storePoint.XMLDOM.appendChild(root);
nickjillings@1572 2790 return root;
nickjillings@1572 2791 };
nickjillings@1525 2792 this.resize = function()
nickjillings@1525 2793 {
nickjillings@1525 2794 var boxwidth = (window.innerWidth-100)/2;
nickjillings@1525 2795 if (boxwidth >= 600)
nickjillings@1525 2796 {
nickjillings@1525 2797 boxwidth = 600;
nickjillings@1525 2798 }
nickjillings@1525 2799 else if (boxwidth < 400)
nickjillings@1525 2800 {
nickjillings@1525 2801 boxwidth = 400;
nickjillings@1525 2802 }
nickjillings@1525 2803 this.holder.style.width = boxwidth+"px";
nickjillings@1525 2804 var text = this.holder.children[2];
nickjillings@1525 2805 var options = this.holder.children[3];
nickjillings@1525 2806 var optCount = options.children.length;
nickjillings@1525 2807 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
nickjillings@1525 2808 var options = options.firstChild;
nickjillings@1525 2809 var text = text.firstChild;
nickjillings@1525 2810 options.style.marginRight = spanMargin;
nickjillings@1525 2811 options.style.marginLeft = spanMargin;
nickjillings@1525 2812 text.style.marginRight = spanMargin;
nickjillings@1525 2813 text.style.marginLeft = spanMargin;
nickjillings@1525 2814 while(options.nextSibling != undefined)
nickjillings@1525 2815 {
nickjillings@1525 2816 options = options.nextSibling;
nickjillings@1525 2817 text = text.nextSibling;
nickjillings@1525 2818 options.style.marginRight = spanMargin;
nickjillings@1525 2819 options.style.marginLeft = spanMargin;
nickjillings@1525 2820 text.style.marginRight = spanMargin;
nickjillings@1525 2821 text.style.marginLeft = spanMargin;
nickjillings@1525 2822 }
nickjillings@1525 2823 };
nickjillings@1525 2824 this.resize();
nickjillings@1572 2825 };
nickjillings@1570 2826
nickjillings@2032 2827 this.createCommentQuestion = function(element) {
nickjillings@2032 2828 var node;
nickjillings@1324 2829 if (element.type == 'question') {
nickjillings@2032 2830 node = new this.commentBox(element);
nickjillings@2032 2831 } else if (element.type == 'radio') {
nickjillings@2032 2832 node = new this.radioBox(element);
nickjillings@1572 2833 } else if (element.type == 'checkbox') {
nickjillings@1572 2834 node = new this.checkboxBox(element);
nickjillings@2032 2835 }
nickjillings@2032 2836 this.commentQuestions.push(node);
nickjillings@2032 2837 return node;
nickjillings@2032 2838 };
nickjillings@1564 2839
nickjillings@2051 2840 this.deleteCommentQuestions = function()
nickjillings@2051 2841 {
nickjillings@2051 2842 this.commentQuestions = [];
nickjillings@2051 2843 };
nickjillings@2051 2844
nickjillings@1564 2845 this.playhead = new function()
nickjillings@1564 2846 {
nickjillings@1564 2847 this.object = document.createElement('div');
nickjillings@1564 2848 this.object.className = 'playhead';
nickjillings@1564 2849 this.object.align = 'left';
nickjillings@1564 2850 var curTime = document.createElement('div');
nickjillings@1564 2851 curTime.style.width = '50px';
nickjillings@1564 2852 this.curTimeSpan = document.createElement('span');
nickjillings@1564 2853 this.curTimeSpan.textContent = '00:00';
nickjillings@1564 2854 curTime.appendChild(this.curTimeSpan);
nickjillings@1564 2855 this.object.appendChild(curTime);
nickjillings@1564 2856 this.scrubberTrack = document.createElement('div');
nickjillings@1564 2857 this.scrubberTrack.className = 'playhead-scrub-track';
nickjillings@1564 2858
nickjillings@1564 2859 this.scrubberHead = document.createElement('div');
nickjillings@1564 2860 this.scrubberHead.id = 'playhead-scrubber';
nickjillings@1564 2861 this.scrubberTrack.appendChild(this.scrubberHead);
nickjillings@1564 2862 this.object.appendChild(this.scrubberTrack);
nickjillings@1564 2863
nickjillings@1564 2864 this.timePerPixel = 0;
nickjillings@1564 2865 this.maxTime = 0;
nickjillings@1564 2866
nickjillings@1567 2867 this.playbackObject;
nickjillings@1567 2868
nickjillings@1567 2869 this.setTimePerPixel = function(audioObject) {
nickjillings@1564 2870 //maxTime must be in seconds
nickjillings@1567 2871 this.playbackObject = audioObject;
nickjillings@1410 2872 this.maxTime = audioObject.buffer.buffer.duration;
nickjillings@1564 2873 var width = 490; //500 - 10, 5 each side of the tracker head
nickjillings@1567 2874 this.timePerPixel = this.maxTime/490;
nickjillings@1567 2875 if (this.maxTime < 60) {
nickjillings@1564 2876 this.curTimeSpan.textContent = '0.00';
nickjillings@1564 2877 } else {
nickjillings@1564 2878 this.curTimeSpan.textContent = '00:00';
nickjillings@1564 2879 }
nickjillings@1564 2880 };
nickjillings@1564 2881
nickjillings@1567 2882 this.update = function() {
nickjillings@1564 2883 // Update the playhead position, startPlay must be called
nickjillings@1564 2884 if (this.timePerPixel > 0) {
nickjillings@1567 2885 var time = this.playbackObject.getCurrentPosition();
nickjillings@1367 2886 if (time > 0 && time < this.maxTime) {
nickjillings@1530 2887 var width = 490;
nickjillings@1530 2888 var pix = Math.floor(time/this.timePerPixel);
nickjillings@1530 2889 this.scrubberHead.style.left = pix+'px';
nickjillings@1530 2890 if (this.maxTime > 60.0) {
nickjillings@1530 2891 var secs = time%60;
nickjillings@1530 2892 var mins = Math.floor((time-secs)/60);
nickjillings@1530 2893 secs = secs.toString();
nickjillings@1530 2894 secs = secs.substr(0,2);
nickjillings@1530 2895 mins = mins.toString();
nickjillings@1530 2896 this.curTimeSpan.textContent = mins+':'+secs;
nickjillings@1530 2897 } else {
nickjillings@1530 2898 time = time.toString();
nickjillings@1530 2899 this.curTimeSpan.textContent = time.substr(0,4);
nickjillings@1530 2900 }
nickjillings@1564 2901 } else {
nickjillings@1530 2902 this.scrubberHead.style.left = '0px';
nickjillings@1530 2903 if (this.maxTime < 60) {
nickjillings@1530 2904 this.curTimeSpan.textContent = '0.00';
nickjillings@1530 2905 } else {
nickjillings@1530 2906 this.curTimeSpan.textContent = '00:00';
nickjillings@1530 2907 }
nickjillings@1564 2908 }
nickjillings@1564 2909 }
nickjillings@1564 2910 };
nickjillings@1567 2911
nickjillings@1567 2912 this.interval = undefined;
nickjillings@1567 2913
nickjillings@1567 2914 this.start = function() {
nickjillings@1567 2915 if (this.playbackObject != undefined && this.interval == undefined) {
nickjillings@1530 2916 if (this.maxTime < 60) {
nickjillings@1530 2917 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nickjillings@1530 2918 } else {
nickjillings@1530 2919 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nickjillings@1530 2920 }
nickjillings@1567 2921 }
nickjillings@1567 2922 };
nickjillings@1567 2923 this.stop = function() {
nickjillings@1567 2924 clearInterval(this.interval);
nickjillings@1567 2925 this.interval = undefined;
nickjillings@2101 2926 this.scrubberHead.style.left = '0px';
nickjillings@1530 2927 if (this.maxTime < 60) {
nickjillings@1530 2928 this.curTimeSpan.textContent = '0.00';
nickjillings@1530 2929 } else {
nickjillings@1530 2930 this.curTimeSpan.textContent = '00:00';
nickjillings@1530 2931 }
nickjillings@1567 2932 };
nickjillings@1564 2933 };
nickjillings@1354 2934
nickjillings@1354 2935 this.volume = new function()
nickjillings@1354 2936 {
nickjillings@1354 2937 // An in-built volume module which can be viewed on page
nickjillings@1354 2938 // Includes trackers on page-by-page data
nickjillings@1354 2939 // Volume does NOT reset to 0dB on each page load
nickjillings@1354 2940 this.valueLin = 1.0;
nickjillings@1354 2941 this.valueDB = 0.0;
nickjillings@1354 2942 this.object = document.createElement('div');
nickjillings@1354 2943 this.object.id = 'master-volume-holder';
nickjillings@1354 2944 this.slider = document.createElement('input');
nickjillings@1354 2945 this.slider.id = 'master-volume-control';
nickjillings@1354 2946 this.slider.type = 'range';
nickjillings@1354 2947 this.valueText = document.createElement('span');
nickjillings@1354 2948 this.valueText.id = 'master-volume-feedback';
nickjillings@1354 2949 this.valueText.textContent = '0dB';
nickjillings@1354 2950
nickjillings@1354 2951 this.slider.min = -60;
nickjillings@1354 2952 this.slider.max = 12;
nickjillings@1354 2953 this.slider.value = 0;
nickjillings@1354 2954 this.slider.step = 1;
nickjillings@1354 2955 this.slider.onmousemove = function(event)
nickjillings@1354 2956 {
nickjillings@1354 2957 interfaceContext.volume.valueDB = event.currentTarget.value;
nickjillings@1354 2958 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
nickjillings@1354 2959 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
nickjillings@1354 2960 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
nickjillings@1354 2961 }
nickjillings@1354 2962 this.slider.onmouseup = function(event)
nickjillings@1354 2963 {
nickjillings@2100 2964 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
nickjillings@1354 2965 if (storePoint.length == 0)
nickjillings@1354 2966 {
nickjillings@1354 2967 storePoint = storage.document.createElement('metricresult');
nickjillings@1354 2968 storePoint.setAttribute('name','volumeTracker');
nickjillings@2100 2969 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
nickjillings@1354 2970 }
nickjillings@1354 2971 else {
nickjillings@1354 2972 storePoint = storePoint[0];
nickjillings@1354 2973 }
nickjillings@1354 2974 var node = storage.document.createElement('movement');
nickjillings@1354 2975 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
nickjillings@1354 2976 node.setAttribute('volume',interfaceContext.volume.valueDB);
nickjillings@1354 2977 node.setAttribute('format','dBFS');
nickjillings@1354 2978 storePoint.appendChild(node);
nickjillings@1354 2979 }
nickjillings@1354 2980
nickjillings@1355 2981 var title = document.createElement('div');
nickjillings@1355 2982 title.innerHTML = '<span>Master Volume Control</span>';
nickjillings@1355 2983 title.style.fontSize = '0.75em';
nickjillings@1355 2984 title.style.width = "100%";
nickjillings@1355 2985 title.align = 'center';
nickjillings@1355 2986 this.object.appendChild(title);
nickjillings@1355 2987
nickjillings@1354 2988 this.object.appendChild(this.slider);
nickjillings@1354 2989 this.object.appendChild(this.valueText);
nickjillings@1354 2990 }
nickjillings@2049 2991 // Global Checkers
nickjillings@2049 2992 // These functions will help enforce the checkers
nickjillings@2049 2993 this.checkHiddenAnchor = function()
nickjillings@2049 2994 {
nickjillings@1324 2995 for (var ao of audioEngineContext.audioObjects)
nickjillings@2049 2996 {
nickjillings@1324 2997 if (ao.specification.type == "anchor")
nickjillings@2049 2998 {
nickjillings@1325 2999 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
nickjillings@1324 3000 // Anchor is not set below
nickjillings@1324 3001 console.log('Anchor node not below marker value');
nickjillings@1324 3002 alert('Please keep listening');
nickjillings@1367 3003 this.storeErrorNode('Anchor node not below marker value');
nickjillings@1324 3004 return false;
nickjillings@1324 3005 }
nickjillings@2049 3006 }
nickjillings@2049 3007 }
nickjillings@2049 3008 return true;
nickjillings@2049 3009 };
nickjillings@2049 3010
nickjillings@2049 3011 this.checkHiddenReference = function()
nickjillings@2049 3012 {
nickjillings@1324 3013 for (var ao of audioEngineContext.audioObjects)
nickjillings@2049 3014 {
nickjillings@1324 3015 if (ao.specification.type == "reference")
nickjillings@2049 3016 {
nickjillings@1325 3017 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
nickjillings@1324 3018 // Anchor is not set below
nickjillings@1367 3019 console.log('Reference node not above marker value');
nickjillings@1367 3020 this.storeErrorNode('Reference node not above marker value');
nickjillings@1324 3021 alert('Please keep listening');
nickjillings@1324 3022 return false;
nickjillings@1324 3023 }
nickjillings@2049 3024 }
nickjillings@2049 3025 }
nickjillings@2049 3026 return true;
nickjillings@2049 3027 };
nickjillings@1474 3028
nickjillings@1474 3029 this.checkFragmentsFullyPlayed = function ()
nickjillings@1474 3030 {
nickjillings@1474 3031 // Checks the entire file has been played back
nickjillings@1474 3032 // NOTE ! This will return true IF playback is Looped!!!
nickjillings@1474 3033 if (audioEngineContext.loopPlayback)
nickjillings@1474 3034 {
nickjillings@1474 3035 console.log("WARNING - Looped source: Cannot check fragments are fully played");
nickjillings@1474 3036 return true;
nickjillings@1474 3037 }
nickjillings@1474 3038 var check_pass = true;
nickjillings@1474 3039 var error_obj = [];
nickjillings@1474 3040 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
nickjillings@1474 3041 {
nickjillings@1474 3042 var object = audioEngineContext.audioObjects[i];
nickjillings@1393 3043 var time = object.buffer.buffer.duration;
nickjillings@1474 3044 var metric = object.metric;
nickjillings@1474 3045 var passed = false;
nickjillings@1474 3046 for (var j=0; j<metric.listenTracker.length; j++)
nickjillings@1474 3047 {
nickjillings@1474 3048 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
nickjillings@1474 3049 var start_time = Number(bt[0].getAttribute('start'));
nickjillings@1474 3050 var stop_time = Number(bt[0].getAttribute('stop'));
nickjillings@1474 3051 var delta = stop_time - start_time;
nickjillings@1474 3052 if (delta >= time)
nickjillings@1474 3053 {
nickjillings@1474 3054 passed = true;
nickjillings@1474 3055 break;
nickjillings@1474 3056 }
nickjillings@1474 3057 }
nickjillings@1474 3058 if (passed == false)
nickjillings@1474 3059 {
nickjillings@1474 3060 check_pass = false;
nickjillings@1290 3061 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
nickjillings@1290 3062 error_obj.push(object.interfaceDOM.getPresentedId());
nickjillings@1474 3063 }
nickjillings@1474 3064 }
nickjillings@1474 3065 if (check_pass == false)
nickjillings@1474 3066 {
nickjillings@1393 3067 var str_start = "You have not completely listened to fragments ";
nickjillings@1474 3068 for (var i=0; i<error_obj.length; i++)
nickjillings@1474 3069 {
nickjillings@1474 3070 str_start += error_obj[i];
nickjillings@1474 3071 if (i != error_obj.length-1)
nickjillings@1474 3072 {
nickjillings@1474 3073 str_start += ', ';
nickjillings@1474 3074 }
nickjillings@1474 3075 }
nickjillings@1474 3076 str_start += ". Please keep listening";
nickjillings@1474 3077 console.log("[ALERT]: "+str_start);
nickjillings@1367 3078 this.storeErrorNode("[ALERT]: "+str_start);
nickjillings@1474 3079 alert(str_start);
nickjillings@1474 3080 }
nickjillings@1474 3081 };
nickjillings@1399 3082 this.checkAllMoved = function()
nickjillings@1399 3083 {
nickjillings@1399 3084 var str = "You have not moved ";
nickjillings@1399 3085 var failed = [];
nickjillings@1340 3086 for (var ao of audioEngineContext.audioObjects)
nickjillings@1399 3087 {
nickjillings@1340 3088 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
nickjillings@1399 3089 {
nickjillings@1340 3090 failed.push(ao.interfaceDOM.getPresentedId());
nickjillings@1399 3091 }
nickjillings@1399 3092 }
nickjillings@1399 3093 if (failed.length == 0)
nickjillings@1399 3094 {
nickjillings@1399 3095 return true;
nickjillings@1399 3096 } else if (failed.length == 1)
nickjillings@1399 3097 {
nickjillings@1399 3098 str += 'track '+failed[0];
nickjillings@1399 3099 } else {
nickjillings@1399 3100 str += 'tracks ';
nickjillings@1399 3101 for (var i=0; i<failed.length-1; i++)
nickjillings@1399 3102 {
nickjillings@1399 3103 str += failed[i]+', ';
nickjillings@1399 3104 }
nickjillings@1399 3105 str += 'and '+failed[i];
nickjillings@1399 3106 }
nickjillings@1399 3107 str +='.';
nickjillings@1399 3108 alert(str);
nickjillings@1399 3109 console.log(str);
nickjillings@1367 3110 this.storeErrorNode(str);
nickjillings@1399 3111 return false;
nickjillings@1399 3112 };
nickjillings@1399 3113 this.checkAllPlayed = function()
nickjillings@1399 3114 {
nickjillings@1399 3115 var str = "You have not played ";
nickjillings@1399 3116 var failed = [];
nickjillings@1340 3117 for (var ao of audioEngineContext.audioObjects)
nickjillings@1399 3118 {
nickjillings@1340 3119 if(ao.metric.wasListenedTo == false)
nickjillings@1399 3120 {
nickjillings@1340 3121 failed.push(ao.interfaceDOM.getPresentedId());
nickjillings@1399 3122 }
nickjillings@1399 3123 }
nickjillings@1399 3124 if (failed.length == 0)
nickjillings@1399 3125 {
nickjillings@1399 3126 return true;
nickjillings@1399 3127 } else if (failed.length == 1)
nickjillings@1399 3128 {
nickjillings@1399 3129 str += 'track '+failed[0];
nickjillings@1399 3130 } else {
nickjillings@1399 3131 str += 'tracks ';
nickjillings@1399 3132 for (var i=0; i<failed.length-1; i++)
nickjillings@1399 3133 {
nickjillings@1399 3134 str += failed[i]+', ';
nickjillings@1399 3135 }
nickjillings@1399 3136 str += 'and '+failed[i];
nickjillings@1399 3137 }
nickjillings@1399 3138 str +='.';
nickjillings@1399 3139 alert(str);
nickjillings@1399 3140 console.log(str);
nickjillings@1367 3141 this.storeErrorNode(str);
nickjillings@1399 3142 return false;
nickjillings@1399 3143 };
nickjillings@1367 3144
nickjillings@1367 3145 this.storeErrorNode = function(errorMessage)
nickjillings@1367 3146 {
nickjillings@1367 3147 var time = audioEngineContext.timer.getTestTime();
nickjillings@1367 3148 var node = storage.document.createElement('error');
nickjillings@1367 3149 node.setAttribute('time',time);
nickjillings@1367 3150 node.textContent = errorMessage;
nickjillings@1367 3151 testState.currentStore.XMLDOM.appendChild(node);
nickjillings@1367 3152 };
nickjillings@1324 3153 }
nickjillings@1324 3154
nickjillings@1324 3155 function Storage()
nickjillings@1324 3156 {
nickjillings@1324 3157 // Holds results in XML format until ready for collection
nickjillings@1324 3158 this.globalPreTest = null;
nickjillings@1324 3159 this.globalPostTest = null;
nickjillings@1324 3160 this.testPages = [];
nickjillings@1294 3161 this.document = null;
nickjillings@1294 3162 this.root = null;
nickjillings@1324 3163 this.state = 0;
nickjillings@1324 3164
nickjillings@1294 3165 this.initialise = function(existingStore)
nickjillings@1324 3166 {
nickjillings@1294 3167 if (existingStore == undefined) {
nickjillings@2147 3168 // We need to get the sessionKey
nickjillings@2147 3169 this.SessionKey.generateKey();
nickjillings@1294 3170 this.document = document.implementation.createDocument(null,"waetresult");
nickjillings@1294 3171 this.root = this.document.childNodes[0];
nickjillings@2150 3172 var projectDocument = specification.projectXML;
nickjillings@2150 3173 projectDocument.setAttribute('file-name',url);
nickjillings@2150 3174 this.root.appendChild(projectDocument);
nickjillings@2170 3175 this.root.appendChild(interfaceContext.returnDateNode());
nickjillings@2150 3176 this.root.appendChild(interfaceContext.returnNavigator());
nickjillings@2147 3177 } else {
nickjillings@1294 3178 this.document = existingStore;
nickjillings@1294 3179 this.root = existingStore.children[0];
nickjillings@1294 3180 this.SessionKey.key = this.root.getAttribute("key");
nickjillings@2147 3181 }
nickjillings@2150 3182 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
nickjillings@1294 3183 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
nickjillings@1324 3184 };
nickjillings@2147 3185
nickjillings@2147 3186 this.SessionKey = {
nickjillings@2147 3187 key: null,
nickjillings@2147 3188 request: new XMLHttpRequest(),
nickjillings@2147 3189 parent: this,
nickjillings@2147 3190 handleEvent: function() {
nickjillings@2147 3191 var parse = new DOMParser();
nickjillings@2147 3192 var xml = parse.parseFromString(this.request.response,"text/xml");
nickjillings@2147 3193 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
nickjillings@2147 3194 this.key = xml.getAllElementsByTagName("key")[0].textContent;
nickjillings@2150 3195 this.parent.root.setAttribute("key",this.key);
nickjillings@2150 3196 this.parent.root.setAttribute("state","empty");
nickjillings@2147 3197 } else {
nickjillings@2147 3198 this.generateKey();
nickjillings@2147 3199 }
nickjillings@2147 3200 },
nickjillings@2147 3201 generateKey: function() {
nickjillings@2147 3202 var temp_key = randomString(32);
nickjillings@2147 3203 this.request.open("GET","keygen.php?key="+temp_key,true);
nickjillings@2147 3204 this.request.addEventListener("load",this);
nickjillings@2147 3205 this.request.send();
nickjillings@2149 3206 },
nickjillings@2150 3207 update: function() {
nickjillings@2150 3208 this.parent.root.setAttribute("state","update");
nickjillings@2150 3209 var xmlhttp = new XMLHttpRequest();
nickjillings@2150 3210 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
nickjillings@2150 3211 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
nickjillings@2150 3212 xmlhttp.onerror = function(){
nickjillings@2150 3213 console.log('Error updating file to server!');
nickjillings@2150 3214 };
nickjillings@2150 3215 var hold = document.createElement("div");
nickjillings@2150 3216 var clone = this.parent.root.cloneNode(true);
nickjillings@2150 3217 hold.appendChild(clone);
nickjillings@2150 3218 xmlhttp.onload = function() {
nickjillings@2150 3219 if (this.status >= 300) {
nickjillings@2150 3220 console.log("WARNING - Could not update at this time");
nickjillings@2150 3221 } else {
nickjillings@2150 3222 var parser = new DOMParser();
nickjillings@2150 3223 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
nickjillings@2150 3224 var response = xmlDoc.getElementsByTagName('response')[0];
nickjillings@2150 3225 if (response.getAttribute("state") == "OK") {
nickjillings@2150 3226 var file = response.getElementsByTagName("file")[0];
nickjillings@2150 3227 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
nickjillings@2150 3228 } else {
nickjillings@2150 3229 var message = response.getElementsByTagName("message");
nickjillings@2150 3230 console.log("Intermediate save: Error! "+message.textContent);
nickjillings@2150 3231 }
nickjillings@2149 3232 }
nickjillings@2149 3233 }
nickjillings@2150 3234 xmlhttp.send([hold.innerHTML]);
nickjillings@2147 3235 }
nickjillings@2147 3236 }
nickjillings@1324 3237
nickjillings@1324 3238 this.createTestPageStore = function(specification)
nickjillings@1324 3239 {
nickjillings@1324 3240 var store = new this.pageNode(this,specification);
nickjillings@1324 3241 this.testPages.push(store);
nickjillings@1324 3242 return this.testPages[this.testPages.length-1];
nickjillings@1324 3243 };
nickjillings@1324 3244
nickjillings@1324 3245 this.surveyNode = function(parent,root,specification)
nickjillings@1324 3246 {
nickjillings@1324 3247 this.specification = specification;
nickjillings@1324 3248 this.parent = parent;
nickjillings@1294 3249 this.state = "empty";
nickjillings@1324 3250 this.XMLDOM = this.parent.document.createElement('survey');
nickjillings@1324 3251 this.XMLDOM.setAttribute('location',this.specification.location);
nickjillings@1294 3252 this.XMLDOM.setAttribute("state",this.state);
nickjillings@1324 3253 for (var optNode of this.specification.options)
nickjillings@1324 3254 {
nickjillings@1324 3255 if (optNode.type != 'statement')
nickjillings@1324 3256 {
nickjillings@1324 3257 var node = this.parent.document.createElement('surveyresult');
nickjillings@1294 3258 node.setAttribute("ref",optNode.id);
nickjillings@1324 3259 node.setAttribute('type',optNode.type);
nickjillings@1324 3260 this.XMLDOM.appendChild(node);
nickjillings@1324 3261 }
nickjillings@1324 3262 }
nickjillings@1324 3263 root.appendChild(this.XMLDOM);
nickjillings@1324 3264
nickjillings@1324 3265 this.postResult = function(node)
nickjillings@1324 3266 {
nickjillings@1324 3267 // From popup: node is the popupOption node containing both spec. and results
nickjillings@1324 3268 // ID is the position
nickjillings@1324 3269 if (node.specification.type == 'statement'){return;}
nickjillings@1294 3270 var surveyresult = this.XMLDOM.children[0];
nickjillings@1294 3271 while(surveyresult != null) {
nickjillings@1294 3272 if (surveyresult.getAttribute("ref") == node.specification.id)
nickjillings@1294 3273 {
nickjillings@1294 3274 break;
nickjillings@1294 3275 }
nickjillings@1294 3276 surveyresult = surveyresult.nextElementSibling;
nickjillings@1294 3277 }
nickjillings@1324 3278 switch(node.specification.type)
nickjillings@1324 3279 {
nickjillings@1324 3280 case "number":
nickjillings@1324 3281 case "question":
nickjillings@1324 3282 var child = this.parent.document.createElement('response');
nickjillings@1324 3283 child.textContent = node.response;
nickjillings@1324 3284 surveyresult.appendChild(child);
nickjillings@1324 3285 break;
nickjillings@1324 3286 case "radio":
nickjillings@1324 3287 var child = this.parent.document.createElement('response');
nickjillings@1324 3288 child.setAttribute('name',node.response.name);
nickjillings@1324 3289 child.textContent = node.response.text;
nickjillings@1324 3290 surveyresult.appendChild(child);
nickjillings@1324 3291 break;
nickjillings@1324 3292 case "checkbox":
nickjillings@1324 3293 for (var i=0; i<node.response.length; i++)
nickjillings@1324 3294 {
nickjillings@1324 3295 var checkNode = this.parent.document.createElement('response');
nickjillings@1347 3296 checkNode.setAttribute('name',node.response[i].name);
nickjillings@1347 3297 checkNode.setAttribute('checked',node.response[i].checked);
nickjillings@1326 3298 surveyresult.appendChild(checkNode);
nickjillings@1324 3299 }
nickjillings@1324 3300 break;
nickjillings@1324 3301 }
nickjillings@1324 3302 };
nickjillings@1294 3303 this.complete = function() {
nickjillings@1294 3304 this.state = "complete";
nickjillings@1294 3305 this.XMLDOM.setAttribute("state",this.state);
nickjillings@1294 3306 }
nickjillings@1324 3307 };
nickjillings@1324 3308
nickjillings@1324 3309 this.pageNode = function(parent,specification)
nickjillings@1324 3310 {
nickjillings@1324 3311 // Create one store per test page
nickjillings@1324 3312 this.specification = specification;
nickjillings@1324 3313 this.parent = parent;
nickjillings@1294 3314 this.state = "empty";
nickjillings@1324 3315 this.XMLDOM = this.parent.document.createElement('page');
nickjillings@1294 3316 this.XMLDOM.setAttribute('ref',specification.id);
nickjillings@1324 3317 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
nickjillings@1294 3318 this.XMLDOM.setAttribute("state",this.state);
nickjillings@1345 3319 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
nickjillings@1345 3320 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
nickjillings@1324 3321
nickjillings@1324 3322 // Add any page metrics
nickjillings@1324 3323 var page_metric = this.parent.document.createElement('metric');
nickjillings@1324 3324 this.XMLDOM.appendChild(page_metric);
nickjillings@1324 3325
nickjillings@1324 3326 // Add the audioelement
nickjillings@1324 3327 for (var element of this.specification.audioElements)
nickjillings@1324 3328 {
nickjillings@1324 3329 var aeNode = this.parent.document.createElement('audioelement');
nickjillings@1294 3330 aeNode.setAttribute('ref',element.id);
nickjillings@1294 3331 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
nickjillings@1324 3332 aeNode.setAttribute('type',element.type);
nickjillings@1324 3333 aeNode.setAttribute('url', element.url);
nickjillings@1324 3334 aeNode.setAttribute('gain', element.gain);
nickjillings@1324 3335 if (element.type == 'anchor' || element.type == 'reference')
nickjillings@1324 3336 {
nickjillings@1324 3337 if (element.marker > 0)
nickjillings@1324 3338 {
nickjillings@1324 3339 aeNode.setAttribute('marker',element.marker);
nickjillings@1324 3340 }
nickjillings@1324 3341 }
nickjillings@1324 3342 var ae_metric = this.parent.document.createElement('metric');
nickjillings@1324 3343 aeNode.appendChild(ae_metric);
nickjillings@1324 3344 this.XMLDOM.appendChild(aeNode);
nickjillings@1324 3345 }
nickjillings@1324 3346
nickjillings@1324 3347 this.parent.root.appendChild(this.XMLDOM);
nickjillings@1294 3348
nickjillings@1294 3349 this.complete = function() {
nickjillings@1294 3350 this.state = "complete";
nickjillings@1294 3351 this.XMLDOM.setAttribute("state","complete");
nickjillings@1294 3352 }
nickjillings@1324 3353 };
nickjillings@2150 3354 this.update = function() {
nickjillings@2150 3355 this.SessionKey.update();
nickjillings@2150 3356 }
nickjillings@1324 3357 this.finish = function()
nickjillings@1324 3358 {
nickjillings@1324 3359 if (this.state == 0)
nickjillings@1324 3360 {
nickjillings@2150 3361 this.update();
nickjillings@1324 3362 }
nickjillings@1324 3363 this.state = 1;
nickjillings@1324 3364 return this.root;
nickjillings@1324 3365 };
nickjillings@1324 3366 }