annotate core.js @ 1218:72b2bb8526b3

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