annotate core.js @ 606:65c7223bd817 Dev_main

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