annotate core.js @ 1102:b5bf2f57187c

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