annotate core.js @ 602:6531e3903ea1

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