annotate core.js @ 609:34d8bca65edc multiple-tests-concatenation

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