annotate core.js @ 1203:1f352095179e

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