annotate core.js @ 1242:987ccc04250b

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