annotate core.js @ 587:0d6d7618f6da Dev_main

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