annotate core.js @ 2167:251c167bf42e

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