annotate core.js @ 620:e0934138c676 Dev_main

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