annotate core.js @ 1248:f04d8ababef7

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