annotate core.js @ 611:fef9f13bec0a multiple-tests-concatenation

Workaround for #1658: in order to obtain a test with zero pages, set attribute hidden='true' in the one and only page
author Giulio Moro <giuliomoro@yahoo.it>
date Sun, 13 Mar 2016 02:12:30 +0000
parents 34d8bca65edc
children cad1178196b4
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 }
giuliomoro@611 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');
giuliomoro@611 1478 var buf;
giuliomoro@611 1479 if(typeof(this.buffer) !== "undefined"){
giuliomoro@611 1480 buf = this.buffer.buffer;
giuliomoro@611 1481 } else {
giuliomoro@611 1482 buf = {};
giuliomoro@611 1483 }
giuliomoro@611 1484 file.setAttribute('sampleRate', buf.sampleRate);
giuliomoro@611 1485 file.setAttribute('channels', buf.numberOfChannels);
giuliomoro@611 1486 file.setAttribute('sampleCount', buf.length);
giuliomoro@611 1487 file.setAttribute('duration', buf.duration);
n@453 1488 this.storeDOM.appendChild(file);
n@453 1489 if (this.specification.type != 'outside-reference') {
n@383 1490 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
n@469 1491 if (interfaceXML != null)
n@469 1492 {
n@469 1493 if (interfaceXML.length == undefined) {
n@469 1494 this.storeDOM.appendChild(interfaceXML);
n@469 1495 } else {
n@469 1496 for (var i=0; i<interfaceXML.length; i++)
n@469 1497 {
n@469 1498 this.storeDOM.appendChild(interfaceXML[i]);
n@469 1499 }
n@383 1500 }
n@383 1501 }
n@459 1502 if (this.commentDOM != null) {
n@459 1503 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
n@459 1504 }
nicholas@236 1505 }
n@453 1506 var nodes = this.metric.exportXMLDOM();
n@453 1507 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
n@453 1508 for (var i=0; i<nodes.length; i++)
n@453 1509 {
n@453 1510 mroot.appendChild(nodes[i]);
n@453 1511 }
n@183 1512 };
n@49 1513 }
n@49 1514
n@49 1515 function timer()
n@49 1516 {
n@49 1517 /* Timer object used in audioEngine to keep track of session timings
n@49 1518 * Uses the timer of the web audio API, so sample resolution
n@49 1519 */
n@49 1520 this.testStarted = false;
n@49 1521 this.testStartTime = 0;
n@49 1522 this.testDuration = 0;
n@49 1523 this.minimumTestTime = 0; // No minimum test time
n@49 1524 this.startTest = function()
n@49 1525 {
n@49 1526 if (this.testStarted == false)
n@49 1527 {
n@49 1528 this.testStartTime = audioContext.currentTime;
n@49 1529 this.testStarted = true;
n@49 1530 this.updateTestTime();
n@52 1531 audioEngineContext.metric.initialiseTest();
n@49 1532 }
n@49 1533 };
n@49 1534 this.stopTest = function()
n@49 1535 {
n@49 1536 if (this.testStarted)
n@49 1537 {
n@49 1538 this.testDuration = this.getTestTime();
n@49 1539 this.testStarted = false;
n@49 1540 } else {
n@49 1541 console.log('ERR: Test tried to end before beginning');
n@49 1542 }
n@49 1543 };
n@49 1544 this.updateTestTime = function()
n@49 1545 {
n@49 1546 if (this.testStarted)
n@49 1547 {
n@49 1548 this.testDuration = audioContext.currentTime - this.testStartTime;
n@49 1549 }
n@49 1550 };
n@49 1551 this.getTestTime = function()
n@49 1552 {
n@49 1553 this.updateTestTime();
n@49 1554 return this.testDuration;
n@49 1555 };
n@49 1556 }
n@49 1557
n@377 1558 function sessionMetrics(engine,specification)
n@49 1559 {
n@49 1560 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
n@49 1561 */
n@49 1562 this.engine = engine;
n@49 1563 this.lastClicked = -1;
n@49 1564 this.data = -1;
n@113 1565 this.reset = function() {
n@113 1566 this.lastClicked = -1;
n@113 1567 this.data = -1;
n@113 1568 };
n@377 1569
n@377 1570 this.enableElementInitialPosition = false;
n@377 1571 this.enableElementListenTracker = false;
n@377 1572 this.enableElementTimer = false;
n@377 1573 this.enableElementTracker = false;
n@377 1574 this.enableFlagListenedTo = false;
n@377 1575 this.enableFlagMoved = false;
n@377 1576 this.enableTestTimer = false;
n@377 1577 // Obtain the metrics enabled
n@453 1578 for (var i=0; i<specification.metrics.enabled.length; i++)
n@377 1579 {
n@453 1580 var node = specification.metrics.enabled[i];
n@453 1581 switch(node)
n@377 1582 {
n@377 1583 case 'testTimer':
n@377 1584 this.enableTestTimer = true;
n@377 1585 break;
n@377 1586 case 'elementTimer':
n@377 1587 this.enableElementTimer = true;
n@377 1588 break;
n@377 1589 case 'elementTracker':
n@377 1590 this.enableElementTracker = true;
n@377 1591 break;
n@377 1592 case 'elementListenTracker':
n@377 1593 this.enableElementListenTracker = true;
n@377 1594 break;
n@377 1595 case 'elementInitialPosition':
n@377 1596 this.enableElementInitialPosition = true;
n@377 1597 break;
n@377 1598 case 'elementFlagListenedTo':
n@377 1599 this.enableFlagListenedTo = true;
n@377 1600 break;
n@377 1601 case 'elementFlagMoved':
n@377 1602 this.enableFlagMoved = true;
n@377 1603 break;
n@377 1604 case 'elementFlagComments':
n@377 1605 this.enableFlagComments = true;
n@377 1606 break;
n@377 1607 }
n@377 1608 }
n@52 1609 this.initialiseTest = function(){};
n@49 1610 }
n@49 1611
n@139 1612 function metricTracker(caller)
n@49 1613 {
n@49 1614 /* Custom object to track and collect metric data
n@49 1615 * Used only inside the audioObjects object.
n@49 1616 */
n@49 1617
n@49 1618 this.listenedTimer = 0;
n@49 1619 this.listenStart = 0;
nicholas@110 1620 this.listenHold = false;
n@51 1621 this.initialPosition = -1;
n@49 1622 this.movementTracker = [];
n@164 1623 this.listenTracker =[];
n@49 1624 this.wasListenedTo = false;
n@49 1625 this.wasMoved = false;
n@49 1626 this.hasComments = false;
n@139 1627 this.parent = caller;
n@49 1628
n@453 1629 this.initialise = function(position)
n@49 1630 {
n@51 1631 if (this.initialPosition == -1) {
n@51 1632 this.initialPosition = position;
n@454 1633 this.moved(0,position);
n@51 1634 }
n@49 1635 };
n@49 1636
n@49 1637 this.moved = function(time,position)
n@49 1638 {
n@454 1639 if (time > 0) {this.wasMoved = true;}
n@49 1640 this.movementTracker[this.movementTracker.length] = [time, position];
n@49 1641 };
n@49 1642
nicholas@132 1643 this.startListening = function(time)
n@49 1644 {
nicholas@110 1645 if (this.listenHold == false)
n@49 1646 {
n@49 1647 this.wasListenedTo = true;
n@49 1648 this.listenStart = time;
nicholas@110 1649 this.listenHold = true;
n@164 1650
n@164 1651 var evnt = document.createElement('event');
n@164 1652 var testTime = document.createElement('testTime');
n@164 1653 testTime.setAttribute('start',time);
n@164 1654 var bufferTime = document.createElement('bufferTime');
n@164 1655 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
n@164 1656 evnt.appendChild(testTime);
n@164 1657 evnt.appendChild(bufferTime);
n@164 1658 this.listenTracker.push(evnt);
n@164 1659
n@139 1660 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
n@139 1661 }
n@139 1662 };
nicholas@132 1663
n@203 1664 this.stopListening = function(time,bufferStopTime)
nicholas@132 1665 {
nicholas@132 1666 if (this.listenHold == true)
nicholas@132 1667 {
n@164 1668 var diff = time - this.listenStart;
n@164 1669 this.listenedTimer += (diff);
n@49 1670 this.listenStart = 0;
nicholas@110 1671 this.listenHold = false;
n@164 1672
n@164 1673 var evnt = this.listenTracker[this.listenTracker.length-1];
n@164 1674 var testTime = evnt.getElementsByTagName('testTime')[0];
n@164 1675 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
n@164 1676 testTime.setAttribute('stop',time);
n@203 1677 if (bufferStopTime == undefined) {
n@203 1678 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
n@203 1679 } else {
n@203 1680 bufferTime.setAttribute('stop',bufferStopTime);
n@203 1681 }
n@164 1682 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
n@49 1683 }
n@49 1684 };
n@177 1685
n@177 1686 this.exportXMLDOM = function() {
n@453 1687 var storeDOM = [];
n@177 1688 if (audioEngineContext.metric.enableElementTimer) {
n@453 1689 var mElementTimer = storage.document.createElement('metricresult');
n@177 1690 mElementTimer.setAttribute('name','enableElementTimer');
n@177 1691 mElementTimer.textContent = this.listenedTimer;
n@453 1692 storeDOM.push(mElementTimer);
n@177 1693 }
n@177 1694 if (audioEngineContext.metric.enableElementTracker) {
n@453 1695 var elementTrackerFull = storage.document.createElement('metricResult');
n@177 1696 elementTrackerFull.setAttribute('name','elementTrackerFull');
n@177 1697 for (var k=0; k<this.movementTracker.length; k++)
n@177 1698 {
n@575 1699 var timePos = storage.document.createElement('movement');
n@575 1700 timePos.setAttribute("time",this.movementTracker[k][0]);
n@575 1701 timePos.setAttribute("value",this.movementTracker[k][1]);
n@177 1702 elementTrackerFull.appendChild(timePos);
n@177 1703 }
n@453 1704 storeDOM.push(elementTrackerFull);
n@177 1705 }
n@177 1706 if (audioEngineContext.metric.enableElementListenTracker) {
n@453 1707 var elementListenTracker = storage.document.createElement('metricResult');
n@177 1708 elementListenTracker.setAttribute('name','elementListenTracker');
n@177 1709 for (var k=0; k<this.listenTracker.length; k++) {
n@177 1710 elementListenTracker.appendChild(this.listenTracker[k]);
n@177 1711 }
n@453 1712 storeDOM.push(elementListenTracker);
n@177 1713 }
n@177 1714 if (audioEngineContext.metric.enableElementInitialPosition) {
n@453 1715 var elementInitial = storage.document.createElement('metricResult');
n@177 1716 elementInitial.setAttribute('name','elementInitialPosition');
n@177 1717 elementInitial.textContent = this.initialPosition;
n@453 1718 storeDOM.push(elementInitial);
n@177 1719 }
n@177 1720 if (audioEngineContext.metric.enableFlagListenedTo) {
n@453 1721 var flagListenedTo = storage.document.createElement('metricResult');
n@177 1722 flagListenedTo.setAttribute('name','elementFlagListenedTo');
n@177 1723 flagListenedTo.textContent = this.wasListenedTo;
n@453 1724 storeDOM.push(flagListenedTo);
n@177 1725 }
n@177 1726 if (audioEngineContext.metric.enableFlagMoved) {
n@453 1727 var flagMoved = storage.document.createElement('metricResult');
n@177 1728 flagMoved.setAttribute('name','elementFlagMoved');
n@177 1729 flagMoved.textContent = this.wasMoved;
n@453 1730 storeDOM.push(flagMoved);
n@177 1731 }
n@177 1732 if (audioEngineContext.metric.enableFlagComments) {
n@453 1733 var flagComments = storage.document.createElement('metricResult');
n@177 1734 flagComments.setAttribute('name','elementFlagComments');
n@177 1735 if (this.parent.commentDOM == null)
n@177 1736 {flag.textContent = 'false';}
n@177 1737 else if (this.parent.commentDOM.textContent.length == 0)
n@177 1738 {flag.textContent = 'false';}
n@177 1739 else
n@177 1740 {flag.textContet = 'true';}
n@453 1741 storeDOM.push(flagComments);
n@177 1742 }
n@453 1743 return storeDOM;
n@177 1744 };
n@54 1745 }
n@54 1746
n@54 1747 function randomiseOrder(input)
n@54 1748 {
n@54 1749 // This takes an array of information and randomises the order
n@54 1750 var N = input.length;
b@207 1751
b@207 1752 var inputSequence = []; // For safety purposes: keep track of randomisation
b@207 1753 for (var counter = 0; counter < N; ++counter)
b@207 1754 inputSequence.push(counter) // Fill array
b@207 1755 var inputSequenceClone = inputSequence.slice(0);
b@207 1756
n@54 1757 var holdArr = [];
b@207 1758 var outputSequence = [];
n@54 1759 for (var n=0; n<N; n++)
n@54 1760 {
n@54 1761 // First pick a random number
n@54 1762 var r = Math.random();
n@54 1763 // Multiply and floor by the number of elements left
n@54 1764 r = Math.floor(r*input.length);
n@54 1765 // Pick out that element and delete from the array
n@54 1766 holdArr.push(input.splice(r,1)[0]);
b@207 1767 // Do the same with sequence
b@207 1768 outputSequence.push(inputSequence.splice(r,1)[0]);
n@54 1769 }
b@207 1770 console.log(inputSequenceClone.toString()); // print original array to console
b@207 1771 console.log(outputSequence.toString()); // print randomised array to console
n@54 1772 return holdArr;
n@125 1773 }
n@125 1774
n@125 1775 function returnDateNode()
n@125 1776 {
n@125 1777 // Create an XML Node for the Date and Time a test was conducted
n@125 1778 // Structure is
n@125 1779 // <datetime>
n@125 1780 // <date year="##" month="##" day="##">DD/MM/YY</date>
n@125 1781 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
n@125 1782 // </datetime>
n@125 1783 var dateTime = new Date();
n@125 1784 var year = document.createAttribute('year');
n@125 1785 var month = document.createAttribute('month');
n@125 1786 var day = document.createAttribute('day');
n@125 1787 var hour = document.createAttribute('hour');
n@125 1788 var minute = document.createAttribute('minute');
n@125 1789 var secs = document.createAttribute('secs');
n@125 1790
n@125 1791 year.nodeValue = dateTime.getFullYear();
n@125 1792 month.nodeValue = dateTime.getMonth()+1;
n@125 1793 day.nodeValue = dateTime.getDate();
n@125 1794 hour.nodeValue = dateTime.getHours();
n@125 1795 minute.nodeValue = dateTime.getMinutes();
n@125 1796 secs.nodeValue = dateTime.getSeconds();
n@125 1797
n@125 1798 var hold = document.createElement("datetime");
n@125 1799 var date = document.createElement("date");
n@125 1800 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
n@125 1801 var time = document.createElement("time");
n@125 1802 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
n@125 1803
n@125 1804 date.setAttributeNode(year);
n@125 1805 date.setAttributeNode(month);
n@125 1806 date.setAttributeNode(day);
n@125 1807 time.setAttributeNode(hour);
n@125 1808 time.setAttributeNode(minute);
n@125 1809 time.setAttributeNode(secs);
n@125 1810
n@125 1811 hold.appendChild(date);
n@125 1812 hold.appendChild(time);
n@377 1813 return hold;
n@125 1814
nicholas@135 1815 }
nicholas@135 1816
n@180 1817 function Specification() {
n@180 1818 // Handles the decoding of the project specification XML into a simple JavaScript Object.
n@180 1819
n@453 1820 this.interface = null;
n@504 1821 this.projectReturn = "null";
n@453 1822 this.randomiseOrder = null;
n@453 1823 this.testPages = null;
n@453 1824 this.pages = [];
n@453 1825 this.metrics = null;
n@453 1826 this.interfaces = null;
n@453 1827 this.loudness = null;
n@453 1828 this.errors = [];
n@453 1829 this.schema = null;
n@380 1830
n@453 1831 this.processAttribute = function(attribute,schema)
n@453 1832 {
n@453 1833 // attribute is the string returned from getAttribute on the XML
n@453 1834 // schema is the <xs:attribute> node
n@453 1835 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
n@453 1836 {
n@477 1837 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
n@453 1838 }
n@453 1839 var defaultOpt = schema.getAttribute('default');
n@453 1840 if (attribute == null) {
n@453 1841 attribute = defaultOpt;
n@453 1842 }
n@453 1843 var dataType = schema.getAttribute('type');
n@453 1844 if (typeof dataType == "string") { dataType = dataType.substr(3);}
n@453 1845 else {dataType = "string";}
n@453 1846 if (attribute == null)
n@453 1847 {
n@453 1848 return attribute;
n@453 1849 }
n@453 1850 switch(dataType)
n@453 1851 {
n@453 1852 case "boolean":
n@453 1853 if (attribute == 'true'){attribute = true;}else{attribute=false;}
n@453 1854 break;
n@453 1855 case "negativeInteger":
n@453 1856 case "positiveInteger":
n@453 1857 case "nonNegativeInteger":
n@453 1858 case "nonPositiveInteger":
n@453 1859 case "integer":
n@453 1860 case "decimal":
n@453 1861 case "short":
n@453 1862 attribute = Number(attribute);
n@453 1863 break;
n@453 1864 case "string":
n@453 1865 default:
n@453 1866 attribute = String(attribute);
n@453 1867 break;
n@453 1868 }
n@453 1869 return attribute;
n@453 1870 };
n@180 1871
n@374 1872 this.decode = function(projectXML) {
n@453 1873 this.errors = [];
n@180 1874 // projectXML - DOM Parsed document
nicholas@240 1875 this.projectXML = projectXML.childNodes[0];
n@180 1876 var setupNode = projectXML.getElementsByTagName('setup')[0];
n@477 1877 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@453 1878 // First decode the attributes
n@477 1879 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@453 1880 for (var i in attributes)
n@297 1881 {
n@453 1882 if (isNaN(Number(i)) == true){break;}
n@453 1883 var attributeName = attributes[i].getAttribute('name');
n@453 1884 var projectAttr = setupNode.getAttribute(attributeName);
n@453 1885 projectAttr = this.processAttribute(projectAttr,attributes[i]);
n@453 1886 switch(typeof projectAttr)
n@410 1887 {
n@453 1888 case "number":
n@453 1889 case "boolean":
n@453 1890 eval('this.'+attributeName+' = '+projectAttr);
n@453 1891 break;
n@453 1892 case "string":
n@453 1893 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 1894 break;
n@410 1895 }
n@453 1896
n@374 1897 }
n@374 1898
n@501 1899 this.metrics = new this.metricNode();
n@180 1900
n@453 1901 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
n@453 1902
n@453 1903 // Now process the survey node options
n@453 1904 var survey = setupNode.getElementsByTagName('survey');
n@453 1905 for (var i in survey) {
n@453 1906 if (isNaN(Number(i)) == true){break;}
n@453 1907 var location = survey[i].getAttribute('location');
n@453 1908 if (location == 'pre' || location == 'before')
n@453 1909 {
n@453 1910 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@453 1911 else {
n@453 1912 this.preTest = new this.surveyNode();
n@501 1913 this.preTest.decode(this,survey[i]);
n@453 1914 }
n@453 1915 } else if (location == 'post' || location == 'after') {
n@453 1916 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@453 1917 else {
n@453 1918 this.postTest = new this.surveyNode();
n@501 1919 this.postTest.decode(this,survey[i]);
n@453 1920 }
n@180 1921 }
n@180 1922 }
n@180 1923
n@453 1924 var interfaceNode = setupNode.getElementsByTagName('interface');
n@453 1925 if (interfaceNode.length > 1)
n@453 1926 {
n@453 1927 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
n@453 1928 }
n@453 1929 this.interfaces = new this.interfaceNode();
n@453 1930 if (interfaceNode.length != 0)
n@453 1931 {
n@453 1932 interfaceNode = interfaceNode[0];
n@477 1933 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
nicholas@213 1934 }
nicholas@213 1935
n@453 1936 // Page tags
n@453 1937 var pageTags = projectXML.getElementsByTagName('page');
n@477 1938 var pageSchema = this.schema.getAllElementsByName('page')[0];
n@453 1939 for (var i=0; i<pageTags.length; i++)
n@297 1940 {
n@453 1941 var node = new this.page();
n@453 1942 node.decode(this,pageTags[i],pageSchema);
n@453 1943 this.pages.push(node);
n@297 1944 }
n@180 1945 };
n@180 1946
n@374 1947 this.encode = function()
n@374 1948 {
n@503 1949 var RootDocument = document.implementation.createDocument(null,"waet");
n@503 1950 var root = RootDocument.children[0];
n@503 1951 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
n@503 1952 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
n@453 1953 // Build setup node
n@503 1954 var setup = RootDocument.createElement("setup");
n@503 1955 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
n@503 1956 // First decode the attributes
n@503 1957 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
n@503 1958 for (var i=0; i<attributes.length; i++)
n@503 1959 {
n@503 1960 var name = attributes[i].getAttribute("name");
n@503 1961 if (name == undefined) {
n@503 1962 name = attributes[i].getAttribute("ref");
n@503 1963 }
n@503 1964 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 1965 {
n@503 1966 eval("setup.setAttribute('"+name+"',this."+name+")");
n@503 1967 }
n@503 1968 }
n@503 1969 root.appendChild(setup);
n@503 1970 // Survey node
n@503 1971 setup.appendChild(this.preTest.encode(RootDocument));
n@503 1972 setup.appendChild(this.postTest.encode(RootDocument));
n@503 1973 setup.appendChild(this.metrics.encode(RootDocument));
n@503 1974 setup.appendChild(this.interfaces.encode(RootDocument));
n@503 1975 for (var page of this.pages)
n@503 1976 {
n@503 1977 root.appendChild(page.encode(RootDocument));
n@503 1978 }
n@503 1979 return RootDocument;
n@374 1980 };
n@374 1981
n@453 1982 this.surveyNode = function() {
n@453 1983 this.location = null;
n@180 1984 this.options = [];
n@501 1985 this.schema = specification.schema.getAllElementsByName('survey')[0];
n@180 1986
n@374 1987 this.OptionNode = function() {
n@374 1988 this.type = undefined;
n@501 1989 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
n@374 1990 this.id = undefined;
n@597 1991 this.name = undefined;
n@374 1992 this.mandatory = undefined;
n@374 1993 this.statement = undefined;
n@374 1994 this.boxsize = undefined;
n@374 1995 this.options = [];
n@374 1996 this.min = undefined;
n@374 1997 this.max = undefined;
n@374 1998 this.step = undefined;
n@374 1999
n@501 2000 this.decode = function(parent,child)
n@374 2001 {
n@501 2002 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2003 for (var i in attributeMap){
n@453 2004 if(isNaN(Number(i)) == true){break;}
n@453 2005 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2006 var projectAttr = child.getAttribute(attributeName);
n@453 2007 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
n@453 2008 switch(typeof projectAttr)
n@453 2009 {
n@453 2010 case "number":
n@453 2011 case "boolean":
n@453 2012 eval('this.'+attributeName+' = '+projectAttr);
n@453 2013 break;
n@453 2014 case "string":
n@453 2015 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2016 break;
n@374 2017 }
n@453 2018 }
n@453 2019 this.statement = child.getElementsByTagName('statement')[0].textContent;
n@453 2020 if (this.type == "checkbox" || this.type == "radio") {
n@453 2021 var children = child.getElementsByTagName('option');
n@453 2022 if (children.length == null) {
n@374 2023 console.log('Malformed' +child.nodeName+ 'entry');
n@374 2024 this.statement = 'Malformed' +child.nodeName+ 'entry';
n@374 2025 this.type = 'statement';
n@374 2026 } else {
n@374 2027 this.options = [];
n@453 2028 for (var i in children)
n@453 2029 {
n@453 2030 if (isNaN(Number(i))==true){break;}
n@453 2031 this.options.push({
n@453 2032 name: children[i].getAttribute('name'),
n@453 2033 text: children[i].textContent
n@453 2034 });
n@374 2035 }
n@374 2036 }
n@191 2037 }
n@374 2038 };
n@374 2039
n@503 2040 this.exportXML = function(doc)
n@374 2041 {
n@544 2042 var node = doc.createElement('surveyentry');
n@453 2043 node.setAttribute('type',this.type);
n@503 2044 var statement = doc.createElement('statement');
n@453 2045 statement.textContent = this.statement;
n@453 2046 node.appendChild(statement);
n@597 2047 if (this.type != "statement") {
n@544 2048 node.id = this.id;
n@597 2049 if (this.name != undefined) { node.setAttribute("name",this.name);}
n@544 2050 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
n@597 2051 switch(this.type)
n@597 2052 {
n@597 2053 case "question":
n@597 2054 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
n@597 2055 break;
n@597 2056 case "number":
n@597 2057 if (this.min != undefined) {node.setAttribute("min", this.min);}
n@597 2058 if (this.max != undefined) {node.setAttribute("max", this.max);}
n@597 2059 break;
n@597 2060 case "checkbox":
n@597 2061 case "radio":
n@597 2062 for (var i=0; i<this.options.length; i++)
n@597 2063 {
n@597 2064 var option = this.options[i];
n@597 2065 var optionNode = doc.createElement("option");
n@597 2066 optionNode.setAttribute("name",option.name);
n@597 2067 optionNode.textContent = option.text;
n@597 2068 node.appendChild(optionNode);
n@597 2069 }
n@597 2070 break;
n@597 2071 }
n@597 2072 }
n@374 2073 return node;
n@374 2074 };
n@374 2075 };
n@501 2076 this.decode = function(parent,xml) {
n@453 2077 this.location = xml.getAttribute('location');
n@453 2078 if (this.location == 'before'){this.location = 'pre';}
n@453 2079 else if (this.location == 'after'){this.location = 'post';}
n@453 2080 for (var i in xml.children)
n@453 2081 {
n@453 2082 if(isNaN(Number(i))==true){break;}
n@374 2083 var node = new this.OptionNode();
n@501 2084 node.decode(parent,xml.children[i]);
n@374 2085 this.options.push(node);
n@453 2086 }
n@453 2087 };
n@503 2088 this.encode = function(doc) {
n@503 2089 var node = doc.createElement('survey');
n@453 2090 node.setAttribute('location',this.location);
n@453 2091 for (var i=0; i<this.options.length; i++)
n@453 2092 {
n@503 2093 node.appendChild(this.options[i].exportXML(doc));
n@453 2094 }
n@453 2095 return node;
n@453 2096 };
n@453 2097 };
n@453 2098
n@453 2099 this.interfaceNode = function()
n@453 2100 {
n@453 2101 this.title = null;
n@453 2102 this.name = null;
n@453 2103 this.options = [];
n@453 2104 this.scales = [];
n@501 2105 this.schema = specification.schema.getAllElementsByName('interface')[1];
n@453 2106
n@501 2107 this.decode = function(parent,xml) {
n@453 2108 this.name = xml.getAttribute('name');
n@453 2109 var titleNode = xml.getElementsByTagName('title');
n@453 2110 if (titleNode.length == 1)
n@453 2111 {
n@453 2112 this.title = titleNode[0].textContent;
n@453 2113 }
n@453 2114 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
n@453 2115 // Extract interfaceoption node schema
n@501 2116 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
n@477 2117 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
n@453 2118 for (var i=0; i<interfaceOptionNodes.length; i++)
n@453 2119 {
n@453 2120 var ioNode = interfaceOptionNodes[i];
n@453 2121 var option = {};
n@453 2122 for (var j=0; j<attributeMap.length; j++)
n@453 2123 {
n@453 2124 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
n@453 2125 var projectAttr = ioNode.getAttribute(attributeName);
n@453 2126 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
n@453 2127 switch(typeof projectAttr)
n@453 2128 {
n@453 2129 case "number":
n@453 2130 case "boolean":
n@453 2131 eval('option.'+attributeName+' = '+projectAttr);
n@453 2132 break;
n@453 2133 case "string":
n@453 2134 eval('option.'+attributeName+' = "'+projectAttr+'"');
n@453 2135 break;
n@453 2136 }
n@453 2137 }
n@453 2138 this.options.push(option);
n@453 2139 }
n@453 2140
n@453 2141 // Now the scales nodes
n@453 2142 var scaleParent = xml.getElementsByTagName('scales');
n@453 2143 if (scaleParent.length == 1) {
n@453 2144 scaleParent = scaleParent[0];
n@453 2145 for (var i=0; i<scaleParent.children.length; i++) {
n@453 2146 var child = scaleParent.children[i];
n@453 2147 this.scales.push({
n@453 2148 text: child.textContent,
n@453 2149 position: Number(child.getAttribute('position'))
n@453 2150 });
n@374 2151 }
n@180 2152 }
n@180 2153 };
n@453 2154
n@503 2155 this.encode = function(doc) {
n@503 2156 var node = doc.createElement("interface");
n@503 2157 if (typeof name == "string")
n@503 2158 node.setAttribute("name",this.name);
n@503 2159 for (var option of this.options)
n@503 2160 {
n@503 2161 var child = doc.createElement("interfaceoption");
n@503 2162 child.setAttribute("type",option.type);
n@503 2163 child.setAttribute("name",option.name);
n@503 2164 node.appendChild(child);
n@503 2165 }
n@503 2166 if (this.scales.length != 0) {
n@503 2167 var scales = doc.createElement("scales");
n@503 2168 for (var scale of this.scales)
n@503 2169 {
n@503 2170 var child = doc.createElement("scalelabel");
n@503 2171 child.setAttribute("position",scale.position);
n@503 2172 child.textContent = scale.text;
n@503 2173 scales.appendChild(child);
n@503 2174 }
n@503 2175 node.appendChild(scales);
n@503 2176 }
n@503 2177 return node;
n@453 2178 };
n@180 2179 };
n@180 2180
n@501 2181 this.metricNode = function() {
n@501 2182 this.enabled = [];
n@501 2183 this.decode = function(parent, xml) {
n@501 2184 var children = xml.getElementsByTagName('metricenable');
n@501 2185 for (var i in children) {
n@501 2186 if (isNaN(Number(i)) == true){break;}
n@501 2187 this.enabled.push(children[i].textContent);
n@501 2188 }
n@501 2189 }
n@503 2190 this.encode = function(doc) {
n@503 2191 var node = doc.createElement('metric');
n@501 2192 for (var i in this.enabled)
n@501 2193 {
n@501 2194 if (isNaN(Number(i)) == true){break;}
n@503 2195 var child = doc.createElement('metricenable');
n@501 2196 child.textContent = this.enabled[i];
n@501 2197 node.appendChild(child);
n@501 2198 }
n@501 2199 return node;
n@501 2200 }
n@501 2201 }
n@501 2202
n@453 2203 this.page = function() {
n@374 2204 this.presentedId = undefined;
n@374 2205 this.id = undefined;
n@374 2206 this.hostURL = undefined;
n@374 2207 this.randomiseOrder = undefined;
n@374 2208 this.loop = undefined;
n@453 2209 this.showElementComments = undefined;
n@374 2210 this.outsideReference = null;
n@410 2211 this.loudness = null;
n@603 2212 this.label = null;
n@453 2213 this.preTest = null;
n@453 2214 this.postTest = null;
n@374 2215 this.interfaces = [];
n@374 2216 this.commentBoxPrefix = "Comment on track";
n@374 2217 this.audioElements = [];
n@374 2218 this.commentQuestions = [];
n@501 2219 this.schema = specification.schema.getAllElementsByName("page")[0];
n@374 2220
n@501 2221 this.decode = function(parent,xml)
n@374 2222 {
n@477 2223 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2224 for (var i=0; i<attributeMap.length; i++)
n@410 2225 {
n@453 2226 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2227 var projectAttr = xml.getAttribute(attributeName);
n@453 2228 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
n@453 2229 switch(typeof projectAttr)
nicholas@417 2230 {
n@453 2231 case "number":
n@453 2232 case "boolean":
n@453 2233 eval('this.'+attributeName+' = '+projectAttr);
n@453 2234 break;
n@453 2235 case "string":
n@453 2236 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2237 break;
n@374 2238 }
n@374 2239 }
n@374 2240
n@453 2241 // Get the Comment Box Prefix
n@453 2242 var CBP = xml.getElementsByTagName('commentboxprefix');
n@453 2243 if (CBP.length != 0) {
n@453 2244 this.commentBoxPrefix = CBP[0].textContent;
n@427 2245 }
n@427 2246
n@453 2247 // Now decode the interfaces
n@453 2248 var interfaceNode = xml.getElementsByTagName('interface');
n@453 2249 for (var i=0; i<interfaceNode.length; i++)
n@453 2250 {
n@453 2251 var node = new parent.interfaceNode();
n@477 2252 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
n@453 2253 this.interfaces.push(node);
n@453 2254 }
n@380 2255
n@453 2256 // Now process the survey node options
n@453 2257 var survey = xml.getElementsByTagName('survey');
n@477 2258 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
n@453 2259 for (var i in survey) {
n@453 2260 if (isNaN(Number(i)) == true){break;}
n@453 2261 var location = survey[i].getAttribute('location');
n@453 2262 if (location == 'pre' || location == 'before')
n@453 2263 {
n@453 2264 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
n@453 2265 else {
n@453 2266 this.preTest = new parent.surveyNode();
n@453 2267 this.preTest.decode(parent,survey[i],surveySchema);
n@453 2268 }
n@453 2269 } else if (location == 'post' || location == 'after') {
n@453 2270 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
n@453 2271 else {
n@453 2272 this.postTest = new parent.surveyNode();
n@453 2273 this.postTest.decode(parent,survey[i],surveySchema);
n@453 2274 }
n@453 2275 }
n@453 2276 }
n@453 2277
n@453 2278 // Now process the audioelement tags
n@453 2279 var audioElements = xml.getElementsByTagName('audioelement');
n@453 2280 for (var i=0; i<audioElements.length; i++)
n@453 2281 {
n@453 2282 var node = new this.audioElementNode();
n@501 2283 node.decode(this,audioElements[i]);
n@453 2284 this.audioElements.push(node);
n@453 2285 }
n@453 2286
n@453 2287 // Now decode the commentquestions
n@453 2288 var commentQuestions = xml.getElementsByTagName('commentquestion');
n@453 2289 for (var i=0; i<commentQuestions.length; i++)
n@453 2290 {
n@374 2291 var node = new this.commentQuestionNode();
n@501 2292 node.decode(parent,commentQuestions[i]);
n@374 2293 this.commentQuestions.push(node);
n@180 2294 }
n@180 2295 };
n@180 2296
n@374 2297 this.encode = function(root)
n@374 2298 {
n@503 2299 var AHNode = root.createElement("page");
n@503 2300 // First decode the attributes
n@503 2301 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@503 2302 for (var i=0; i<attributes.length; i++)
n@503 2303 {
n@503 2304 var name = attributes[i].getAttribute("name");
n@503 2305 if (name == undefined) {
n@503 2306 name = attributes[i].getAttribute("ref");
n@503 2307 }
n@503 2308 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 2309 {
n@503 2310 eval("AHNode.setAttribute('"+name+"',this."+name+")");
n@503 2311 }
n@503 2312 }
n@410 2313 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
n@503 2314 // <commentboxprefix>
n@503 2315 var commentboxprefix = root.createElement("commentboxprefix");
n@503 2316 commentboxprefix.textContent = this.commentBoxPrefix;
n@503 2317 AHNode.appendChild(commentboxprefix);
n@503 2318
n@374 2319 for (var i=0; i<this.interfaces.length; i++)
n@324 2320 {
n@374 2321 AHNode.appendChild(this.interfaces[i].encode(root));
n@374 2322 }
n@374 2323
n@374 2324 for (var i=0; i<this.audioElements.length; i++) {
n@374 2325 AHNode.appendChild(this.audioElements[i].encode(root));
n@374 2326 }
n@374 2327 // Create <CommentQuestion>
n@374 2328 for (var i=0; i<this.commentQuestions.length; i++)
n@374 2329 {
n@503 2330 AHNode.appendChild(this.commentQuestions[i].encode(root));
n@374 2331 }
n@374 2332
n@503 2333 AHNode.appendChild(this.preTest.encode(root));
n@503 2334 AHNode.appendChild(this.postTest.encode(root));
n@374 2335 return AHNode;
n@374 2336 };
n@374 2337
n@453 2338 this.commentQuestionNode = function() {
n@453 2339 this.id = null;
n@597 2340 this.name = undefined;
n@453 2341 this.type = undefined;
n@374 2342 this.options = [];
n@453 2343 this.statement = undefined;
n@501 2344 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
n@501 2345 this.decode = function(parent,xml)
n@374 2346 {
n@453 2347 this.id = xml.id;
n@597 2348 this.name = xml.getAttribute('name');
n@453 2349 this.type = xml.getAttribute('type');
n@453 2350 this.statement = xml.getElementsByTagName('statement')[0].textContent;
n@453 2351 var optNodes = xml.getElementsByTagName('option');
n@453 2352 for (var i=0; i<optNodes.length; i++)
n@453 2353 {
n@453 2354 var optNode = optNodes[i];
n@453 2355 this.options.push({
n@453 2356 name: optNode.getAttribute('name'),
n@453 2357 text: optNode.textContent
n@453 2358 });
n@374 2359 }
n@374 2360 };
n@453 2361
n@374 2362 this.encode = function(root)
n@374 2363 {
n@503 2364 var node = root.createElement("commentquestion");
n@503 2365 node.id = this.id;
n@503 2366 node.setAttribute("type",this.type);
n@597 2367 if (this.name != undefined){node.setAttribute("name",this.name);}
n@503 2368 var statement = root.createElement("statement");
n@503 2369 statement.textContent = this.statement;
n@503 2370 node.appendChild(statement);
n@503 2371 for (var option of this.options)
n@503 2372 {
n@503 2373 var child = root.createElement("option");
n@503 2374 child.setAttribute("name",option.name);
n@503 2375 child.textContent = option.text;
n@503 2376 node.appendChild(child);
n@503 2377 }
n@503 2378 return node;
n@374 2379 };
n@374 2380 };
n@374 2381
n@374 2382 this.audioElementNode = function() {
n@374 2383 this.url = null;
n@374 2384 this.id = null;
n@597 2385 this.name = null;
n@374 2386 this.parent = null;
n@453 2387 this.type = null;
n@525 2388 this.marker = null;
n@374 2389 this.enforce = false;
n@564 2390 this.gain = 0.0;
n@501 2391 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
n@453 2392 this.parent = null;
n@501 2393 this.decode = function(parent,xml)
n@374 2394 {
n@374 2395 this.parent = parent;
n@477 2396 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
n@453 2397 for (var i=0; i<attributeMap.length; i++)
n@400 2398 {
n@453 2399 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
n@453 2400 var projectAttr = xml.getAttribute(attributeName);
n@453 2401 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
n@453 2402 switch(typeof projectAttr)
n@374 2403 {
n@453 2404 case "number":
n@453 2405 case "boolean":
n@453 2406 eval('this.'+attributeName+' = '+projectAttr);
n@453 2407 break;
n@453 2408 case "string":
n@453 2409 eval('this.'+attributeName+' = "'+projectAttr+'"');
n@453 2410 break;
n@324 2411 }
n@324 2412 }
n@453 2413
n@374 2414 };
n@374 2415 this.encode = function(root)
n@374 2416 {
n@503 2417 var AENode = root.createElement("audioelement");
n@503 2418 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
n@503 2419 for (var i=0; i<attributes.length; i++)
n@503 2420 {
n@503 2421 var name = attributes[i].getAttribute("name");
n@503 2422 if (name == undefined) {
n@503 2423 name = attributes[i].getAttribute("ref");
n@503 2424 }
n@503 2425 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
n@503 2426 {
n@503 2427 eval("AENode.setAttribute('"+name+"',this."+name+")");
n@503 2428 }
n@503 2429 }
n@374 2430 return AENode;
n@374 2431 };
n@180 2432 };
n@180 2433 };
n@180 2434 }
n@374 2435
n@182 2436 function Interface(specificationObject) {
n@180 2437 // This handles the bindings between the interface and the audioEngineContext;
n@182 2438 this.specification = specificationObject;
n@182 2439 this.insertPoint = document.getElementById("topLevelBody");
n@180 2440
n@453 2441 this.newPage = function(audioHolderObject,store)
n@375 2442 {
n@500 2443 audioEngineContext.newTestPage(audioHolderObject,store);
n@550 2444 interfaceContext.commentBoxes.deleteCommentBoxes();
n@375 2445 interfaceContext.deleteCommentQuestions();
n@453 2446 loadTest(audioHolderObject,store);
giuliomoro@611 2447 if(audioHolderObject.hidden === true){
giuliomoro@611 2448 // work-around to have zero pages: set the attribute hidden=true and
giuliomoro@611 2449 // it will automatically skip over.
giuliomoro@611 2450 testState.advanceState();
giuliomoro@611 2451 }
n@375 2452 };
n@375 2453
n@182 2454 // Bounded by interface!!
n@182 2455 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
n@182 2456 // For example, APE returns the slider position normalised in a <value> tag.
n@182 2457 this.interfaceObjects = [];
n@182 2458 this.interfaceObject = function(){};
n@182 2459
n@302 2460 this.resizeWindow = function(event)
n@302 2461 {
n@395 2462 popup.resize(event);
n@302 2463 for(var i=0; i<this.commentBoxes.length; i++)
n@302 2464 {this.commentBoxes[i].resize();}
n@302 2465 for(var i=0; i<this.commentQuestions.length; i++)
n@302 2466 {this.commentQuestions[i].resize();}
n@302 2467 try
n@302 2468 {
n@302 2469 resizeWindow(event);
n@302 2470 }
n@302 2471 catch(err)
n@302 2472 {
n@302 2473 console.log("Warning - Interface does not have Resize option");
n@302 2474 console.log(err);
n@302 2475 }
n@302 2476 };
n@302 2477
n@356 2478 this.returnNavigator = function()
n@356 2479 {
n@491 2480 var node = storage.document.createElement("navigator");
n@491 2481 var platform = storage.document.createElement("platform");
n@356 2482 platform.textContent = navigator.platform;
n@491 2483 var vendor = storage.document.createElement("vendor");
n@356 2484 vendor.textContent = navigator.vendor;
n@491 2485 var userAgent = storage.document.createElement("uagent");
n@356 2486 userAgent.textContent = navigator.userAgent;
n@491 2487 var screen = storage.document.createElement("window");
n@491 2488 screen.setAttribute('innerWidth',window.innerWidth);
n@491 2489 screen.setAttribute('innerHeight',window.innerHeight);
n@356 2490 node.appendChild(platform);
n@356 2491 node.appendChild(vendor);
n@356 2492 node.appendChild(userAgent);
n@491 2493 node.appendChild(screen);
n@356 2494 return node;
n@356 2495 };
n@356 2496
n@550 2497 this.commentBoxes = new function() {
n@550 2498 this.boxes = [];
n@550 2499 this.injectPoint = null;
n@550 2500 this.elementCommentBox = function(audioObject) {
n@550 2501 var element = audioObject.specification;
n@550 2502 this.audioObject = audioObject;
n@550 2503 this.id = audioObject.id;
n@550 2504 var audioHolderObject = audioObject.specification.parent;
n@550 2505 // Create document objects to hold the comment boxes
n@550 2506 this.trackComment = document.createElement('div');
n@550 2507 this.trackComment.className = 'comment-div';
n@550 2508 this.trackComment.id = 'comment-div-'+audioObject.id;
n@550 2509 // Create a string next to each comment asking for a comment
n@550 2510 this.trackString = document.createElement('span');
n@550 2511 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
n@550 2512 // Create the HTML5 comment box 'textarea'
n@550 2513 this.trackCommentBox = document.createElement('textarea');
n@550 2514 this.trackCommentBox.rows = '4';
n@550 2515 this.trackCommentBox.cols = '100';
n@550 2516 this.trackCommentBox.name = 'trackComment'+audioObject.id;
n@550 2517 this.trackCommentBox.className = 'trackComment';
n@550 2518 var br = document.createElement('br');
n@550 2519 // Add to the holder.
n@550 2520 this.trackComment.appendChild(this.trackString);
n@550 2521 this.trackComment.appendChild(br);
n@550 2522 this.trackComment.appendChild(this.trackCommentBox);
n@550 2523
n@550 2524 this.exportXMLDOM = function() {
n@550 2525 var root = document.createElement('comment');
n@550 2526 var question = document.createElement('question');
n@550 2527 question.textContent = this.trackString.textContent;
n@550 2528 var response = document.createElement('response');
n@550 2529 response.textContent = this.trackCommentBox.value;
n@550 2530 console.log("Comment frag-"+this.id+": "+response.textContent);
n@550 2531 root.appendChild(question);
n@550 2532 root.appendChild(response);
n@550 2533 return root;
n@550 2534 };
n@550 2535 this.resize = function()
n@550 2536 {
n@550 2537 var boxwidth = (window.innerWidth-100)/2;
n@550 2538 if (boxwidth >= 600)
n@550 2539 {
n@550 2540 boxwidth = 600;
n@550 2541 }
n@550 2542 else if (boxwidth < 400)
n@550 2543 {
n@550 2544 boxwidth = 400;
n@550 2545 }
n@550 2546 this.trackComment.style.width = boxwidth+"px";
n@550 2547 this.trackCommentBox.style.width = boxwidth-6+"px";
n@550 2548 };
n@550 2549 this.resize();
n@550 2550 };
n@550 2551 this.createCommentBox = function(audioObject) {
n@550 2552 var node = new this.elementCommentBox(audioObject);
n@550 2553 this.boxes.push(node);
n@550 2554 audioObject.commentDOM = node;
n@550 2555 return node;
n@550 2556 };
n@550 2557 this.sortCommentBoxes = function() {
n@550 2558 this.boxes.sort(function(a,b){return a.id - b.id;});
n@550 2559 };
n@550 2560
n@550 2561 this.showCommentBoxes = function(inject, sort) {
n@550 2562 this.injectPoint = inject;
n@550 2563 if (sort) {this.sortCommentBoxes();}
n@550 2564 for (var box of this.boxes) {
n@550 2565 inject.appendChild(box.trackComment);
n@550 2566 }
n@550 2567 };
n@550 2568
n@550 2569 this.deleteCommentBoxes = function() {
n@550 2570 if (this.injectPoint != null) {
n@550 2571 for (var box of this.boxes) {
n@550 2572 this.injectPoint.removeChild(box.trackComment);
n@550 2573 }
n@550 2574 this.injectPoint = null;
n@550 2575 }
n@550 2576 this.boxes = [];
n@550 2577 };
n@550 2578 }
n@182 2579
n@193 2580 this.commentQuestions = [];
n@193 2581
n@193 2582 this.commentBox = function(commentQuestion) {
n@193 2583 this.specification = commentQuestion;
n@193 2584 // Create document objects to hold the comment boxes
n@193 2585 this.holder = document.createElement('div');
n@193 2586 this.holder.className = 'comment-div';
n@193 2587 // Create a string next to each comment asking for a comment
n@193 2588 this.string = document.createElement('span');
n@453 2589 this.string.innerHTML = commentQuestion.statement;
n@193 2590 // Create the HTML5 comment box 'textarea'
n@193 2591 this.textArea = document.createElement('textarea');
n@193 2592 this.textArea.rows = '4';
n@193 2593 this.textArea.cols = '100';
n@193 2594 this.textArea.className = 'trackComment';
n@193 2595 var br = document.createElement('br');
n@193 2596 // Add to the holder.
n@193 2597 this.holder.appendChild(this.string);
n@193 2598 this.holder.appendChild(br);
n@193 2599 this.holder.appendChild(this.textArea);
n@193 2600
n@520 2601 this.exportXMLDOM = function(storePoint) {
n@520 2602 var root = storePoint.parent.document.createElement('comment');
n@193 2603 root.id = this.specification.id;
n@193 2604 root.setAttribute('type',this.specification.type);
b@254 2605 console.log("Question: "+this.string.textContent);
b@254 2606 console.log("Response: "+root.textContent);
n@520 2607 var question = storePoint.parent.document.createElement('question');
n@520 2608 question.textContent = this.string.textContent;
n@520 2609 var response = storePoint.parent.document.createElement('response');
n@520 2610 response.textContent = this.textArea.value;
n@520 2611 root.appendChild(question);
n@520 2612 root.appendChild(response);
n@520 2613 storePoint.XMLDOM.appendChild(root);
n@193 2614 return root;
n@193 2615 };
n@302 2616 this.resize = function()
n@302 2617 {
n@302 2618 var boxwidth = (window.innerWidth-100)/2;
n@302 2619 if (boxwidth >= 600)
n@302 2620 {
n@302 2621 boxwidth = 600;
n@302 2622 }
n@302 2623 else if (boxwidth < 400)
n@302 2624 {
n@302 2625 boxwidth = 400;
n@302 2626 }
n@302 2627 this.holder.style.width = boxwidth+"px";
n@302 2628 this.textArea.style.width = boxwidth-6+"px";
n@302 2629 };
n@302 2630 this.resize();
n@193 2631 };
n@193 2632
n@193 2633 this.radioBox = function(commentQuestion) {
n@193 2634 this.specification = commentQuestion;
n@193 2635 // Create document objects to hold the comment boxes
n@193 2636 this.holder = document.createElement('div');
n@193 2637 this.holder.className = 'comment-div';
n@193 2638 // Create a string next to each comment asking for a comment
n@193 2639 this.string = document.createElement('span');
n@193 2640 this.string.innerHTML = commentQuestion.statement;
n@193 2641 var br = document.createElement('br');
n@193 2642 // Add to the holder.
n@193 2643 this.holder.appendChild(this.string);
n@193 2644 this.holder.appendChild(br);
n@193 2645 this.options = [];
n@193 2646 this.inputs = document.createElement('div');
n@193 2647 this.span = document.createElement('div');
n@193 2648 this.inputs.align = 'center';
n@193 2649 this.inputs.style.marginLeft = '12px';
n@193 2650 this.span.style.marginLeft = '12px';
n@193 2651 this.span.align = 'center';
n@193 2652 this.span.style.marginTop = '15px';
n@193 2653
n@193 2654 var optCount = commentQuestion.options.length;
n@453 2655 for (var optNode of commentQuestion.options)
n@193 2656 {
n@193 2657 var div = document.createElement('div');
n@301 2658 div.style.width = '80px';
n@193 2659 div.style.float = 'left';
n@193 2660 var input = document.createElement('input');
n@193 2661 input.type = 'radio';
n@193 2662 input.name = commentQuestion.id;
n@453 2663 input.setAttribute('setvalue',optNode.name);
n@193 2664 input.className = 'comment-radio';
n@193 2665 div.appendChild(input);
n@193 2666 this.inputs.appendChild(div);
n@193 2667
n@193 2668
n@193 2669 div = document.createElement('div');
n@301 2670 div.style.width = '80px';
n@193 2671 div.style.float = 'left';
n@193 2672 div.align = 'center';
n@193 2673 var span = document.createElement('span');
n@453 2674 span.textContent = optNode.text;
n@193 2675 span.className = 'comment-radio-span';
n@193 2676 div.appendChild(span);
n@193 2677 this.span.appendChild(div);
n@193 2678 this.options.push(input);
n@193 2679 }
n@193 2680 this.holder.appendChild(this.span);
n@193 2681 this.holder.appendChild(this.inputs);
n@193 2682
n@520 2683 this.exportXMLDOM = function(storePoint) {
n@520 2684 var root = storePoint.parent.document.createElement('comment');
n@193 2685 root.id = this.specification.id;
n@193 2686 root.setAttribute('type',this.specification.type);
n@193 2687 var question = document.createElement('question');
n@193 2688 question.textContent = this.string.textContent;
n@193 2689 var response = document.createElement('response');
n@193 2690 var i=0;
n@193 2691 while(this.options[i].checked == false) {
n@193 2692 i++;
n@193 2693 if (i >= this.options.length) {
n@193 2694 break;
n@193 2695 }
n@193 2696 }
n@193 2697 if (i >= this.options.length) {
n@193 2698 response.textContent = 'null';
n@193 2699 } else {
n@193 2700 response.textContent = this.options[i].getAttribute('setvalue');
n@193 2701 response.setAttribute('number',i);
n@193 2702 }
n@195 2703 console.log('Comment: '+question.textContent);
n@195 2704 console.log('Response: '+response.textContent);
n@193 2705 root.appendChild(question);
n@193 2706 root.appendChild(response);
n@520 2707 storePoint.XMLDOM.appendChild(root);
n@193 2708 return root;
n@193 2709 };
n@302 2710 this.resize = function()
n@302 2711 {
n@302 2712 var boxwidth = (window.innerWidth-100)/2;
n@302 2713 if (boxwidth >= 600)
n@302 2714 {
n@302 2715 boxwidth = 600;
n@302 2716 }
n@302 2717 else if (boxwidth < 400)
n@302 2718 {
n@302 2719 boxwidth = 400;
n@302 2720 }
n@302 2721 this.holder.style.width = boxwidth+"px";
n@302 2722 var text = this.holder.children[2];
n@302 2723 var options = this.holder.children[3];
n@302 2724 var optCount = options.children.length;
n@302 2725 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@302 2726 var options = options.firstChild;
n@302 2727 var text = text.firstChild;
n@302 2728 options.style.marginRight = spanMargin;
n@302 2729 options.style.marginLeft = spanMargin;
n@302 2730 text.style.marginRight = spanMargin;
n@302 2731 text.style.marginLeft = spanMargin;
n@302 2732 while(options.nextSibling != undefined)
n@302 2733 {
n@302 2734 options = options.nextSibling;
n@302 2735 text = text.nextSibling;
n@302 2736 options.style.marginRight = spanMargin;
n@302 2737 options.style.marginLeft = spanMargin;
n@302 2738 text.style.marginRight = spanMargin;
n@302 2739 text.style.marginLeft = spanMargin;
n@302 2740 }
n@302 2741 };
n@302 2742 this.resize();
n@193 2743 };
n@193 2744
n@195 2745 this.checkboxBox = function(commentQuestion) {
n@195 2746 this.specification = commentQuestion;
n@195 2747 // Create document objects to hold the comment boxes
n@195 2748 this.holder = document.createElement('div');
n@195 2749 this.holder.className = 'comment-div';
n@195 2750 // Create a string next to each comment asking for a comment
n@195 2751 this.string = document.createElement('span');
n@195 2752 this.string.innerHTML = commentQuestion.statement;
n@195 2753 var br = document.createElement('br');
n@195 2754 // Add to the holder.
n@195 2755 this.holder.appendChild(this.string);
n@195 2756 this.holder.appendChild(br);
n@195 2757 this.options = [];
n@195 2758 this.inputs = document.createElement('div');
n@195 2759 this.span = document.createElement('div');
n@195 2760 this.inputs.align = 'center';
n@195 2761 this.inputs.style.marginLeft = '12px';
n@195 2762 this.span.style.marginLeft = '12px';
n@195 2763 this.span.align = 'center';
n@195 2764 this.span.style.marginTop = '15px';
n@195 2765
n@195 2766 var optCount = commentQuestion.options.length;
n@195 2767 for (var i=0; i<optCount; i++)
n@195 2768 {
n@195 2769 var div = document.createElement('div');
n@301 2770 div.style.width = '80px';
n@195 2771 div.style.float = 'left';
n@195 2772 var input = document.createElement('input');
n@195 2773 input.type = 'checkbox';
n@195 2774 input.name = commentQuestion.id;
n@195 2775 input.setAttribute('setvalue',commentQuestion.options[i].name);
n@195 2776 input.className = 'comment-radio';
n@195 2777 div.appendChild(input);
n@195 2778 this.inputs.appendChild(div);
n@195 2779
n@195 2780
n@195 2781 div = document.createElement('div');
n@301 2782 div.style.width = '80px';
n@195 2783 div.style.float = 'left';
n@195 2784 div.align = 'center';
n@195 2785 var span = document.createElement('span');
n@195 2786 span.textContent = commentQuestion.options[i].text;
n@195 2787 span.className = 'comment-radio-span';
n@195 2788 div.appendChild(span);
n@195 2789 this.span.appendChild(div);
n@195 2790 this.options.push(input);
n@195 2791 }
n@195 2792 this.holder.appendChild(this.span);
n@195 2793 this.holder.appendChild(this.inputs);
n@195 2794
n@520 2795 this.exportXMLDOM = function(storePoint) {
n@520 2796 var root = storePoint.parent.document.createElement('comment');
n@195 2797 root.id = this.specification.id;
n@195 2798 root.setAttribute('type',this.specification.type);
n@195 2799 var question = document.createElement('question');
n@195 2800 question.textContent = this.string.textContent;
n@195 2801 root.appendChild(question);
n@195 2802 console.log('Comment: '+question.textContent);
n@195 2803 for (var i=0; i<this.options.length; i++) {
n@195 2804 var response = document.createElement('response');
n@195 2805 response.textContent = this.options[i].checked;
n@195 2806 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
n@195 2807 root.appendChild(response);
n@195 2808 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
n@195 2809 }
n@520 2810 storePoint.XMLDOM.appendChild(root);
n@195 2811 return root;
n@195 2812 };
n@302 2813 this.resize = function()
n@302 2814 {
n@302 2815 var boxwidth = (window.innerWidth-100)/2;
n@302 2816 if (boxwidth >= 600)
n@302 2817 {
n@302 2818 boxwidth = 600;
n@302 2819 }
n@302 2820 else if (boxwidth < 400)
n@302 2821 {
n@302 2822 boxwidth = 400;
n@302 2823 }
n@302 2824 this.holder.style.width = boxwidth+"px";
n@302 2825 var text = this.holder.children[2];
n@302 2826 var options = this.holder.children[3];
n@302 2827 var optCount = options.children.length;
n@302 2828 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
n@302 2829 var options = options.firstChild;
n@302 2830 var text = text.firstChild;
n@302 2831 options.style.marginRight = spanMargin;
n@302 2832 options.style.marginLeft = spanMargin;
n@302 2833 text.style.marginRight = spanMargin;
n@302 2834 text.style.marginLeft = spanMargin;
n@302 2835 while(options.nextSibling != undefined)
n@302 2836 {
n@302 2837 options = options.nextSibling;
n@302 2838 text = text.nextSibling;
n@302 2839 options.style.marginRight = spanMargin;
n@302 2840 options.style.marginLeft = spanMargin;
n@302 2841 text.style.marginRight = spanMargin;
n@302 2842 text.style.marginLeft = spanMargin;
n@302 2843 }
n@302 2844 };
n@302 2845 this.resize();
n@195 2846 };
nicholas@211 2847
n@193 2848 this.createCommentQuestion = function(element) {
n@193 2849 var node;
n@453 2850 if (element.type == 'question') {
n@193 2851 node = new this.commentBox(element);
n@193 2852 } else if (element.type == 'radio') {
n@193 2853 node = new this.radioBox(element);
n@195 2854 } else if (element.type == 'checkbox') {
n@195 2855 node = new this.checkboxBox(element);
n@193 2856 }
n@193 2857 this.commentQuestions.push(node);
n@193 2858 return node;
n@193 2859 };
n@201 2860
nicholas@237 2861 this.deleteCommentQuestions = function()
nicholas@237 2862 {
nicholas@237 2863 this.commentQuestions = [];
nicholas@237 2864 };
nicholas@237 2865
n@201 2866 this.playhead = new function()
n@201 2867 {
n@201 2868 this.object = document.createElement('div');
n@201 2869 this.object.className = 'playhead';
n@201 2870 this.object.align = 'left';
n@201 2871 var curTime = document.createElement('div');
n@201 2872 curTime.style.width = '50px';
n@201 2873 this.curTimeSpan = document.createElement('span');
n@201 2874 this.curTimeSpan.textContent = '00:00';
n@201 2875 curTime.appendChild(this.curTimeSpan);
n@201 2876 this.object.appendChild(curTime);
n@201 2877 this.scrubberTrack = document.createElement('div');
n@201 2878 this.scrubberTrack.className = 'playhead-scrub-track';
n@201 2879
n@201 2880 this.scrubberHead = document.createElement('div');
n@201 2881 this.scrubberHead.id = 'playhead-scrubber';
n@201 2882 this.scrubberTrack.appendChild(this.scrubberHead);
n@201 2883 this.object.appendChild(this.scrubberTrack);
n@201 2884
n@201 2885 this.timePerPixel = 0;
n@201 2886 this.maxTime = 0;
n@201 2887
n@204 2888 this.playbackObject;
n@204 2889
n@204 2890 this.setTimePerPixel = function(audioObject) {
n@201 2891 //maxTime must be in seconds
n@204 2892 this.playbackObject = audioObject;
n@379 2893 this.maxTime = audioObject.buffer.buffer.duration;
n@201 2894 var width = 490; //500 - 10, 5 each side of the tracker head
n@204 2895 this.timePerPixel = this.maxTime/490;
n@204 2896 if (this.maxTime < 60) {
n@201 2897 this.curTimeSpan.textContent = '0.00';
n@201 2898 } else {
n@201 2899 this.curTimeSpan.textContent = '00:00';
n@201 2900 }
n@201 2901 };
n@201 2902
n@204 2903 this.update = function() {
n@201 2904 // Update the playhead position, startPlay must be called
n@201 2905 if (this.timePerPixel > 0) {
n@204 2906 var time = this.playbackObject.getCurrentPosition();
n@498 2907 if (time > 0 && time < this.maxTime) {
nicholas@267 2908 var width = 490;
nicholas@267 2909 var pix = Math.floor(time/this.timePerPixel);
nicholas@267 2910 this.scrubberHead.style.left = pix+'px';
nicholas@267 2911 if (this.maxTime > 60.0) {
nicholas@267 2912 var secs = time%60;
nicholas@267 2913 var mins = Math.floor((time-secs)/60);
nicholas@267 2914 secs = secs.toString();
nicholas@267 2915 secs = secs.substr(0,2);
nicholas@267 2916 mins = mins.toString();
nicholas@267 2917 this.curTimeSpan.textContent = mins+':'+secs;
nicholas@267 2918 } else {
nicholas@267 2919 time = time.toString();
nicholas@267 2920 this.curTimeSpan.textContent = time.substr(0,4);
nicholas@267 2921 }
n@201 2922 } else {
nicholas@267 2923 this.scrubberHead.style.left = '0px';
nicholas@267 2924 if (this.maxTime < 60) {
nicholas@267 2925 this.curTimeSpan.textContent = '0.00';
nicholas@267 2926 } else {
nicholas@267 2927 this.curTimeSpan.textContent = '00:00';
nicholas@267 2928 }
n@201 2929 }
n@201 2930 }
n@201 2931 };
n@204 2932
n@204 2933 this.interval = undefined;
n@204 2934
n@204 2935 this.start = function() {
n@204 2936 if (this.playbackObject != undefined && this.interval == undefined) {
nicholas@267 2937 if (this.maxTime < 60) {
nicholas@267 2938 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
nicholas@267 2939 } else {
nicholas@267 2940 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
nicholas@267 2941 }
n@204 2942 }
n@204 2943 };
n@204 2944 this.stop = function() {
n@204 2945 clearInterval(this.interval);
n@204 2946 this.interval = undefined;
n@527 2947 this.scrubberHead.style.left = '0px';
nicholas@267 2948 if (this.maxTime < 60) {
nicholas@267 2949 this.curTimeSpan.textContent = '0.00';
nicholas@267 2950 } else {
nicholas@267 2951 this.curTimeSpan.textContent = '00:00';
nicholas@267 2952 }
n@204 2953 };
n@201 2954 };
n@483 2955
n@483 2956 this.volume = new function()
n@483 2957 {
n@483 2958 // An in-built volume module which can be viewed on page
n@483 2959 // Includes trackers on page-by-page data
n@483 2960 // Volume does NOT reset to 0dB on each page load
n@483 2961 this.valueLin = 1.0;
n@483 2962 this.valueDB = 0.0;
n@483 2963 this.object = document.createElement('div');
n@483 2964 this.object.id = 'master-volume-holder';
n@483 2965 this.slider = document.createElement('input');
n@483 2966 this.slider.id = 'master-volume-control';
n@483 2967 this.slider.type = 'range';
n@483 2968 this.valueText = document.createElement('span');
n@483 2969 this.valueText.id = 'master-volume-feedback';
n@483 2970 this.valueText.textContent = '0dB';
n@483 2971
n@483 2972 this.slider.min = -60;
n@483 2973 this.slider.max = 12;
n@483 2974 this.slider.value = 0;
n@483 2975 this.slider.step = 1;
n@483 2976 this.slider.onmousemove = function(event)
n@483 2977 {
n@483 2978 interfaceContext.volume.valueDB = event.currentTarget.value;
n@483 2979 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
n@483 2980 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
n@483 2981 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
n@483 2982 }
n@483 2983 this.slider.onmouseup = function(event)
n@483 2984 {
n@526 2985 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
n@483 2986 if (storePoint.length == 0)
n@483 2987 {
n@483 2988 storePoint = storage.document.createElement('metricresult');
n@483 2989 storePoint.setAttribute('name','volumeTracker');
n@526 2990 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
n@483 2991 }
n@483 2992 else {
n@483 2993 storePoint = storePoint[0];
n@483 2994 }
n@483 2995 var node = storage.document.createElement('movement');
n@483 2996 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
n@483 2997 node.setAttribute('volume',interfaceContext.volume.valueDB);
n@483 2998 node.setAttribute('format','dBFS');
n@483 2999 storePoint.appendChild(node);
n@483 3000 }
n@483 3001
n@484 3002 var title = document.createElement('div');
n@484 3003 title.innerHTML = '<span>Master Volume Control</span>';
n@484 3004 title.style.fontSize = '0.75em';
n@484 3005 title.style.width = "100%";
n@484 3006 title.align = 'center';
n@484 3007 this.object.appendChild(title);
n@484 3008
n@483 3009 this.object.appendChild(this.slider);
n@483 3010 this.object.appendChild(this.valueText);
n@483 3011 }
nicholas@235 3012 // Global Checkers
nicholas@235 3013 // These functions will help enforce the checkers
nicholas@235 3014 this.checkHiddenAnchor = function()
nicholas@235 3015 {
n@453 3016 for (var ao of audioEngineContext.audioObjects)
nicholas@235 3017 {
n@453 3018 if (ao.specification.type == "anchor")
nicholas@235 3019 {
n@454 3020 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
n@453 3021 // Anchor is not set below
n@453 3022 console.log('Anchor node not below marker value');
n@453 3023 alert('Please keep listening');
n@498 3024 this.storeErrorNode('Anchor node not below marker value');
n@453 3025 return false;
n@453 3026 }
nicholas@235 3027 }
nicholas@235 3028 }
nicholas@235 3029 return true;
nicholas@235 3030 };
nicholas@235 3031
nicholas@235 3032 this.checkHiddenReference = function()
nicholas@235 3033 {
n@453 3034 for (var ao of audioEngineContext.audioObjects)
nicholas@235 3035 {
n@453 3036 if (ao.specification.type == "reference")
nicholas@235 3037 {
n@454 3038 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
n@453 3039 // Anchor is not set below
n@498 3040 console.log('Reference node not above marker value');
n@498 3041 this.storeErrorNode('Reference node not above marker value');
n@453 3042 alert('Please keep listening');
n@453 3043 return false;
n@453 3044 }
nicholas@235 3045 }
nicholas@235 3046 }
nicholas@235 3047 return true;
nicholas@235 3048 };
n@366 3049
n@366 3050 this.checkFragmentsFullyPlayed = function ()
n@366 3051 {
n@366 3052 // Checks the entire file has been played back
n@366 3053 // NOTE ! This will return true IF playback is Looped!!!
n@366 3054 if (audioEngineContext.loopPlayback)
n@366 3055 {
n@366 3056 console.log("WARNING - Looped source: Cannot check fragments are fully played");
n@366 3057 return true;
n@366 3058 }
n@366 3059 var check_pass = true;
n@366 3060 var error_obj = [];
n@366 3061 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
n@366 3062 {
n@366 3063 var object = audioEngineContext.audioObjects[i];
nicholas@415 3064 var time = object.buffer.buffer.duration;
n@366 3065 var metric = object.metric;
n@366 3066 var passed = false;
n@366 3067 for (var j=0; j<metric.listenTracker.length; j++)
n@366 3068 {
n@366 3069 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
n@366 3070 var start_time = Number(bt[0].getAttribute('start'));
n@366 3071 var stop_time = Number(bt[0].getAttribute('stop'));
n@366 3072 var delta = stop_time - start_time;
n@366 3073 if (delta >= time)
n@366 3074 {
n@366 3075 passed = true;
n@366 3076 break;
n@366 3077 }
n@366 3078 }
n@366 3079 if (passed == false)
n@366 3080 {
n@366 3081 check_pass = false;
n@598 3082 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
n@598 3083 error_obj.push(object.interfaceDOM.getPresentedId());
n@366 3084 }
n@366 3085 }
n@366 3086 if (check_pass == false)
n@366 3087 {
nicholas@415 3088 var str_start = "You have not completely listened to fragments ";
n@366 3089 for (var i=0; i<error_obj.length; i++)
n@366 3090 {
n@366 3091 str_start += error_obj[i];
n@366 3092 if (i != error_obj.length-1)
n@366 3093 {
n@366 3094 str_start += ', ';
n@366 3095 }
n@366 3096 }
n@366 3097 str_start += ". Please keep listening";
n@366 3098 console.log("[ALERT]: "+str_start);
n@498 3099 this.storeErrorNode("[ALERT]: "+str_start);
n@366 3100 alert(str_start);
n@366 3101 }
n@366 3102 };
nicholas@421 3103 this.checkAllMoved = function()
nicholas@421 3104 {
nicholas@421 3105 var str = "You have not moved ";
nicholas@421 3106 var failed = [];
n@469 3107 for (var ao of audioEngineContext.audioObjects)
nicholas@421 3108 {
n@469 3109 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
nicholas@421 3110 {
n@469 3111 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@421 3112 }
nicholas@421 3113 }
nicholas@421 3114 if (failed.length == 0)
nicholas@421 3115 {
nicholas@421 3116 return true;
nicholas@421 3117 } else if (failed.length == 1)
nicholas@421 3118 {
nicholas@421 3119 str += 'track '+failed[0];
nicholas@421 3120 } else {
nicholas@421 3121 str += 'tracks ';
nicholas@421 3122 for (var i=0; i<failed.length-1; i++)
nicholas@421 3123 {
nicholas@421 3124 str += failed[i]+', ';
nicholas@421 3125 }
nicholas@421 3126 str += 'and '+failed[i];
nicholas@421 3127 }
nicholas@421 3128 str +='.';
nicholas@421 3129 alert(str);
nicholas@421 3130 console.log(str);
n@498 3131 this.storeErrorNode(str);
nicholas@421 3132 return false;
nicholas@421 3133 };
nicholas@421 3134 this.checkAllPlayed = function()
nicholas@421 3135 {
nicholas@421 3136 var str = "You have not played ";
nicholas@421 3137 var failed = [];
n@469 3138 for (var ao of audioEngineContext.audioObjects)
nicholas@421 3139 {
n@469 3140 if(ao.metric.wasListenedTo == false)
nicholas@421 3141 {
n@469 3142 failed.push(ao.interfaceDOM.getPresentedId());
nicholas@421 3143 }
nicholas@421 3144 }
nicholas@421 3145 if (failed.length == 0)
nicholas@421 3146 {
nicholas@421 3147 return true;
nicholas@421 3148 } else if (failed.length == 1)
nicholas@421 3149 {
nicholas@421 3150 str += 'track '+failed[0];
nicholas@421 3151 } else {
nicholas@421 3152 str += 'tracks ';
nicholas@421 3153 for (var i=0; i<failed.length-1; i++)
nicholas@421 3154 {
nicholas@421 3155 str += failed[i]+', ';
nicholas@421 3156 }
nicholas@421 3157 str += 'and '+failed[i];
nicholas@421 3158 }
nicholas@421 3159 str +='.';
nicholas@421 3160 alert(str);
nicholas@421 3161 console.log(str);
n@498 3162 this.storeErrorNode(str);
nicholas@421 3163 return false;
nicholas@421 3164 };
n@498 3165
n@498 3166 this.storeErrorNode = function(errorMessage)
n@498 3167 {
n@498 3168 var time = audioEngineContext.timer.getTestTime();
n@498 3169 var node = storage.document.createElement('error');
n@498 3170 node.setAttribute('time',time);
n@498 3171 node.textContent = errorMessage;
n@498 3172 testState.currentStore.XMLDOM.appendChild(node);
n@498 3173 };
n@453 3174 }
n@453 3175
n@453 3176 function Storage()
n@453 3177 {
n@453 3178 // Holds results in XML format until ready for collection
n@453 3179 this.globalPreTest = null;
n@453 3180 this.globalPostTest = null;
n@453 3181 this.testPages = [];
n@602 3182 this.document = null;
n@602 3183 this.root = null;
n@453 3184 this.state = 0;
n@453 3185
n@602 3186 this.initialise = function(existingStore)
n@453 3187 {
n@602 3188 if (existingStore == undefined) {
n@584 3189 // We need to get the sessionKey
n@584 3190 this.SessionKey.generateKey();
n@602 3191 this.document = document.implementation.createDocument(null,"waetresult");
n@602 3192 this.root = this.document.childNodes[0];
n@589 3193 var projectDocument = specification.projectXML;
n@589 3194 projectDocument.setAttribute('file-name',url);
n@589 3195 this.root.appendChild(projectDocument);
n@589 3196 this.root.appendChild(returnDateNode());
n@589 3197 this.root.appendChild(interfaceContext.returnNavigator());
n@584 3198 } else {
n@602 3199 this.document = existingStore;
n@602 3200 this.root = existingStore.children[0];
n@602 3201 this.SessionKey.key = this.root.getAttribute("key");
n@584 3202 }
n@589 3203 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
n@602 3204 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
n@453 3205 };
n@584 3206
n@584 3207 this.SessionKey = {
n@584 3208 key: null,
n@584 3209 request: new XMLHttpRequest(),
n@584 3210 parent: this,
n@584 3211 handleEvent: function() {
n@584 3212 var parse = new DOMParser();
n@584 3213 var xml = parse.parseFromString(this.request.response,"text/xml");
n@584 3214 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
n@584 3215 this.key = xml.getAllElementsByTagName("key")[0].textContent;
n@589 3216 this.parent.root.setAttribute("key",this.key);
n@589 3217 this.parent.root.setAttribute("state","empty");
n@584 3218 } else {
n@584 3219 this.generateKey();
n@584 3220 }
n@584 3221 },
n@584 3222 generateKey: function() {
n@584 3223 var temp_key = randomString(32);
n@584 3224 this.request.open("GET","keygen.php?key="+temp_key,true);
n@584 3225 this.request.addEventListener("load",this);
n@584 3226 this.request.send();
n@587 3227 },
n@589 3228 update: function() {
n@589 3229 this.parent.root.setAttribute("state","update");
n@589 3230 var xmlhttp = new XMLHttpRequest();
n@589 3231 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
n@589 3232 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
n@589 3233 xmlhttp.onerror = function(){
n@589 3234 console.log('Error updating file to server!');
n@589 3235 };
n@589 3236 var hold = document.createElement("div");
n@589 3237 var clone = this.parent.root.cloneNode(true);
n@589 3238 hold.appendChild(clone);
n@589 3239 xmlhttp.onload = function() {
n@589 3240 if (this.status >= 300) {
n@589 3241 console.log("WARNING - Could not update at this time");
n@589 3242 } else {
n@589 3243 var parser = new DOMParser();
n@589 3244 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
n@589 3245 var response = xmlDoc.getElementsByTagName('response')[0];
n@589 3246 if (response.getAttribute("state") == "OK") {
n@589 3247 var file = response.getElementsByTagName("file")[0];
n@589 3248 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
n@589 3249 } else {
n@589 3250 var message = response.getElementsByTagName("message");
n@589 3251 console.log("Intermediate save: Error! "+message.textContent);
n@589 3252 }
n@587 3253 }
n@587 3254 }
n@589 3255 xmlhttp.send([hold.innerHTML]);
n@584 3256 }
n@584 3257 }
n@453 3258
n@453 3259 this.createTestPageStore = function(specification)
n@453 3260 {
n@453 3261 var store = new this.pageNode(this,specification);
n@453 3262 this.testPages.push(store);
n@453 3263 return this.testPages[this.testPages.length-1];
n@453 3264 };
n@453 3265
n@453 3266 this.surveyNode = function(parent,root,specification)
n@453 3267 {
n@453 3268 this.specification = specification;
n@453 3269 this.parent = parent;
n@602 3270 this.state = "empty";
n@453 3271 this.XMLDOM = this.parent.document.createElement('survey');
n@453 3272 this.XMLDOM.setAttribute('location',this.specification.location);
n@602 3273 this.XMLDOM.setAttribute("state",this.state);
n@453 3274 for (var optNode of this.specification.options)
n@453 3275 {
n@453 3276 if (optNode.type != 'statement')
n@453 3277 {
n@453 3278 var node = this.parent.document.createElement('surveyresult');
n@602 3279 node.setAttribute("ref",optNode.id);
n@453 3280 node.setAttribute('type',optNode.type);
n@453 3281 this.XMLDOM.appendChild(node);
n@453 3282 }
n@453 3283 }
n@453 3284 root.appendChild(this.XMLDOM);
n@453 3285
n@453 3286 this.postResult = function(node)
n@453 3287 {
n@453 3288 // From popup: node is the popupOption node containing both spec. and results
n@453 3289 // ID is the position
n@453 3290 if (node.specification.type == 'statement'){return;}
n@602 3291 var surveyresult = this.XMLDOM.children[0];
n@602 3292 while(surveyresult != null) {
n@602 3293 if (surveyresult.getAttribute("ref") == node.specification.id)
n@602 3294 {
n@602 3295 break;
n@602 3296 }
n@602 3297 surveyresult = surveyresult.nextElementSibling;
n@602 3298 }
n@453 3299 switch(node.specification.type)
n@453 3300 {
n@453 3301 case "number":
n@453 3302 case "question":
n@453 3303 var child = this.parent.document.createElement('response');
n@453 3304 child.textContent = node.response;
n@453 3305 surveyresult.appendChild(child);
n@453 3306 break;
n@453 3307 case "radio":
n@453 3308 var child = this.parent.document.createElement('response');
n@453 3309 child.setAttribute('name',node.response.name);
n@453 3310 child.textContent = node.response.text;
n@453 3311 surveyresult.appendChild(child);
n@453 3312 break;
n@453 3313 case "checkbox":
n@453 3314 for (var i=0; i<node.response.length; i++)
n@453 3315 {
n@453 3316 var checkNode = this.parent.document.createElement('response');
n@476 3317 checkNode.setAttribute('name',node.response[i].name);
n@476 3318 checkNode.setAttribute('checked',node.response[i].checked);
n@455 3319 surveyresult.appendChild(checkNode);
n@453 3320 }
n@453 3321 break;
n@453 3322 }
n@453 3323 };
n@602 3324 this.complete = function() {
n@602 3325 this.state = "complete";
n@602 3326 this.XMLDOM.setAttribute("state",this.state);
n@602 3327 }
n@453 3328 };
n@453 3329
n@453 3330 this.pageNode = function(parent,specification)
n@453 3331 {
n@453 3332 // Create one store per test page
n@453 3333 this.specification = specification;
n@453 3334 this.parent = parent;
n@602 3335 this.state = "empty";
n@453 3336 this.XMLDOM = this.parent.document.createElement('page');
n@602 3337 this.XMLDOM.setAttribute('ref',specification.id);
n@453 3338 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
n@602 3339 this.XMLDOM.setAttribute("state",this.state);
n@474 3340 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
n@474 3341 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
n@453 3342
n@453 3343 // Add any page metrics
n@453 3344 var page_metric = this.parent.document.createElement('metric');
n@453 3345 this.XMLDOM.appendChild(page_metric);
n@453 3346
n@453 3347 // Add the audioelement
n@453 3348 for (var element of this.specification.audioElements)
n@453 3349 {
n@453 3350 var aeNode = this.parent.document.createElement('audioelement');
n@602 3351 aeNode.setAttribute('ref',element.id);
n@602 3352 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
n@453 3353 aeNode.setAttribute('type',element.type);
n@453 3354 aeNode.setAttribute('url', element.url);
n@453 3355 aeNode.setAttribute('gain', element.gain);
n@453 3356 if (element.type == 'anchor' || element.type == 'reference')
n@453 3357 {
n@453 3358 if (element.marker > 0)
n@453 3359 {
n@453 3360 aeNode.setAttribute('marker',element.marker);
n@453 3361 }
n@453 3362 }
n@453 3363 var ae_metric = this.parent.document.createElement('metric');
n@453 3364 aeNode.appendChild(ae_metric);
n@453 3365 this.XMLDOM.appendChild(aeNode);
n@453 3366 }
n@453 3367
n@453 3368 this.parent.root.appendChild(this.XMLDOM);
n@602 3369
n@602 3370 this.complete = function() {
n@602 3371 this.state = "complete";
n@602 3372 this.XMLDOM.setAttribute("state","complete");
n@602 3373 }
n@453 3374 };
n@589 3375 this.update = function() {
n@589 3376 this.SessionKey.update();
n@589 3377 }
n@453 3378 this.finish = function()
n@453 3379 {
n@453 3380 if (this.state == 0)
n@453 3381 {
n@589 3382 this.update();
n@453 3383 }
n@453 3384 this.state = 1;
n@453 3385 return this.root;
n@453 3386 };
n@453 3387 }