annotate core.js @ 1092:e4b378468589

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