annotate core.js @ 1294:e7a819fe85bf

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