annotate core.js @ 1094:a589dab20020

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