annotate core.js @ 1087:8bf6395cefc1

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