annotate core.js @ 1244:75b97c56a9d1

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