annotate core.js @ 1237:164bf95acf95

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