annotate core.js @ 1090:c07b9e2312ba

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