annotate core.js @ 1241:879b0b20b20c

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