annotate core.js @ 1491:5fd7e8f4313f

Add interfaces section to WAC paper
author Dave <djmoffat@users.noreply.github.com>
date Mon, 12 Oct 2015 12:22:42 +0100
parents 0b095f66de65
children 6fb0b21d6f85
rev   line source
djmoffat@718 1 /**
djmoffat@718 2 * core.js
djmoffat@718 3 *
djmoffat@718 4 * Main script to run, calls all other core functions and manages loading/store to backend.
djmoffat@718 5 * Also contains all global variables.
djmoffat@718 6 */
djmoffat@718 7
djmoffat@718 8 /* create the web audio API context and store in audioContext*/
djmoffat@718 9 var audioContext; // Hold the browser web audio API
djmoffat@718 10 var projectXML; // Hold the parsed setup XML
djmoffat@718 11 var specification;
djmoffat@718 12 var interfaceContext;
djmoffat@718 13 var popup; // Hold the interfacePopup object
djmoffat@718 14 var testState;
djmoffat@718 15 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
djmoffat@718 16 var audioEngineContext; // The custome AudioEngine object
djmoffat@718 17 var projectReturn; // Hold the URL for the return
djmoffat@718 18
djmoffat@718 19
djmoffat@718 20 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
djmoffat@718 21 AudioBufferSourceNode.prototype.owner = undefined;
djmoffat@718 22
djmoffat@718 23 window.onload = function() {
djmoffat@718 24 // Function called once the browser has loaded all files.
djmoffat@718 25 // This should perform any initial commands such as structure / loading documents
djmoffat@718 26
djmoffat@718 27 // Create a web audio API context
djmoffat@718 28 // Fixed for cross-browser support
djmoffat@718 29 var AudioContext = window.AudioContext || window.webkitAudioContext;
djmoffat@718 30 audioContext = new AudioContext;
djmoffat@718 31
djmoffat@718 32 // Create test state
djmoffat@718 33 testState = new stateMachine();
djmoffat@718 34
djmoffat@718 35 // Create the audio engine object
djmoffat@718 36 audioEngineContext = new AudioEngine();
djmoffat@718 37
djmoffat@718 38 // Create the popup interface object
djmoffat@718 39 popup = new interfacePopup();
djmoffat@718 40
djmoffat@718 41 // Create the specification object
djmoffat@718 42 specification = new Specification();
djmoffat@718 43
djmoffat@718 44 // Create the interface object
djmoffat@718 45 interfaceContext = new Interface(specification);
djmoffat@718 46 };
djmoffat@718 47
djmoffat@718 48 function interfacePopup() {
djmoffat@718 49 // Creates an object to manage the popup
djmoffat@718 50 this.popup = null;
djmoffat@718 51 this.popupContent = null;
djmoffat@718 52 this.popupTitle = null;
djmoffat@718 53 this.popupResponse = null;
djmoffat@718 54 this.buttonProceed = null;
djmoffat@718 55 this.buttonPrevious = null;
djmoffat@718 56 this.popupOptions = null;
djmoffat@718 57 this.currentIndex = null;
djmoffat@718 58 this.responses = null;
djmoffat@718 59
djmoffat@718 60 this.createPopup = function(){
djmoffat@718 61 // Create popup window interface
djmoffat@718 62 var insertPoint = document.getElementById("topLevelBody");
djmoffat@718 63 var blank = document.createElement('div');
djmoffat@718 64 blank.className = 'testHalt';
djmoffat@718 65
djmoffat@718 66 this.popup = document.createElement('div');
djmoffat@718 67 this.popup.id = 'popupHolder';
djmoffat@718 68 this.popup.className = 'popupHolder';
djmoffat@718 69 this.popup.style.position = 'absolute';
djmoffat@718 70 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
djmoffat@718 71 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
djmoffat@718 72
djmoffat@718 73 this.popupContent = document.createElement('div');
djmoffat@718 74 this.popupContent.id = 'popupContent';
djmoffat@718 75 this.popupContent.style.marginTop = '20px';
djmoffat@718 76 this.popupContent.align = 'center';
djmoffat@718 77 this.popup.appendChild(this.popupContent);
djmoffat@718 78
djmoffat@718 79 var titleHolder = document.createElement('div');
djmoffat@718 80 titleHolder.id = 'popupTitleHolder';
djmoffat@718 81 titleHolder.style.width = 'inherit';
djmoffat@718 82 titleHolder.style.height = '25px';
djmoffat@718 83 titleHolder.style.marginBottom = '5px';
djmoffat@718 84
djmoffat@718 85 this.popupTitle = document.createElement('span');
djmoffat@718 86 this.popupTitle.id = 'popupTitle';
djmoffat@718 87 titleHolder.appendChild(this.popupTitle);
djmoffat@718 88 this.popupContent.appendChild(titleHolder);
djmoffat@718 89
djmoffat@718 90 this.popupResponse = document.createElement('div');
djmoffat@718 91 this.popupResponse.id = 'popupResponse';
djmoffat@718 92 this.popupResponse.style.width = 'inherit';
djmoffat@718 93 this.popupResponse.style.minHeight = '170px';
djmoffat@718 94 this.popupResponse.style.maxHeight = '320px';
djmoffat@718 95 this.popupResponse.style.overflow = 'auto';
djmoffat@718 96 this.popupContent.appendChild(this.popupResponse);
djmoffat@718 97
djmoffat@718 98 var buttonHolder = document.createElement('div');
djmoffat@718 99 buttonHolder.id='buttonHolder';
djmoffat@718 100 buttonHolder.width = 'inherit';
djmoffat@718 101 buttonHolder.style.height= '30px';
djmoffat@718 102 buttonHolder.align = 'left';
djmoffat@718 103 this.popupContent.appendChild(buttonHolder);
djmoffat@718 104
djmoffat@718 105 this.buttonProceed = document.createElement('button');
djmoffat@718 106 this.buttonProceed.className = 'popupButton';
djmoffat@718 107 this.buttonProceed.style.left = '390px';
djmoffat@718 108 this.buttonProceed.style.top = '2px';
djmoffat@718 109 this.buttonProceed.innerHTML = 'Next';
djmoffat@718 110 this.buttonProceed.onclick = function(){popup.proceedClicked();};
djmoffat@718 111
djmoffat@718 112 this.buttonPrevious = document.createElement('button');
djmoffat@718 113 this.buttonPrevious.className = 'popupButton';
djmoffat@718 114 this.buttonPrevious.style.left = '10px';
djmoffat@718 115 this.buttonPrevious.style.top = '2px';
djmoffat@718 116 this.buttonPrevious.innerHTML = 'Back';
djmoffat@718 117 this.buttonPrevious.onclick = function(){popup.previousClick();};
djmoffat@718 118
djmoffat@718 119 buttonHolder.appendChild(this.buttonPrevious);
djmoffat@718 120 buttonHolder.appendChild(this.buttonProceed);
djmoffat@718 121
djmoffat@718 122 this.popup.style.zIndex = -1;
djmoffat@718 123 this.popup.style.visibility = 'hidden';
djmoffat@718 124 blank.style.zIndex = -2;
djmoffat@718 125 blank.style.visibility = 'hidden';
djmoffat@718 126 insertPoint.appendChild(this.popup);
djmoffat@718 127 insertPoint.appendChild(blank);
djmoffat@718 128 };
djmoffat@718 129
djmoffat@718 130 this.showPopup = function(){
djmoffat@718 131 if (this.popup == null) {
djmoffat@718 132 this.createPopup();
djmoffat@718 133 }
djmoffat@718 134 this.popup.style.zIndex = 3;
djmoffat@718 135 this.popup.style.visibility = 'visible';
djmoffat@718 136 var blank = document.getElementsByClassName('testHalt')[0];
djmoffat@718 137 blank.style.zIndex = 2;
djmoffat@718 138 blank.style.visibility = 'visible';
djmoffat@718 139 $(window).keypress(function(e){
djmoffat@718 140 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
djmoffat@718 141 {
djmoffat@718 142 // Enter key pressed
djmoffat@718 143 var textarea = $(popup.popupContent).find('textarea');
djmoffat@718 144 if (textarea.length != 0)
djmoffat@718 145 {
djmoffat@718 146 if (textarea[0] == document.activeElement)
djmoffat@718 147 {return;}
djmoffat@718 148 }
djmoffat@718 149 popup.buttonProceed.onclick();
djmoffat@718 150 }
djmoffat@718 151 });
djmoffat@718 152 };
djmoffat@718 153
djmoffat@718 154 this.hidePopup = function(){
djmoffat@718 155 this.popup.style.zIndex = -1;
djmoffat@718 156 this.popup.style.visibility = 'hidden';
djmoffat@718 157 var blank = document.getElementsByClassName('testHalt')[0];
djmoffat@718 158 blank.style.zIndex = -2;
djmoffat@718 159 blank.style.visibility = 'hidden';
djmoffat@718 160 this.buttonPrevious.style.visibility = 'inherit';
djmoffat@718 161 };
djmoffat@718 162
djmoffat@718 163 this.postNode = function() {
djmoffat@718 164 // This will take the node from the popupOptions and display it
djmoffat@718 165 var node = this.popupOptions[this.currentIndex];
djmoffat@718 166 this.popupResponse.innerHTML = null;
djmoffat@718 167 if (node.type == 'statement') {
djmoffat@718 168 this.popupTitle.textContent = null;
djmoffat@718 169 var statement = document.createElement('span');
djmoffat@718 170 statement.textContent = node.statement;
djmoffat@718 171 this.popupResponse.appendChild(statement);
djmoffat@718 172 } else if (node.type == 'question') {
djmoffat@718 173 this.popupTitle.textContent = node.question;
djmoffat@718 174 var textArea = document.createElement('textarea');
djmoffat@718 175 switch (node.boxsize) {
djmoffat@718 176 case 'small':
djmoffat@718 177 textArea.cols = "20";
djmoffat@718 178 textArea.rows = "1";
djmoffat@718 179 break;
djmoffat@718 180 case 'normal':
djmoffat@718 181 textArea.cols = "30";
djmoffat@718 182 textArea.rows = "2";
djmoffat@718 183 break;
djmoffat@718 184 case 'large':
djmoffat@718 185 textArea.cols = "40";
djmoffat@718 186 textArea.rows = "5";
djmoffat@718 187 break;
djmoffat@718 188 case 'huge':
djmoffat@718 189 textArea.cols = "50";
djmoffat@718 190 textArea.rows = "10";
djmoffat@718 191 break;
djmoffat@718 192 }
djmoffat@718 193 this.popupResponse.appendChild(textArea);
djmoffat@718 194 textArea.focus();
djmoffat@718 195 } else if (node.type == 'checkbox') {
djmoffat@718 196 this.popupTitle.textContent = node.statement;
djmoffat@718 197 var optHold = this.popupResponse;
djmoffat@718 198 for (var i=0; i<node.options.length; i++) {
djmoffat@718 199 var option = node.options[i];
djmoffat@718 200 var input = document.createElement('input');
djmoffat@718 201 input.id = option.id;
djmoffat@718 202 input.type = 'checkbox';
djmoffat@718 203 var span = document.createElement('span');
djmoffat@718 204 span.textContent = option.text;
djmoffat@718 205 var hold = document.createElement('div');
djmoffat@718 206 hold.setAttribute('name','option');
djmoffat@718 207 hold.style.padding = '4px';
djmoffat@718 208 hold.appendChild(input);
djmoffat@718 209 hold.appendChild(span);
djmoffat@718 210 optHold.appendChild(hold);
djmoffat@718 211 }
djmoffat@718 212 } else if (node.type == 'radio') {
djmoffat@718 213 this.popupTitle.textContent = node.statement;
djmoffat@718 214 var optHold = this.popupResponse;
djmoffat@718 215 for (var i=0; i<node.options.length; i++) {
djmoffat@718 216 var option = node.options[i];
djmoffat@718 217 var input = document.createElement('input');
djmoffat@718 218 input.id = option.name;
djmoffat@718 219 input.type = 'radio';
djmoffat@718 220 input.name = node.id;
djmoffat@718 221 var span = document.createElement('span');
djmoffat@718 222 span.textContent = option.text;
djmoffat@718 223 var hold = document.createElement('div');
djmoffat@718 224 hold.setAttribute('name','option');
djmoffat@718 225 hold.style.padding = '4px';
djmoffat@718 226 hold.appendChild(input);
djmoffat@718 227 hold.appendChild(span);
djmoffat@718 228 optHold.appendChild(hold);
djmoffat@718 229 }
djmoffat@718 230 } else if (node.type == 'number') {
djmoffat@718 231 this.popupTitle.textContent = node.statement;
djmoffat@718 232 var input = document.createElement('input');
djmoffat@718 233 input.type = 'textarea';
djmoffat@718 234 if (node.min != null) {input.min = node.min;}
djmoffat@718 235 if (node.max != null) {input.max = node.max;}
djmoffat@718 236 if (node.step != null) {input.step = node.step;}
djmoffat@718 237 this.popupResponse.appendChild(input);
djmoffat@718 238 }
djmoffat@718 239 if(this.currentIndex+1 == this.popupOptions.length) {
djmoffat@718 240 if (this.responses.nodeName == "PRETEST") {
djmoffat@718 241 this.buttonProceed.textContent = 'Start';
djmoffat@718 242 } else {
djmoffat@718 243 this.buttonProceed.textContent = 'Submit';
djmoffat@718 244 }
djmoffat@718 245 } else {
djmoffat@718 246 this.buttonProceed.textContent = 'Next';
djmoffat@718 247 }
djmoffat@718 248 if(this.currentIndex > 0)
djmoffat@718 249 this.buttonPrevious.style.visibility = 'visible';
djmoffat@718 250 else
djmoffat@718 251 this.buttonPrevious.style.visibility = 'hidden';
djmoffat@718 252 };
djmoffat@718 253
djmoffat@718 254 this.initState = function(node) {
djmoffat@718 255 //Call this with your preTest and postTest nodes when needed to
djmoffat@718 256 // initialise the popup procedure.
djmoffat@718 257 this.popupOptions = node.options;
djmoffat@718 258 if (this.popupOptions.length > 0) {
djmoffat@718 259 if (node.type == 'pretest') {
djmoffat@718 260 this.responses = document.createElement('PreTest');
djmoffat@718 261 } else if (node.type == 'posttest') {
djmoffat@718 262 this.responses = document.createElement('PostTest');
djmoffat@718 263 } else {
djmoffat@718 264 console.log ('WARNING - popup node neither pre or post!');
djmoffat@718 265 this.responses = document.createElement('responses');
djmoffat@718 266 }
djmoffat@718 267 this.currentIndex = 0;
djmoffat@718 268 this.showPopup();
djmoffat@718 269 this.postNode();
djmoffat@718 270 } else {
djmoffat@718 271 advanceState();
djmoffat@718 272 }
djmoffat@718 273 };
djmoffat@718 274
djmoffat@718 275 this.proceedClicked = function() {
djmoffat@718 276 // Each time the popup button is clicked!
djmoffat@718 277 var node = this.popupOptions[this.currentIndex];
djmoffat@718 278 if (node.type == 'question') {
djmoffat@718 279 // Must extract the question data
djmoffat@718 280 var textArea = $(popup.popupContent).find('textarea')[0];
djmoffat@718 281 if (node.mandatory == true && textArea.value.length == 0) {
djmoffat@718 282 alert('This question is mandatory');
djmoffat@718 283 return;
djmoffat@718 284 } else {
djmoffat@718 285 // Save the text content
djmoffat@718 286 var hold = document.createElement('comment');
djmoffat@718 287 hold.id = node.id;
djmoffat@718 288 hold.innerHTML = textArea.value;
djmoffat@718 289 console.log("Question: "+ node.question);
djmoffat@718 290 console.log("Question Response: "+ textArea.value);
djmoffat@718 291 this.responses.appendChild(hold);
djmoffat@718 292 }
djmoffat@718 293 } else if (node.type == 'checkbox') {
djmoffat@718 294 // Must extract checkbox data
djmoffat@718 295 var optHold = this.popupResponse;
djmoffat@718 296 var hold = document.createElement('checkbox');
djmoffat@718 297 console.log("Checkbox: "+ node.statement);
djmoffat@718 298 hold.id = node.id;
djmoffat@718 299 for (var i=0; i<optHold.childElementCount; i++) {
djmoffat@718 300 var input = optHold.childNodes[i].getElementsByTagName('input')[0];
djmoffat@718 301 var statement = optHold.childNodes[i].getElementsByTagName('span')[0];
djmoffat@718 302 var response = document.createElement('option');
djmoffat@718 303 response.setAttribute('name',input.id);
djmoffat@718 304 response.textContent = input.checked;
djmoffat@718 305 hold.appendChild(response);
djmoffat@718 306 console.log(input.id +': '+ input.checked);
djmoffat@718 307 }
djmoffat@718 308 this.responses.appendChild(hold);
djmoffat@718 309 } else if (node.type == "radio") {
djmoffat@718 310 var optHold = this.popupResponse;
djmoffat@718 311 var hold = document.createElement('radio');
djmoffat@718 312 var responseID = null;
djmoffat@718 313 var i=0;
djmoffat@718 314 while(responseID == null) {
djmoffat@718 315 var input = optHold.childNodes[i].getElementsByTagName('input')[0];
djmoffat@718 316 if (input.checked == true) {
djmoffat@718 317 responseID = i;
djmoffat@718 318 }
djmoffat@718 319 i++;
djmoffat@718 320 }
djmoffat@718 321 hold.id = node.id;
djmoffat@718 322 hold.setAttribute('name',node.options[responseID].name);
djmoffat@718 323 hold.textContent = node.options[responseID].text;
djmoffat@718 324 this.responses.appendChild(hold);
djmoffat@718 325 } else if (node.type == "number") {
djmoffat@718 326 var input = this.popupContent.getElementsByTagName('input')[0];
djmoffat@718 327 if (node.mandatory == true && input.value.length == 0) {
djmoffat@718 328 alert('This question is mandatory. Please enter a number');
djmoffat@718 329 return;
djmoffat@718 330 }
djmoffat@718 331 var enteredNumber = Number(input.value);
djmoffat@718 332 if (isNaN(enteredNumber)) {
djmoffat@718 333 alert('Please enter a valid number');
djmoffat@718 334 return;
djmoffat@718 335 }
djmoffat@718 336 if (enteredNumber < node.min && node.min != null) {
djmoffat@718 337 alert('Number is below the minimum value of '+node.min);
djmoffat@718 338 return;
djmoffat@718 339 }
djmoffat@718 340 if (enteredNumber > node.max && node.max != null) {
djmoffat@718 341 alert('Number is above the maximum value of '+node.max);
djmoffat@718 342 return;
djmoffat@718 343 }
djmoffat@718 344 var hold = document.createElement('number');
djmoffat@718 345 hold.id = node.id;
djmoffat@718 346 hold.textContent = input.value;
djmoffat@718 347 this.responses.appendChild(hold);
djmoffat@718 348 }
djmoffat@718 349 this.currentIndex++;
djmoffat@718 350 if (this.currentIndex < this.popupOptions.length) {
djmoffat@718 351 this.postNode();
djmoffat@718 352 } else {
djmoffat@718 353 // Reached the end of the popupOptions
djmoffat@718 354 this.hidePopup();
djmoffat@718 355 if (this.responses.nodeName == testState.stateResults[testState.stateIndex].nodeName) {
djmoffat@718 356 testState.stateResults[testState.stateIndex] = this.responses;
djmoffat@718 357 } else {
djmoffat@718 358 testState.stateResults[testState.stateIndex].appendChild(this.responses);
djmoffat@718 359 }
djmoffat@718 360 advanceState();
djmoffat@718 361 }
djmoffat@718 362 };
djmoffat@718 363
djmoffat@718 364 this.previousClick = function() {
djmoffat@718 365 // Triggered when the 'Back' button is clicked in the survey
djmoffat@718 366 if (this.currentIndex > 0) {
djmoffat@718 367 this.currentIndex--;
djmoffat@718 368 var node = this.popupOptions[this.currentIndex];
djmoffat@718 369 if (node.type != 'statement') {
djmoffat@718 370 var prevResp = this.responses.childNodes[this.responses.childElementCount-1];
djmoffat@718 371 this.responses.removeChild(prevResp);
djmoffat@718 372 }
djmoffat@718 373 this.postNode();
djmoffat@718 374 if (node.type == 'question') {
djmoffat@718 375 this.popupContent.getElementsByTagName('textarea')[0].value = prevResp.textContent;
djmoffat@718 376 } else if (node.type == 'checkbox') {
djmoffat@718 377 var options = this.popupContent.getElementsByTagName('input');
djmoffat@718 378 var savedOptions = prevResp.getElementsByTagName('option');
djmoffat@718 379 for (var i=0; i<options.length; i++) {
djmoffat@718 380 var id = options[i].id;
djmoffat@718 381 for (var j=0; j<savedOptions.length; j++) {
djmoffat@718 382 if (savedOptions[j].getAttribute('name') == id) {
djmoffat@718 383 if (savedOptions[j].textContent == 'true') {options[i].checked = true;}
djmoffat@718 384 else {options[i].checked = false;}
djmoffat@718 385 break;
djmoffat@718 386 }
djmoffat@718 387 }
djmoffat@718 388 }
djmoffat@718 389 } else if (node.type == 'number') {
djmoffat@718 390 this.popupContent.getElementsByTagName('input')[0].value = prevResp.textContent;
djmoffat@718 391 } else if (node.type == 'radio') {
djmoffat@718 392 var options = this.popupContent.getElementsByTagName('input');
djmoffat@718 393 var name = prevResp.getAttribute('name');
djmoffat@718 394 for (var i=0; i<options.length; i++) {
djmoffat@718 395 if (options[i].id == name) {
djmoffat@718 396 options[i].checked = true;
djmoffat@718 397 break;
djmoffat@718 398 }
djmoffat@718 399 }
djmoffat@718 400 }
djmoffat@718 401 }
djmoffat@718 402 };
djmoffat@718 403 }
djmoffat@718 404
djmoffat@718 405 function advanceState()
djmoffat@718 406 {
djmoffat@718 407 // Just for complete clarity
djmoffat@718 408 testState.advanceState();
djmoffat@718 409 }
djmoffat@718 410
djmoffat@718 411 function stateMachine()
djmoffat@718 412 {
djmoffat@718 413 // Object prototype for tracking and managing the test state
djmoffat@718 414 this.stateMap = [];
djmoffat@718 415 this.stateIndex = null;
djmoffat@718 416 this.currentStateMap = [];
djmoffat@718 417 this.currentIndex = null;
djmoffat@718 418 this.currentTestId = 0;
djmoffat@718 419 this.stateResults = [];
djmoffat@718 420 this.timerCallBackHolders = null;
djmoffat@718 421 this.initialise = function(){
djmoffat@718 422 if (this.stateMap.length > 0) {
djmoffat@718 423 if(this.stateIndex != null) {
djmoffat@718 424 console.log('NOTE - State already initialise');
djmoffat@718 425 }
djmoffat@718 426 this.stateIndex = -1;
djmoffat@718 427 var that = this;
djmoffat@718 428 var aH_pId = 0;
djmoffat@718 429 for (var id=0; id<this.stateMap.length; id++){
djmoffat@718 430 var name = this.stateMap[id].type;
djmoffat@718 431 var obj = document.createElement(name);
djmoffat@718 432 if (name == 'audioHolder') {
djmoffat@718 433 obj.id = this.stateMap[id].id;
djmoffat@718 434 obj.setAttribute('presentedid',aH_pId);
djmoffat@718 435 aH_pId+=1;
djmoffat@718 436 }
djmoffat@718 437 this.stateResults.push(obj);
djmoffat@718 438 }
djmoffat@718 439 } else {
djmoffat@718 440 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
djmoffat@718 441 }
djmoffat@718 442 };
djmoffat@718 443 this.advanceState = function(){
djmoffat@718 444 if (this.stateIndex == null) {
djmoffat@718 445 this.initialise();
djmoffat@718 446 }
djmoffat@718 447 if (this.stateIndex == -1) {
djmoffat@718 448 console.log('Starting test...');
djmoffat@718 449 }
djmoffat@718 450 if (this.currentIndex == null){
djmoffat@718 451 if (this.currentStateMap.type == "audioHolder") {
djmoffat@718 452 // Save current page
djmoffat@718 453 this.testPageCompleted(this.stateResults[this.stateIndex],this.currentStateMap,this.currentTestId);
djmoffat@718 454 this.currentTestId++;
djmoffat@718 455 }
djmoffat@718 456 this.stateIndex++;
djmoffat@718 457 if (this.stateIndex >= this.stateMap.length) {
djmoffat@718 458 console.log('Test Completed');
djmoffat@718 459 createProjectSave(specification.projectReturn);
djmoffat@718 460 } else {
djmoffat@718 461 this.currentStateMap = this.stateMap[this.stateIndex];
djmoffat@718 462 if (this.currentStateMap.type == "audioHolder") {
djmoffat@718 463 console.log('Loading test page');
djmoffat@718 464 loadTest(this.currentStateMap);
djmoffat@718 465 this.initialiseInnerState(this.currentStateMap);
djmoffat@718 466 } else if (this.currentStateMap.type == "pretest" || this.currentStateMap.type == "posttest") {
djmoffat@718 467 if (this.currentStateMap.options.length >= 1) {
djmoffat@718 468 popup.initState(this.currentStateMap);
djmoffat@718 469 } else {
djmoffat@718 470 this.advanceState();
djmoffat@718 471 }
djmoffat@718 472 } else {
djmoffat@718 473 this.advanceState();
djmoffat@718 474 }
djmoffat@718 475 }
djmoffat@718 476 } else {
djmoffat@718 477 this.advanceInnerState();
djmoffat@718 478 }
djmoffat@718 479 };
djmoffat@718 480
djmoffat@718 481 this.testPageCompleted = function(store, testXML, testId) {
djmoffat@718 482 // Function called each time a test page has been completed
djmoffat@718 483 // Can be used to over-rule default behaviour
djmoffat@718 484
djmoffat@718 485 pageXMLSave(store, testXML);
djmoffat@718 486 };
djmoffat@718 487
djmoffat@718 488 this.initialiseInnerState = function(node) {
djmoffat@718 489 // Parses the received testXML for pre and post test options
djmoffat@718 490 this.currentStateMap = [];
djmoffat@718 491 var preTest = node.preTest;
djmoffat@718 492 var postTest = node.postTest;
djmoffat@718 493 if (preTest == undefined) {preTest = document.createElement("preTest");}
djmoffat@718 494 if (postTest == undefined){postTest= document.createElement("postTest");}
djmoffat@718 495 this.currentStateMap.push(preTest);
djmoffat@718 496 this.currentStateMap.push(node);
djmoffat@718 497 this.currentStateMap.push(postTest);
djmoffat@718 498 this.currentIndex = -1;
djmoffat@718 499 this.advanceInnerState();
djmoffat@718 500 };
djmoffat@718 501
djmoffat@718 502 this.advanceInnerState = function() {
djmoffat@718 503 this.currentIndex++;
djmoffat@718 504 if (this.currentIndex >= this.currentStateMap.length) {
djmoffat@718 505 this.currentIndex = null;
djmoffat@718 506 this.currentStateMap = this.stateMap[this.stateIndex];
djmoffat@718 507 this.advanceState();
djmoffat@718 508 } else {
djmoffat@718 509 if (this.currentStateMap[this.currentIndex].type == "audioHolder") {
djmoffat@718 510 console.log("Loading test page"+this.currentTestId);
djmoffat@718 511 } else if (this.currentStateMap[this.currentIndex].type == "pretest") {
djmoffat@718 512 popup.initState(this.currentStateMap[this.currentIndex]);
djmoffat@718 513 } else if (this.currentStateMap[this.currentIndex].type == "posttest") {
djmoffat@718 514 popup.initState(this.currentStateMap[this.currentIndex]);
djmoffat@718 515 } else {
djmoffat@718 516 this.advanceInnerState();
djmoffat@718 517 }
djmoffat@718 518 }
djmoffat@718 519 };
djmoffat@718 520
djmoffat@718 521 this.previousState = function(){};
djmoffat@718 522 }
djmoffat@718 523
djmoffat@718 524 function testEnded(testId)
djmoffat@718 525 {
djmoffat@718 526 pageXMLSave(testId);
djmoffat@718 527 if (testXMLSetups.length-1 > testId)
djmoffat@718 528 {
djmoffat@718 529 // Yes we have another test to perform
djmoffat@718 530 testId = (Number(testId)+1);
djmoffat@718 531 currentState = 'testRun-'+testId;
djmoffat@718 532 loadTest(testId);
djmoffat@718 533 } else {
djmoffat@718 534 console.log('Testing Completed!');
djmoffat@718 535 currentState = 'postTest';
djmoffat@718 536 // Check for any post tests
djmoffat@718 537 var xmlSetup = projectXML.find('setup');
djmoffat@718 538 var postTest = xmlSetup.find('PostTest')[0];
djmoffat@718 539 popup.initState(postTest);
djmoffat@718 540 }
djmoffat@718 541 }
djmoffat@718 542
djmoffat@718 543 function loadProjectSpec(url) {
djmoffat@718 544 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
djmoffat@718 545 // If url is null, request client to upload project XML document
djmoffat@718 546 var r = new XMLHttpRequest();
djmoffat@718 547 r.open('GET',url,true);
djmoffat@718 548 r.onload = function() {
djmoffat@718 549 loadProjectSpecCallback(r.response);
djmoffat@718 550 };
djmoffat@718 551 r.send();
djmoffat@718 552 };
djmoffat@718 553
djmoffat@718 554 function loadProjectSpecCallback(response) {
djmoffat@718 555 // Function called after asynchronous download of XML project specification
djmoffat@718 556 //var decode = $.parseXML(response);
djmoffat@718 557 //projectXML = $(decode);
djmoffat@718 558
djmoffat@718 559 var parse = new DOMParser();
djmoffat@718 560 projectXML = parse.parseFromString(response,'text/xml');
djmoffat@718 561
djmoffat@718 562 // Build the specification
djmoffat@718 563 specification.decode();
djmoffat@718 564
djmoffat@718 565 testState.stateMap.push(specification.preTest);
djmoffat@718 566
djmoffat@718 567 $(specification.audioHolders).each(function(index,elem){
djmoffat@718 568 testState.stateMap.push(elem);
djmoffat@718 569 });
djmoffat@718 570
djmoffat@718 571 testState.stateMap.push(specification.postTest);
djmoffat@718 572
djmoffat@718 573 // Obtain the metrics enabled
djmoffat@718 574 $(specification.metrics).each(function(index,node){
djmoffat@718 575 var enabled = node.textContent;
djmoffat@718 576 switch(node.enabled)
djmoffat@718 577 {
djmoffat@718 578 case 'testTimer':
djmoffat@718 579 sessionMetrics.prototype.enableTestTimer = true;
djmoffat@718 580 break;
djmoffat@718 581 case 'elementTimer':
djmoffat@718 582 sessionMetrics.prototype.enableElementTimer = true;
djmoffat@718 583 break;
djmoffat@718 584 case 'elementTracker':
djmoffat@718 585 sessionMetrics.prototype.enableElementTracker = true;
djmoffat@718 586 break;
djmoffat@718 587 case 'elementListenTracker':
djmoffat@718 588 sessionMetrics.prototype.enableElementListenTracker = true;
djmoffat@718 589 break;
djmoffat@718 590 case 'elementInitialPosition':
djmoffat@718 591 sessionMetrics.prototype.enableElementInitialPosition = true;
djmoffat@718 592 break;
djmoffat@718 593 case 'elementFlagListenedTo':
djmoffat@718 594 sessionMetrics.prototype.enableFlagListenedTo = true;
djmoffat@718 595 break;
djmoffat@718 596 case 'elementFlagMoved':
djmoffat@718 597 sessionMetrics.prototype.enableFlagMoved = true;
djmoffat@718 598 break;
djmoffat@718 599 case 'elementFlagComments':
djmoffat@718 600 sessionMetrics.prototype.enableFlagComments = true;
djmoffat@718 601 break;
djmoffat@718 602 }
djmoffat@718 603 });
djmoffat@718 604
djmoffat@718 605
djmoffat@718 606
djmoffat@718 607 // Detect the interface to use and load the relevant javascripts.
djmoffat@718 608 var interfaceJS = document.createElement('script');
djmoffat@718 609 interfaceJS.setAttribute("type","text/javascript");
djmoffat@718 610 if (specification.interfaceType == 'APE') {
djmoffat@718 611 interfaceJS.setAttribute("src","ape.js");
djmoffat@718 612
djmoffat@718 613 // APE comes with a css file
djmoffat@718 614 var css = document.createElement('link');
djmoffat@718 615 css.rel = 'stylesheet';
djmoffat@718 616 css.type = 'text/css';
djmoffat@718 617 css.href = 'ape.css';
djmoffat@718 618
djmoffat@718 619 document.getElementsByTagName("head")[0].appendChild(css);
djmoffat@718 620 } else if (specification.interfaceType == "MUSHRA")
djmoffat@718 621 {
djmoffat@718 622 interfaceJS.setAttribute("src","mushra.js");
djmoffat@718 623
djmoffat@718 624 // MUSHRA comes with a css file
djmoffat@718 625 var css = document.createElement('link');
djmoffat@718 626 css.rel = 'stylesheet';
djmoffat@718 627 css.type = 'text/css';
djmoffat@718 628 css.href = 'mushra.css';
djmoffat@718 629
djmoffat@718 630 document.getElementsByTagName("head")[0].appendChild(css);
djmoffat@718 631 }
djmoffat@718 632 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
djmoffat@718 633
djmoffat@718 634 // Define window callbacks for interface
djmoffat@718 635 window.onresize = function(event){interfaceContext.resizeWindow(event);};
djmoffat@718 636 }
djmoffat@718 637
djmoffat@718 638 function createProjectSave(destURL) {
djmoffat@718 639 // Save the data from interface into XML and send to destURL
djmoffat@718 640 // If destURL is null then download XML in client
djmoffat@718 641 // Now time to render file locally
djmoffat@718 642 var xmlDoc = interfaceXMLSave();
djmoffat@718 643 var parent = document.createElement("div");
djmoffat@718 644 parent.appendChild(xmlDoc);
djmoffat@718 645 var file = [parent.innerHTML];
djmoffat@718 646 if (destURL == "null" || destURL == undefined) {
djmoffat@718 647 var bb = new Blob(file,{type : 'application/xml'});
djmoffat@718 648 var dnlk = window.URL.createObjectURL(bb);
djmoffat@718 649 var a = document.createElement("a");
djmoffat@718 650 a.hidden = '';
djmoffat@718 651 a.href = dnlk;
djmoffat@718 652 a.download = "save.xml";
djmoffat@718 653 a.textContent = "Save File";
djmoffat@718 654
djmoffat@718 655 popup.showPopup();
djmoffat@718 656 popup.popupContent.innerHTML = null;
djmoffat@718 657 popup.popupContent.appendChild(a);
djmoffat@718 658 } else {
djmoffat@718 659 var xmlhttp = new XMLHttpRequest;
djmoffat@718 660 xmlhttp.open("POST",destURL,true);
djmoffat@718 661 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
djmoffat@718 662 xmlhttp.onerror = function(){
djmoffat@718 663 console.log('Error saving file to server! Presenting download locally');
djmoffat@718 664 createProjectSave(null);
djmoffat@718 665 };
djmoffat@718 666 xmlhttp.onreadystatechange = function() {
djmoffat@718 667 console.log(xmlhttp.status);
djmoffat@718 668 if (xmlhttp.status != 200 && xmlhttp.readyState == 4) {
djmoffat@718 669 createProjectSave(null);
djmoffat@718 670 } else {
djmoffat@718 671 popup.showPopup();
djmoffat@718 672 popup.popupContent.innerHTML = null;
djmoffat@718 673 popup.popupContent.textContent = "Thank you!";
djmoffat@718 674 }
djmoffat@718 675 };
djmoffat@718 676 xmlhttp.send(file);
djmoffat@718 677 }
djmoffat@718 678 }
djmoffat@718 679
djmoffat@718 680 function errorSessionDump(msg){
djmoffat@718 681 // Create the partial interface XML save
djmoffat@718 682 // Include error node with message on why the dump occured
djmoffat@718 683 var xmlDoc = interfaceXMLSave();
djmoffat@718 684 var err = document.createElement('error');
djmoffat@718 685 err.textContent = msg;
djmoffat@718 686 xmlDoc.appendChild(err);
djmoffat@718 687 var parent = document.createElement("div");
djmoffat@718 688 parent.appendChild(xmlDoc);
djmoffat@718 689 var file = [parent.innerHTML];
djmoffat@718 690 var bb = new Blob(file,{type : 'application/xml'});
djmoffat@718 691 var dnlk = window.URL.createObjectURL(bb);
djmoffat@718 692 var a = document.createElement("a");
djmoffat@718 693 a.hidden = '';
djmoffat@718 694 a.href = dnlk;
djmoffat@718 695 a.download = "save.xml";
djmoffat@718 696 a.textContent = "Save File";
djmoffat@718 697
djmoffat@718 698 popup.showPopup();
djmoffat@718 699 popup.popupContent.innerHTML = "ERROR : "+msg;
djmoffat@718 700 popup.popupContent.appendChild(a);
djmoffat@718 701 }
djmoffat@718 702
djmoffat@718 703 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
djmoffat@718 704 function interfaceXMLSave(){
djmoffat@718 705 // Create the XML string to be exported with results
djmoffat@718 706 var xmlDoc = document.createElement("BrowserEvaluationResult");
djmoffat@718 707 var projectDocument = specification.projectXML;
djmoffat@718 708 projectDocument.setAttribute('file-name',url);
djmoffat@718 709 xmlDoc.appendChild(projectDocument);
djmoffat@718 710 xmlDoc.appendChild(returnDateNode());
djmoffat@718 711 for (var i=0; i<testState.stateResults.length; i++)
djmoffat@718 712 {
djmoffat@718 713 xmlDoc.appendChild(testState.stateResults[i]);
djmoffat@718 714 }
djmoffat@718 715
djmoffat@718 716 return xmlDoc;
djmoffat@718 717 }
djmoffat@718 718
djmoffat@718 719 function AudioEngine() {
djmoffat@718 720
djmoffat@718 721 // Create two output paths, the main outputGain and fooGain.
djmoffat@718 722 // Output gain is default to 1 and any items for playback route here
djmoffat@718 723 // Foo gain is used for analysis to ensure paths get processed, but are not heard
djmoffat@718 724 // because web audio will optimise and any route which does not go to the destination gets ignored.
djmoffat@718 725 this.outputGain = audioContext.createGain();
djmoffat@718 726 this.fooGain = audioContext.createGain();
djmoffat@718 727 this.fooGain.gain = 0;
djmoffat@718 728
djmoffat@718 729 // Use this to detect playback state: 0 - stopped, 1 - playing
djmoffat@718 730 this.status = 0;
djmoffat@718 731
djmoffat@718 732 // Connect both gains to output
djmoffat@718 733 this.outputGain.connect(audioContext.destination);
djmoffat@718 734 this.fooGain.connect(audioContext.destination);
djmoffat@718 735
djmoffat@718 736 // Create the timer Object
djmoffat@718 737 this.timer = new timer();
djmoffat@718 738 // Create session metrics
djmoffat@718 739 this.metric = new sessionMetrics(this);
djmoffat@718 740
djmoffat@718 741 this.loopPlayback = false;
djmoffat@718 742
djmoffat@718 743 // Create store for new audioObjects
djmoffat@718 744 this.audioObjects = [];
djmoffat@718 745
djmoffat@718 746 this.play = function(id) {
djmoffat@718 747 // Start the timer and set the audioEngine state to playing (1)
djmoffat@718 748 if (this.status == 0 && this.loopPlayback) {
djmoffat@718 749 // Check if all audioObjects are ready
djmoffat@718 750 if(this.checkAllReady())
djmoffat@718 751 {
djmoffat@718 752 this.status = 1;
djmoffat@718 753 this.setSynchronousLoop();
djmoffat@718 754 }
djmoffat@718 755 }
djmoffat@718 756 else
djmoffat@718 757 {
djmoffat@718 758 this.status = 1;
djmoffat@718 759 }
djmoffat@718 760 if (this.status== 1) {
djmoffat@718 761 this.timer.startTest();
djmoffat@718 762 if (id == undefined) {
djmoffat@718 763 id = -1;
djmoffat@718 764 console.log('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
djmoffat@718 765 return;
djmoffat@718 766 } else {
djmoffat@718 767 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
djmoffat@718 768 }
djmoffat@718 769 if (this.loopPlayback) {
djmoffat@718 770 for (var i=0; i<this.audioObjects.length; i++)
djmoffat@718 771 {
djmoffat@718 772 this.audioObjects[i].play(this.timer.getTestTime()+1);
djmoffat@718 773 if (id == i) {
djmoffat@718 774 this.audioObjects[i].loopStart();
djmoffat@718 775 } else {
djmoffat@718 776 this.audioObjects[i].loopStop();
djmoffat@718 777 }
djmoffat@718 778 }
djmoffat@718 779 } else {
djmoffat@718 780 for (var i=0; i<this.audioObjects.length; i++)
djmoffat@718 781 {
djmoffat@718 782 if (i != id) {
djmoffat@718 783 this.audioObjects[i].outputGain.gain.value = 0.0;
djmoffat@718 784 this.audioObjects[i].stop();
djmoffat@718 785 } else if (i == id) {
djmoffat@718 786 this.audioObjects[id].outputGain.gain.value = 1.0;
djmoffat@718 787 this.audioObjects[id].play(audioContext.currentTime+0.01);
djmoffat@718 788 }
djmoffat@718 789 }
djmoffat@718 790 }
djmoffat@718 791 interfaceContext.playhead.start();
djmoffat@718 792 }
djmoffat@718 793 };
djmoffat@718 794
djmoffat@718 795 this.stop = function() {
djmoffat@718 796 // Send stop and reset command to all playback buffers and set audioEngine state to stopped (1)
djmoffat@718 797 if (this.status == 1) {
djmoffat@718 798 for (var i=0; i<this.audioObjects.length; i++)
djmoffat@718 799 {
djmoffat@718 800 this.audioObjects[i].stop();
djmoffat@718 801 }
djmoffat@718 802 interfaceContext.playhead.stop();
djmoffat@718 803 this.status = 0;
djmoffat@718 804 }
djmoffat@718 805 };
djmoffat@718 806
djmoffat@718 807 this.newTrack = function(element) {
djmoffat@718 808 // Pull data from given URL into new audio buffer
djmoffat@718 809 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
djmoffat@718 810
djmoffat@718 811 // Create the audioObject with ID of the new track length;
djmoffat@718 812 audioObjectId = this.audioObjects.length;
djmoffat@718 813 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
djmoffat@718 814
djmoffat@718 815 // AudioObject will get track itself.
djmoffat@718 816 this.audioObjects[audioObjectId].specification = element;
djmoffat@718 817 this.audioObjects[audioObjectId].constructTrack(element.parent.hostURL + element.url);
djmoffat@718 818 return this.audioObjects[audioObjectId];
djmoffat@718 819 };
djmoffat@718 820
djmoffat@718 821 this.newTestPage = function() {
djmoffat@718 822 this.state = 0;
djmoffat@718 823 this.audioObjectsReady = false;
djmoffat@718 824 this.metric.reset();
djmoffat@718 825 this.audioObjects = [];
djmoffat@718 826 };
djmoffat@718 827
djmoffat@718 828 this.checkAllPlayed = function() {
djmoffat@718 829 arr = [];
djmoffat@718 830 for (var id=0; id<this.audioObjects.length; id++) {
djmoffat@718 831 if (this.audioObjects[id].metric.wasListenedTo == false) {
djmoffat@718 832 arr.push(this.audioObjects[id].id);
djmoffat@718 833 }
djmoffat@718 834 }
djmoffat@718 835 return arr;
djmoffat@718 836 };
djmoffat@718 837
djmoffat@718 838 this.checkAllReady = function() {
djmoffat@718 839 var ready = true;
djmoffat@718 840 for (var i=0; i<this.audioObjects.length; i++) {
djmoffat@718 841 if (this.audioObjects[i].state == 0) {
djmoffat@718 842 // Track not ready
djmoffat@718 843 console.log('WAIT -- audioObject '+i+' not ready yet!');
djmoffat@718 844 ready = false;
djmoffat@718 845 };
djmoffat@718 846 }
djmoffat@718 847 return ready;
djmoffat@718 848 };
djmoffat@718 849
djmoffat@718 850 this.setSynchronousLoop = function() {
djmoffat@718 851 // Pads the signals so they are all exactly the same length
djmoffat@718 852 var length = 0;
djmoffat@718 853 var lens = [];
djmoffat@718 854 var maxId;
djmoffat@718 855 for (var i=0; i<this.audioObjects.length; i++)
djmoffat@718 856 {
djmoffat@718 857 lens.push(this.audioObjects[i].buffer.length);
djmoffat@718 858 if (length < this.audioObjects[i].buffer.length)
djmoffat@718 859 {
djmoffat@718 860 length = this.audioObjects[i].buffer.length;
djmoffat@718 861 maxId = i;
djmoffat@718 862 }
djmoffat@718 863 }
djmoffat@718 864 // Perform difference
djmoffat@718 865 for (var i=0; i<lens.length; i++)
djmoffat@718 866 {
djmoffat@718 867 lens[i] = length - lens[i];
djmoffat@718 868 }
djmoffat@718 869 // Extract the audio and zero-pad
djmoffat@718 870 for (var i=0; i<lens.length; i++)
djmoffat@718 871 {
djmoffat@718 872 var orig = this.audioObjects[i].buffer;
djmoffat@718 873 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate);
djmoffat@718 874 for (var c=0; c<orig.numberOfChannels; c++)
djmoffat@718 875 {
djmoffat@718 876 var inData = hold.getChannelData(c);
djmoffat@718 877 var outData = orig.getChannelData(c);
djmoffat@718 878 for (var n=0; n<orig.length; n++)
djmoffat@718 879 {inData[n] = outData[n];}
djmoffat@718 880 }
djmoffat@718 881 this.audioObjects[i].buffer = hold;
djmoffat@718 882 delete orig;
djmoffat@718 883 }
djmoffat@718 884 };
djmoffat@718 885
djmoffat@718 886 }
djmoffat@718 887
djmoffat@718 888 function audioObject(id) {
djmoffat@718 889 // The main buffer object with common control nodes to the AudioEngine
djmoffat@718 890
djmoffat@718 891 this.specification;
djmoffat@718 892 this.id = id;
djmoffat@718 893 this.state = 0; // 0 - no data, 1 - ready
djmoffat@718 894 this.url = null; // Hold the URL given for the output back to the results.
djmoffat@718 895 this.metric = new metricTracker(this);
djmoffat@718 896
djmoffat@718 897 // Bindings for GUI
djmoffat@718 898 this.interfaceDOM = null;
djmoffat@718 899 this.commentDOM = null;
djmoffat@718 900
djmoffat@718 901 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
djmoffat@718 902 this.bufferNode = undefined;
djmoffat@718 903 this.outputGain = audioContext.createGain();
djmoffat@718 904
djmoffat@718 905 // Default output gain to be zero
djmoffat@718 906 this.outputGain.gain.value = 0.0;
djmoffat@718 907
djmoffat@718 908 // Connect buffer to the audio graph
djmoffat@718 909 this.outputGain.connect(audioEngineContext.outputGain);
djmoffat@718 910
djmoffat@718 911 // the audiobuffer is not designed for multi-start playback
djmoffat@718 912 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
djmoffat@718 913 this.buffer;
djmoffat@718 914
djmoffat@718 915 this.loopStart = function() {
djmoffat@718 916 this.outputGain.gain.value = 1.0;
djmoffat@718 917 this.metric.startListening(audioEngineContext.timer.getTestTime());
djmoffat@718 918 };
djmoffat@718 919
djmoffat@718 920 this.loopStop = function() {
djmoffat@718 921 if (this.outputGain.gain.value != 0.0) {
djmoffat@718 922 this.outputGain.gain.value = 0.0;
djmoffat@718 923 this.metric.stopListening(audioEngineContext.timer.getTestTime());
djmoffat@718 924 }
djmoffat@718 925 };
djmoffat@718 926
djmoffat@718 927 this.play = function(startTime) {
djmoffat@718 928 if (this.bufferNode == undefined) {
djmoffat@718 929 this.bufferNode = audioContext.createBufferSource();
djmoffat@718 930 this.bufferNode.owner = this;
djmoffat@718 931 this.bufferNode.connect(this.outputGain);
djmoffat@718 932 this.bufferNode.buffer = this.buffer;
djmoffat@718 933 this.bufferNode.loop = audioEngineContext.loopPlayback;
djmoffat@718 934 this.bufferNode.onended = function(event) {
djmoffat@718 935 // Safari does not like using 'this' to reference the calling object!
djmoffat@718 936 event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
djmoffat@718 937 };
djmoffat@718 938 if (this.bufferNode.loop == false) {
djmoffat@718 939 this.metric.startListening(audioEngineContext.timer.getTestTime());
djmoffat@718 940 }
djmoffat@718 941 this.bufferNode.start(startTime);
djmoffat@718 942 }
djmoffat@718 943 };
djmoffat@718 944
djmoffat@718 945 this.stop = function() {
djmoffat@718 946 if (this.bufferNode != undefined)
djmoffat@718 947 {
djmoffat@718 948 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
djmoffat@718 949 this.bufferNode.stop(0);
djmoffat@718 950 this.bufferNode = undefined;
djmoffat@718 951 }
djmoffat@718 952 };
djmoffat@718 953
djmoffat@718 954 this.getCurrentPosition = function() {
djmoffat@718 955 var time = audioEngineContext.timer.getTestTime();
djmoffat@718 956 if (this.bufferNode != undefined) {
djmoffat@718 957 if (this.bufferNode.loop == true) {
djmoffat@718 958 if (audioEngineContext.status == 1) {
djmoffat@718 959 return time%this.buffer.duration;
djmoffat@718 960 } else {
djmoffat@718 961 return 0;
djmoffat@718 962 }
djmoffat@718 963 } else {
djmoffat@718 964 if (this.metric.listenHold) {
djmoffat@718 965 return time - this.metric.listenStart;
djmoffat@718 966 } else {
djmoffat@718 967 return 0;
djmoffat@718 968 }
djmoffat@718 969 }
djmoffat@718 970 } else {
djmoffat@718 971 return 0;
djmoffat@718 972 }
djmoffat@718 973 };
djmoffat@718 974
djmoffat@718 975 this.constructTrack = function(url) {
djmoffat@718 976 var request = new XMLHttpRequest();
djmoffat@718 977 this.url = url;
djmoffat@718 978 request.open('GET',url,true);
djmoffat@718 979 request.responseType = 'arraybuffer';
djmoffat@718 980
djmoffat@718 981 var audioObj = this;
djmoffat@718 982
djmoffat@718 983 // Create callback to decode the data asynchronously
djmoffat@718 984 request.onloadend = function() {
djmoffat@718 985 audioContext.decodeAudioData(request.response, function(decodedData) {
djmoffat@718 986 audioObj.buffer = decodedData;
djmoffat@718 987 audioObj.state = 1;
djmoffat@718 988 if (audioObj.specification.type != 'outsidereference')
djmoffat@718 989 {audioObj.interfaceDOM.enable();}
djmoffat@718 990 }, function(){
djmoffat@718 991 // Should only be called if there was an error, but sometimes gets called continuously
djmoffat@718 992 // Check here if the error is genuine
djmoffat@718 993 if (audioObj.state == 0 || audioObj.buffer == undefined) {
djmoffat@718 994 // Genuine error
djmoffat@718 995 console.log('FATAL - Error loading buffer on '+audioObj.id);
djmoffat@718 996 if (request.status == 404)
djmoffat@718 997 {
djmoffat@718 998 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
djmoffat@718 999 console.log('URL: '+audioObj.url);
djmoffat@718 1000 errorSessionDump('Fragment '+audioObj.id+' 404 error');
djmoffat@718 1001 }
djmoffat@718 1002 }
djmoffat@718 1003 });
djmoffat@718 1004 };
djmoffat@718 1005 request.send();
djmoffat@718 1006 };
djmoffat@718 1007
djmoffat@718 1008 this.exportXMLDOM = function() {
djmoffat@718 1009 var root = document.createElement('audioElement');
djmoffat@718 1010 root.id = this.specification.id;
djmoffat@718 1011 root.setAttribute('url',this.url);
djmoffat@718 1012 var file = document.createElement('file');
djmoffat@718 1013 file.setAttribute('sampleRate',this.buffer.sampleRate);
djmoffat@718 1014 file.setAttribute('channels',this.buffer.numberOfChannels);
djmoffat@718 1015 file.setAttribute('sampleCount',this.buffer.length);
djmoffat@718 1016 file.setAttribute('duration',this.buffer.duration);
djmoffat@718 1017 root.appendChild(file);
djmoffat@718 1018 if (this.specification.type != 'outsidereference') {
djmoffat@718 1019 root.appendChild(this.interfaceDOM.exportXMLDOM(this));
djmoffat@718 1020 root.appendChild(this.commentDOM.exportXMLDOM(this));
djmoffat@718 1021 if(this.specification.type == 'anchor') {
djmoffat@718 1022 root.setAttribute('anchor',true);
djmoffat@718 1023 } else if(this.specification.type == 'reference') {
djmoffat@718 1024 root.setAttribute('reference',true);
djmoffat@718 1025 }
djmoffat@718 1026 }
djmoffat@718 1027 root.appendChild(this.metric.exportXMLDOM());
djmoffat@718 1028 return root;
djmoffat@718 1029 };
djmoffat@718 1030 }
djmoffat@718 1031
djmoffat@718 1032 function timer()
djmoffat@718 1033 {
djmoffat@718 1034 /* Timer object used in audioEngine to keep track of session timings
djmoffat@718 1035 * Uses the timer of the web audio API, so sample resolution
djmoffat@718 1036 */
djmoffat@718 1037 this.testStarted = false;
djmoffat@718 1038 this.testStartTime = 0;
djmoffat@718 1039 this.testDuration = 0;
djmoffat@718 1040 this.minimumTestTime = 0; // No minimum test time
djmoffat@718 1041 this.startTest = function()
djmoffat@718 1042 {
djmoffat@718 1043 if (this.testStarted == false)
djmoffat@718 1044 {
djmoffat@718 1045 this.testStartTime = audioContext.currentTime;
djmoffat@718 1046 this.testStarted = true;
djmoffat@718 1047 this.updateTestTime();
djmoffat@718 1048 audioEngineContext.metric.initialiseTest();
djmoffat@718 1049 }
djmoffat@718 1050 };
djmoffat@718 1051 this.stopTest = function()
djmoffat@718 1052 {
djmoffat@718 1053 if (this.testStarted)
djmoffat@718 1054 {
djmoffat@718 1055 this.testDuration = this.getTestTime();
djmoffat@718 1056 this.testStarted = false;
djmoffat@718 1057 } else {
djmoffat@718 1058 console.log('ERR: Test tried to end before beginning');
djmoffat@718 1059 }
djmoffat@718 1060 };
djmoffat@718 1061 this.updateTestTime = function()
djmoffat@718 1062 {
djmoffat@718 1063 if (this.testStarted)
djmoffat@718 1064 {
djmoffat@718 1065 this.testDuration = audioContext.currentTime - this.testStartTime;
djmoffat@718 1066 }
djmoffat@718 1067 };
djmoffat@718 1068 this.getTestTime = function()
djmoffat@718 1069 {
djmoffat@718 1070 this.updateTestTime();
djmoffat@718 1071 return this.testDuration;
djmoffat@718 1072 };
djmoffat@718 1073 }
djmoffat@718 1074
djmoffat@718 1075 function sessionMetrics(engine)
djmoffat@718 1076 {
djmoffat@718 1077 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
djmoffat@718 1078 */
djmoffat@718 1079 this.engine = engine;
djmoffat@718 1080 this.lastClicked = -1;
djmoffat@718 1081 this.data = -1;
djmoffat@718 1082 this.reset = function() {
djmoffat@718 1083 this.lastClicked = -1;
djmoffat@718 1084 this.data = -1;
djmoffat@718 1085 };
djmoffat@718 1086 this.initialiseTest = function(){};
djmoffat@718 1087 }
djmoffat@718 1088
djmoffat@718 1089 function metricTracker(caller)
djmoffat@718 1090 {
djmoffat@718 1091 /* Custom object to track and collect metric data
djmoffat@718 1092 * Used only inside the audioObjects object.
djmoffat@718 1093 */
djmoffat@718 1094
djmoffat@718 1095 this.listenedTimer = 0;
djmoffat@718 1096 this.listenStart = 0;
djmoffat@718 1097 this.listenHold = false;
djmoffat@718 1098 this.initialPosition = -1;
djmoffat@718 1099 this.movementTracker = [];
djmoffat@718 1100 this.listenTracker =[];
djmoffat@718 1101 this.wasListenedTo = false;
djmoffat@718 1102 this.wasMoved = false;
djmoffat@718 1103 this.hasComments = false;
djmoffat@718 1104 this.parent = caller;
djmoffat@718 1105
djmoffat@718 1106 this.initialised = function(position)
djmoffat@718 1107 {
djmoffat@718 1108 if (this.initialPosition == -1) {
djmoffat@718 1109 this.initialPosition = position;
djmoffat@718 1110 }
djmoffat@718 1111 };
djmoffat@718 1112
djmoffat@718 1113 this.moved = function(time,position)
djmoffat@718 1114 {
djmoffat@718 1115 this.wasMoved = true;
djmoffat@718 1116 this.movementTracker[this.movementTracker.length] = [time, position];
djmoffat@718 1117 };
djmoffat@718 1118
djmoffat@718 1119 this.startListening = function(time)
djmoffat@718 1120 {
djmoffat@718 1121 if (this.listenHold == false)
djmoffat@718 1122 {
djmoffat@718 1123 this.wasListenedTo = true;
djmoffat@718 1124 this.listenStart = time;
djmoffat@718 1125 this.listenHold = true;
djmoffat@718 1126
djmoffat@718 1127 var evnt = document.createElement('event');
djmoffat@718 1128 var testTime = document.createElement('testTime');
djmoffat@718 1129 testTime.setAttribute('start',time);
djmoffat@718 1130 var bufferTime = document.createElement('bufferTime');
djmoffat@718 1131 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
djmoffat@718 1132 evnt.appendChild(testTime);
djmoffat@718 1133 evnt.appendChild(bufferTime);
djmoffat@718 1134 this.listenTracker.push(evnt);
djmoffat@718 1135
djmoffat@718 1136 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
djmoffat@718 1137 }
djmoffat@718 1138 };
djmoffat@718 1139
djmoffat@718 1140 this.stopListening = function(time,bufferStopTime)
djmoffat@718 1141 {
djmoffat@718 1142 if (this.listenHold == true)
djmoffat@718 1143 {
djmoffat@718 1144 var diff = time - this.listenStart;
djmoffat@718 1145 this.listenedTimer += (diff);
djmoffat@718 1146 this.listenStart = 0;
djmoffat@718 1147 this.listenHold = false;
djmoffat@718 1148
djmoffat@718 1149 var evnt = this.listenTracker[this.listenTracker.length-1];
djmoffat@718 1150 var testTime = evnt.getElementsByTagName('testTime')[0];
djmoffat@718 1151 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
djmoffat@718 1152 testTime.setAttribute('stop',time);
djmoffat@718 1153 if (bufferStopTime == undefined) {
djmoffat@718 1154 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
djmoffat@718 1155 } else {
djmoffat@718 1156 bufferTime.setAttribute('stop',bufferStopTime);
djmoffat@718 1157 }
djmoffat@718 1158 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
djmoffat@718 1159 }
djmoffat@718 1160 };
djmoffat@718 1161
djmoffat@718 1162 this.exportXMLDOM = function() {
djmoffat@718 1163 var root = document.createElement('metric');
djmoffat@718 1164 if (audioEngineContext.metric.enableElementTimer) {
djmoffat@718 1165 var mElementTimer = document.createElement('metricresult');
djmoffat@718 1166 mElementTimer.setAttribute('name','enableElementTimer');
djmoffat@718 1167 mElementTimer.textContent = this.listenedTimer;
djmoffat@718 1168 root.appendChild(mElementTimer);
djmoffat@718 1169 }
djmoffat@718 1170 if (audioEngineContext.metric.enableElementTracker) {
djmoffat@718 1171 var elementTrackerFull = document.createElement('metricResult');
djmoffat@718 1172 elementTrackerFull.setAttribute('name','elementTrackerFull');
djmoffat@718 1173 for (var k=0; k<this.movementTracker.length; k++)
djmoffat@718 1174 {
djmoffat@718 1175 var timePos = document.createElement('timePos');
djmoffat@718 1176 timePos.id = k;
djmoffat@718 1177 var time = document.createElement('time');
djmoffat@718 1178 time.textContent = this.movementTracker[k][0];
djmoffat@718 1179 var position = document.createElement('position');
djmoffat@718 1180 position.textContent = this.movementTracker[k][1];
djmoffat@718 1181 timePos.appendChild(time);
djmoffat@718 1182 timePos.appendChild(position);
djmoffat@718 1183 elementTrackerFull.appendChild(timePos);
djmoffat@718 1184 }
djmoffat@718 1185 root.appendChild(elementTrackerFull);
djmoffat@718 1186 }
djmoffat@718 1187 if (audioEngineContext.metric.enableElementListenTracker) {
djmoffat@718 1188 var elementListenTracker = document.createElement('metricResult');
djmoffat@718 1189 elementListenTracker.setAttribute('name','elementListenTracker');
djmoffat@718 1190 for (var k=0; k<this.listenTracker.length; k++) {
djmoffat@718 1191 elementListenTracker.appendChild(this.listenTracker[k]);
djmoffat@718 1192 }
djmoffat@718 1193 root.appendChild(elementListenTracker);
djmoffat@718 1194 }
djmoffat@718 1195 if (audioEngineContext.metric.enableElementInitialPosition) {
djmoffat@718 1196 var elementInitial = document.createElement('metricResult');
djmoffat@718 1197 elementInitial.setAttribute('name','elementInitialPosition');
djmoffat@718 1198 elementInitial.textContent = this.initialPosition;
djmoffat@718 1199 root.appendChild(elementInitial);
djmoffat@718 1200 }
djmoffat@718 1201 if (audioEngineContext.metric.enableFlagListenedTo) {
djmoffat@718 1202 var flagListenedTo = document.createElement('metricResult');
djmoffat@718 1203 flagListenedTo.setAttribute('name','elementFlagListenedTo');
djmoffat@718 1204 flagListenedTo.textContent = this.wasListenedTo;
djmoffat@718 1205 root.appendChild(flagListenedTo);
djmoffat@718 1206 }
djmoffat@718 1207 if (audioEngineContext.metric.enableFlagMoved) {
djmoffat@718 1208 var flagMoved = document.createElement('metricResult');
djmoffat@718 1209 flagMoved.setAttribute('name','elementFlagMoved');
djmoffat@718 1210 flagMoved.textContent = this.wasMoved;
djmoffat@718 1211 root.appendChild(flagMoved);
djmoffat@718 1212 }
djmoffat@718 1213 if (audioEngineContext.metric.enableFlagComments) {
djmoffat@718 1214 var flagComments = document.createElement('metricResult');
djmoffat@718 1215 flagComments.setAttribute('name','elementFlagComments');
djmoffat@718 1216 if (this.parent.commentDOM == null)
djmoffat@718 1217 {flag.textContent = 'false';}
djmoffat@718 1218 else if (this.parent.commentDOM.textContent.length == 0)
djmoffat@718 1219 {flag.textContent = 'false';}
djmoffat@718 1220 else
djmoffat@718 1221 {flag.textContet = 'true';}
djmoffat@718 1222 root.appendChild(flagComments);
djmoffat@718 1223 }
djmoffat@718 1224
djmoffat@718 1225 return root;
djmoffat@718 1226 };
djmoffat@718 1227 }
djmoffat@718 1228
djmoffat@718 1229 function randomiseOrder(input)
djmoffat@718 1230 {
djmoffat@718 1231 // This takes an array of information and randomises the order
djmoffat@718 1232 var N = input.length;
djmoffat@718 1233
djmoffat@718 1234 var inputSequence = []; // For safety purposes: keep track of randomisation
djmoffat@718 1235 for (var counter = 0; counter < N; ++counter)
djmoffat@718 1236 inputSequence.push(counter) // Fill array
djmoffat@718 1237 var inputSequenceClone = inputSequence.slice(0);
djmoffat@718 1238
djmoffat@718 1239 var holdArr = [];
djmoffat@718 1240 var outputSequence = [];
djmoffat@718 1241 for (var n=0; n<N; n++)
djmoffat@718 1242 {
djmoffat@718 1243 // First pick a random number
djmoffat@718 1244 var r = Math.random();
djmoffat@718 1245 // Multiply and floor by the number of elements left
djmoffat@718 1246 r = Math.floor(r*input.length);
djmoffat@718 1247 // Pick out that element and delete from the array
djmoffat@718 1248 holdArr.push(input.splice(r,1)[0]);
djmoffat@718 1249 // Do the same with sequence
djmoffat@718 1250 outputSequence.push(inputSequence.splice(r,1)[0]);
djmoffat@718 1251 }
djmoffat@718 1252 console.log(inputSequenceClone.toString()); // print original array to console
djmoffat@718 1253 console.log(outputSequence.toString()); // print randomised array to console
djmoffat@718 1254 return holdArr;
djmoffat@718 1255 }
djmoffat@718 1256
djmoffat@718 1257 function returnDateNode()
djmoffat@718 1258 {
djmoffat@718 1259 // Create an XML Node for the Date and Time a test was conducted
djmoffat@718 1260 // Structure is
djmoffat@718 1261 // <datetime>
djmoffat@718 1262 // <date year="##" month="##" day="##">DD/MM/YY</date>
djmoffat@718 1263 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
djmoffat@718 1264 // </datetime>
djmoffat@718 1265 var dateTime = new Date();
djmoffat@718 1266 var year = document.createAttribute('year');
djmoffat@718 1267 var month = document.createAttribute('month');
djmoffat@718 1268 var day = document.createAttribute('day');
djmoffat@718 1269 var hour = document.createAttribute('hour');
djmoffat@718 1270 var minute = document.createAttribute('minute');
djmoffat@718 1271 var secs = document.createAttribute('secs');
djmoffat@718 1272
djmoffat@718 1273 year.nodeValue = dateTime.getFullYear();
djmoffat@718 1274 month.nodeValue = dateTime.getMonth()+1;
djmoffat@718 1275 day.nodeValue = dateTime.getDate();
djmoffat@718 1276 hour.nodeValue = dateTime.getHours();
djmoffat@718 1277 minute.nodeValue = dateTime.getMinutes();
djmoffat@718 1278 secs.nodeValue = dateTime.getSeconds();
djmoffat@718 1279
djmoffat@718 1280 var hold = document.createElement("datetime");
djmoffat@718 1281 var date = document.createElement("date");
djmoffat@718 1282 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
djmoffat@718 1283 var time = document.createElement("time");
djmoffat@718 1284 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
djmoffat@718 1285
djmoffat@718 1286 date.setAttributeNode(year);
djmoffat@718 1287 date.setAttributeNode(month);
djmoffat@718 1288 date.setAttributeNode(day);
djmoffat@718 1289 time.setAttributeNode(hour);
djmoffat@718 1290 time.setAttributeNode(minute);
djmoffat@718 1291 time.setAttributeNode(secs);
djmoffat@718 1292
djmoffat@718 1293 hold.appendChild(date);
djmoffat@718 1294 hold.appendChild(time);
djmoffat@718 1295 return hold
djmoffat@718 1296
djmoffat@718 1297 }
djmoffat@718 1298
djmoffat@718 1299 function Specification() {
djmoffat@718 1300 // Handles the decoding of the project specification XML into a simple JavaScript Object.
djmoffat@718 1301
djmoffat@718 1302 this.interfaceType;
djmoffat@718 1303 this.commonInterface;
djmoffat@718 1304 this.projectReturn;
djmoffat@718 1305 this.randomiseOrder;
djmoffat@718 1306 this.collectMetrics;
djmoffat@718 1307 this.testPages;
djmoffat@718 1308 this.preTest;
djmoffat@718 1309 this.postTest;
djmoffat@718 1310 this.metrics =[];
djmoffat@718 1311
djmoffat@718 1312 this.audioHolders = [];
djmoffat@718 1313
djmoffat@718 1314 this.decode = function() {
djmoffat@718 1315 // projectXML - DOM Parsed document
djmoffat@718 1316 this.projectXML = projectXML.childNodes[0];
djmoffat@718 1317 var setupNode = projectXML.getElementsByTagName('setup')[0];
djmoffat@718 1318 this.interfaceType = setupNode.getAttribute('interface');
djmoffat@718 1319 this.projectReturn = setupNode.getAttribute('projectReturn');
djmoffat@718 1320 this.testPages = setupNode.getAttribute('testPages');
djmoffat@718 1321 if (setupNode.getAttribute('randomiseOrder') == "true") {
djmoffat@718 1322 this.randomiseOrder = true;
djmoffat@718 1323 } else {this.randomiseOrder = false;}
djmoffat@718 1324 if (setupNode.getAttribute('collectMetrics') == "true") {
djmoffat@718 1325 this.collectMetrics = true;
djmoffat@718 1326 } else {this.collectMetrics = false;}
djmoffat@718 1327 if (isNaN(Number(this.testPages)) || this.testPages == undefined)
djmoffat@718 1328 {
djmoffat@718 1329 this.testPages = null;
djmoffat@718 1330 } else {
djmoffat@718 1331 this.testPages = Number(this.testPages);
djmoffat@718 1332 if (this.testPages == 0) {this.testPages = null;}
djmoffat@718 1333 }
djmoffat@718 1334 var metricCollection = setupNode.getElementsByTagName('Metric');
djmoffat@718 1335
djmoffat@718 1336 this.preTest = new this.prepostNode('pretest',setupNode.getElementsByTagName('PreTest'));
djmoffat@718 1337 this.postTest = new this.prepostNode('posttest',setupNode.getElementsByTagName('PostTest'));
djmoffat@718 1338
djmoffat@718 1339 if (metricCollection.length > 0) {
djmoffat@718 1340 metricCollection = metricCollection[0].getElementsByTagName('metricEnable');
djmoffat@718 1341 for (var i=0; i<metricCollection.length; i++) {
djmoffat@718 1342 this.metrics.push(new this.metricNode(metricCollection[i].textContent));
djmoffat@718 1343 }
djmoffat@718 1344 }
djmoffat@718 1345
djmoffat@718 1346 var commonInterfaceNode = setupNode.getElementsByTagName('interface');
djmoffat@718 1347 if (commonInterfaceNode.length > 0) {
djmoffat@718 1348 commonInterfaceNode = commonInterfaceNode[0];
djmoffat@718 1349 } else {
djmoffat@718 1350 commonInterfaceNode = undefined;
djmoffat@718 1351 }
djmoffat@718 1352
djmoffat@718 1353 this.commonInterface = new function() {
djmoffat@718 1354 this.OptionNode = function(child) {
djmoffat@718 1355 this.type = child.nodeName;
djmoffat@718 1356 if (this.type == 'option')
djmoffat@718 1357 {
djmoffat@718 1358 this.name = child.getAttribute('name');
djmoffat@718 1359 }
djmoffat@718 1360 else if (this.type == 'check') {
djmoffat@718 1361 this.check = child.getAttribute('name');
djmoffat@718 1362 if (this.check == 'scalerange') {
djmoffat@718 1363 this.min = child.getAttribute('min');
djmoffat@718 1364 this.max = child.getAttribute('max');
djmoffat@718 1365 if (this.min == null) {this.min = 1;}
djmoffat@718 1366 else if (Number(this.min) > 1 && this.min != null) {
djmoffat@718 1367 this.min = Number(this.min)/100;
djmoffat@718 1368 } else {
djmoffat@718 1369 this.min = Number(this.min);
djmoffat@718 1370 }
djmoffat@718 1371 if (this.max == null) {this.max = 0;}
djmoffat@718 1372 else if (Number(this.max) > 1 && this.max != null) {
djmoffat@718 1373 this.max = Number(this.max)/100;
djmoffat@718 1374 } else {
djmoffat@718 1375 this.max = Number(this.max);
djmoffat@718 1376 }
djmoffat@718 1377 }
djmoffat@718 1378 } else if (this.type == 'anchor' || this.type == 'reference') {
djmoffat@718 1379 this.value = Number(child.textContent);
djmoffat@718 1380 this.enforce = child.getAttribute('enforce');
djmoffat@718 1381 if (this.enforce == 'true') {this.enforce = true;}
djmoffat@718 1382 else {this.enforce = false;}
djmoffat@718 1383 }
djmoffat@718 1384 };
djmoffat@718 1385 this.options = [];
djmoffat@718 1386 if (commonInterfaceNode != undefined) {
djmoffat@718 1387 var child = commonInterfaceNode.firstElementChild;
djmoffat@718 1388 while (child != undefined) {
djmoffat@718 1389 this.options.push(new this.OptionNode(child));
djmoffat@718 1390 child = child.nextElementSibling;
djmoffat@718 1391 }
djmoffat@718 1392 }
djmoffat@718 1393 };
djmoffat@718 1394
djmoffat@718 1395 var audioHolders = projectXML.getElementsByTagName('audioHolder');
djmoffat@718 1396 for (var i=0; i<audioHolders.length; i++) {
djmoffat@718 1397 this.audioHolders.push(new this.audioHolderNode(this,audioHolders[i]));
djmoffat@718 1398 }
djmoffat@718 1399
djmoffat@718 1400 // New check if we need to randomise the test order
djmoffat@718 1401 if (this.randomiseOrder)
djmoffat@718 1402 {
djmoffat@718 1403 this.audioHolders = randomiseOrder(this.audioHolders);
djmoffat@718 1404 for (var i=0; i<this.audioHolders.length; i++)
djmoffat@718 1405 {
djmoffat@718 1406 this.audioHolders[i].presentedId = i;
djmoffat@718 1407 }
djmoffat@718 1408 }
djmoffat@718 1409
djmoffat@718 1410 if (this.testPages != null || this.testPages != undefined)
djmoffat@718 1411 {
djmoffat@718 1412 if (this.testPages > audioHolders.length)
djmoffat@718 1413 {
djmoffat@718 1414 console.log('Warning: You have specified '+audioHolders.length+' tests but requested '+this.testPages+' be completed!');
djmoffat@718 1415 this.testPages = audioHolders.length;
djmoffat@718 1416 }
djmoffat@718 1417 var aH = this.audioHolders;
djmoffat@718 1418 this.audioHolders = [];
djmoffat@718 1419 for (var i=0; i<this.testPages; i++)
djmoffat@718 1420 {
djmoffat@718 1421 this.audioHolders.push(aH[i]);
djmoffat@718 1422 }
djmoffat@718 1423 }
djmoffat@718 1424 };
djmoffat@718 1425
djmoffat@718 1426 this.prepostNode = function(type,Collection) {
djmoffat@718 1427 this.type = type;
djmoffat@718 1428 this.options = [];
djmoffat@718 1429
djmoffat@718 1430 this.OptionNode = function(child) {
djmoffat@718 1431
djmoffat@718 1432 this.childOption = function(element) {
djmoffat@718 1433 this.type = 'option';
djmoffat@718 1434 this.id = element.id;
djmoffat@718 1435 this.name = element.getAttribute('name');
djmoffat@718 1436 this.text = element.textContent;
djmoffat@718 1437 };
djmoffat@718 1438
djmoffat@718 1439 this.type = child.nodeName;
djmoffat@718 1440 if (child.nodeName == "question") {
djmoffat@718 1441 this.id = child.id;
djmoffat@718 1442 this.mandatory;
djmoffat@718 1443 if (child.getAttribute('mandatory') == "true") {this.mandatory = true;}
djmoffat@718 1444 else {this.mandatory = false;}
djmoffat@718 1445 this.question = child.textContent;
djmoffat@718 1446 if (child.getAttribute('boxsize') == null) {
djmoffat@718 1447 this.boxsize = 'normal';
djmoffat@718 1448 } else {
djmoffat@718 1449 this.boxsize = child.getAttribute('boxsize');
djmoffat@718 1450 }
djmoffat@718 1451 } else if (child.nodeName == "statement") {
djmoffat@718 1452 this.statement = child.textContent;
djmoffat@718 1453 } else if (child.nodeName == "checkbox" || child.nodeName == "radio") {
djmoffat@718 1454 var element = child.firstElementChild;
djmoffat@718 1455 this.id = child.id;
djmoffat@718 1456 if (element == null) {
djmoffat@718 1457 console.log('Malformed' +child.nodeName+ 'entry');
djmoffat@718 1458 this.statement = 'Malformed' +child.nodeName+ 'entry';
djmoffat@718 1459 this.type = 'statement';
djmoffat@718 1460 } else {
djmoffat@718 1461 this.options = [];
djmoffat@718 1462 while (element != null) {
djmoffat@718 1463 if (element.nodeName == 'statement' && this.statement == undefined){
djmoffat@718 1464 this.statement = element.textContent;
djmoffat@718 1465 } else if (element.nodeName == 'option') {
djmoffat@718 1466 this.options.push(new this.childOption(element));
djmoffat@718 1467 }
djmoffat@718 1468 element = element.nextElementSibling;
djmoffat@718 1469 }
djmoffat@718 1470 }
djmoffat@718 1471 } else if (child.nodeName == "number") {
djmoffat@718 1472 this.statement = child.textContent;
djmoffat@718 1473 this.id = child.id;
djmoffat@718 1474 this.min = child.getAttribute('min');
djmoffat@718 1475 this.max = child.getAttribute('max');
djmoffat@718 1476 this.step = child.getAttribute('step');
djmoffat@718 1477 }
djmoffat@718 1478 };
djmoffat@718 1479
djmoffat@718 1480 // On construction:
djmoffat@718 1481 if (Collection.length != 0) {
djmoffat@718 1482 Collection = Collection[0];
djmoffat@718 1483 if (Collection.childElementCount != 0) {
djmoffat@718 1484 var child = Collection.firstElementChild;
djmoffat@718 1485 this.options.push(new this.OptionNode(child));
djmoffat@718 1486 while (child.nextElementSibling != null) {
djmoffat@718 1487 child = child.nextElementSibling;
djmoffat@718 1488 this.options.push(new this.OptionNode(child));
djmoffat@718 1489 }
djmoffat@718 1490 }
djmoffat@718 1491 }
djmoffat@718 1492 };
djmoffat@718 1493
djmoffat@718 1494 this.metricNode = function(name) {
djmoffat@718 1495 this.enabled = name;
djmoffat@718 1496 };
djmoffat@718 1497
djmoffat@718 1498 this.audioHolderNode = function(parent,xml) {
djmoffat@718 1499 this.type = 'audioHolder';
djmoffat@718 1500 this.presentedId = parent.audioHolders.length;
djmoffat@718 1501 this.interfaceNode = function(DOM) {
djmoffat@718 1502 var title = DOM.getElementsByTagName('title');
djmoffat@718 1503 if (title.length == 0) {this.title = null;}
djmoffat@718 1504 else {this.title = title[0].textContent;}
djmoffat@718 1505 this.options = parent.commonInterface.options;
djmoffat@718 1506 var scale = DOM.getElementsByTagName('scale');
djmoffat@718 1507 this.scale = [];
djmoffat@718 1508 for (var i=0; i<scale.length; i++) {
djmoffat@718 1509 var arr = [null, null];
djmoffat@718 1510 arr[0] = scale[i].getAttribute('position');
djmoffat@718 1511 arr[1] = scale[i].textContent;
djmoffat@718 1512 this.scale.push(arr);
djmoffat@718 1513 }
djmoffat@718 1514 };
djmoffat@718 1515
djmoffat@718 1516 this.audioElementNode = function(parent,xml) {
djmoffat@718 1517 this.url = xml.getAttribute('url');
djmoffat@718 1518 this.id = xml.id;
djmoffat@718 1519 this.parent = parent;
djmoffat@718 1520 this.type = xml.getAttribute('type');
djmoffat@718 1521 if (this.type == null) {this.type = "normal";}
djmoffat@718 1522 if (this.type == 'anchor') {this.anchor = true;}
djmoffat@718 1523 else {this.anchor = false;}
djmoffat@718 1524 if (this.type == 'reference') {this.reference = true;}
djmoffat@718 1525 else {this.reference = false;}
djmoffat@718 1526
djmoffat@718 1527 this.marker = xml.getAttribute('marker');
djmoffat@718 1528 if (this.marker == null) {this.marker = undefined;}
djmoffat@718 1529
djmoffat@718 1530 if (this.anchor == true) {
djmoffat@718 1531 if (this.marker != undefined) {this.enforce = true;}
djmoffat@718 1532 else {this.enforce = enforceAnchor;}
djmoffat@718 1533 this.marker = anchor;
djmoffat@718 1534 }
djmoffat@718 1535 else if (this.reference == true) {
djmoffat@718 1536 if (this.marker != undefined) {this.enforce = true;}
djmoffat@718 1537 else {this.enforce = enforceReference;}
djmoffat@718 1538 this.marker = reference;
djmoffat@718 1539 }
djmoffat@718 1540
djmoffat@718 1541 if (this.marker != undefined) {
djmoffat@718 1542 this.marker = Number(this.marker);
djmoffat@718 1543 if (this.marker > 1) {this.marker /= 100;}
djmoffat@718 1544 }
djmoffat@718 1545 };
djmoffat@718 1546
djmoffat@718 1547 this.commentQuestionNode = function(xml) {
djmoffat@718 1548 this.childOption = function(element) {
djmoffat@718 1549 this.type = 'option';
djmoffat@718 1550 this.name = element.getAttribute('name');
djmoffat@718 1551 this.text = element.textContent;
djmoffat@718 1552 };
djmoffat@718 1553 this.id = xml.id;
djmoffat@718 1554 if (xml.getAttribute('mandatory') == 'true') {this.mandatory = true;}
djmoffat@718 1555 else {this.mandatory = false;}
djmoffat@718 1556 this.type = xml.getAttribute('type');
djmoffat@718 1557 if (this.type == undefined) {this.type = 'text';}
djmoffat@718 1558 switch (this.type) {
djmoffat@718 1559 case 'text':
djmoffat@718 1560 this.question = xml.textContent;
djmoffat@718 1561 break;
djmoffat@718 1562 case 'radio':
djmoffat@718 1563 var child = xml.firstElementChild;
djmoffat@718 1564 this.options = [];
djmoffat@718 1565 while (child != undefined) {
djmoffat@718 1566 if (child.nodeName == 'statement' && this.statement == undefined) {
djmoffat@718 1567 this.statement = child.textContent;
djmoffat@718 1568 } else if (child.nodeName == 'option') {
djmoffat@718 1569 this.options.push(new this.childOption(child));
djmoffat@718 1570 }
djmoffat@718 1571 child = child.nextElementSibling;
djmoffat@718 1572 }
djmoffat@718 1573 break;
djmoffat@718 1574 case 'checkbox':
djmoffat@718 1575 var child = xml.firstElementChild;
djmoffat@718 1576 this.options = [];
djmoffat@718 1577 while (child != undefined) {
djmoffat@718 1578 if (child.nodeName == 'statement' && this.statement == undefined) {
djmoffat@718 1579 this.statement = child.textContent;
djmoffat@718 1580 } else if (child.nodeName == 'option') {
djmoffat@718 1581 this.options.push(new this.childOption(child));
djmoffat@718 1582 }
djmoffat@718 1583 child = child.nextElementSibling;
djmoffat@718 1584 }
djmoffat@718 1585 break;
djmoffat@718 1586 }
djmoffat@718 1587 };
djmoffat@718 1588
djmoffat@718 1589 this.id = xml.id;
djmoffat@718 1590 this.hostURL = xml.getAttribute('hostURL');
djmoffat@718 1591 this.sampleRate = xml.getAttribute('sampleRate');
djmoffat@718 1592 if (xml.getAttribute('randomiseOrder') == "true") {this.randomiseOrder = true;}
djmoffat@718 1593 else {this.randomiseOrder = false;}
djmoffat@718 1594 this.repeatCount = xml.getAttribute('repeatCount');
djmoffat@718 1595 if (xml.getAttribute('loop') == 'true') {this.loop = true;}
djmoffat@718 1596 else {this.loop == false;}
djmoffat@718 1597 if (xml.getAttribute('elementComments') == "true") {this.elementComments = true;}
djmoffat@718 1598 else {this.elementComments = false;}
djmoffat@718 1599
djmoffat@718 1600 var anchor = xml.getElementsByTagName('anchor');
djmoffat@718 1601 var enforceAnchor = false;
djmoffat@718 1602 if (anchor.length == 0) {
djmoffat@718 1603 // Find anchor in commonInterface;
djmoffat@718 1604 for (var i=0; i<parent.commonInterface.options.length; i++) {
djmoffat@718 1605 if(parent.commonInterface.options[i].type == 'anchor') {
djmoffat@718 1606 anchor = parent.commonInterface.options[i].value;
djmoffat@718 1607 enforceAnchor = parent.commonInterface.options[i].enforce;
djmoffat@718 1608 break;
djmoffat@718 1609 }
djmoffat@718 1610 }
djmoffat@718 1611 if (typeof(anchor) == "object") {
djmoffat@718 1612 anchor = null;
djmoffat@718 1613 }
djmoffat@718 1614 } else {
djmoffat@718 1615 anchor = anchor[0].textContent;
djmoffat@718 1616 }
djmoffat@718 1617
djmoffat@718 1618 var reference = xml.getElementsByTagName('anchor');
djmoffat@718 1619 var enforceReference = false;
djmoffat@718 1620 if (reference.length == 0) {
djmoffat@718 1621 // Find anchor in commonInterface;
djmoffat@718 1622 for (var i=0; i<parent.commonInterface.options.length; i++) {
djmoffat@718 1623 if(parent.commonInterface.options[i].type == 'reference') {
djmoffat@718 1624 reference = parent.commonInterface.options[i].value;
djmoffat@718 1625 enforceReference = parent.commonInterface.options[i].enforce;
djmoffat@718 1626 break;
djmoffat@718 1627 }
djmoffat@718 1628 }
djmoffat@718 1629 if (typeof(reference) == "object") {
djmoffat@718 1630 reference = null;
djmoffat@718 1631 }
djmoffat@718 1632 } else {
djmoffat@718 1633 reference = reference[0].textContent;
djmoffat@718 1634 }
djmoffat@718 1635
djmoffat@718 1636 if (typeof(anchor) == 'number') {
djmoffat@718 1637 if (anchor > 1 && anchor < 100) {anchor /= 100.0;}
djmoffat@718 1638 }
djmoffat@718 1639
djmoffat@718 1640 if (typeof(reference) == 'number') {
djmoffat@718 1641 if (reference > 1 && reference < 100) {reference /= 100.0;}
djmoffat@718 1642 }
djmoffat@718 1643
djmoffat@718 1644 this.preTest = new parent.prepostNode('pretest',xml.getElementsByTagName('PreTest'));
djmoffat@718 1645 this.postTest = new parent.prepostNode('posttest',xml.getElementsByTagName('PostTest'));
djmoffat@718 1646
djmoffat@718 1647 this.interfaces = [];
djmoffat@718 1648 var interfaceDOM = xml.getElementsByTagName('interface');
djmoffat@718 1649 for (var i=0; i<interfaceDOM.length; i++) {
djmoffat@718 1650 this.interfaces.push(new this.interfaceNode(interfaceDOM[i]));
djmoffat@718 1651 }
djmoffat@718 1652
djmoffat@718 1653 this.commentBoxPrefix = xml.getElementsByTagName('commentBoxPrefix');
djmoffat@718 1654 if (this.commentBoxPrefix.length != 0) {
djmoffat@718 1655 this.commentBoxPrefix = this.commentBoxPrefix[0].textContent;
djmoffat@718 1656 } else {
djmoffat@718 1657 this.commentBoxPrefix = "Comment on track";
djmoffat@718 1658 }
djmoffat@718 1659
djmoffat@718 1660 this.audioElements =[];
djmoffat@718 1661 var audioElementsDOM = xml.getElementsByTagName('audioElements');
djmoffat@718 1662 this.outsideReference = null;
djmoffat@718 1663 for (var i=0; i<audioElementsDOM.length; i++) {
djmoffat@718 1664 if (audioElementsDOM[i].getAttribute('type') == 'outsidereference') {
djmoffat@718 1665 if (this.outsideReference == null) {
djmoffat@718 1666 this.outsideReference = new this.audioElementNode(this,audioElementsDOM[i]);
djmoffat@718 1667 } else {
djmoffat@718 1668 console.log('Error only one audioelement can be of type outsidereference per audioholder');
djmoffat@718 1669 this.audioElements.push(new this.audioElementNode(this,audioElementsDOM[i]));
djmoffat@718 1670 console.log('Element id '+audioElementsDOM[i].id+' made into normal node');
djmoffat@718 1671 }
djmoffat@718 1672 } else {
djmoffat@718 1673 this.audioElements.push(new this.audioElementNode(this,audioElementsDOM[i]));
djmoffat@718 1674 }
djmoffat@718 1675 }
djmoffat@718 1676
djmoffat@718 1677 if (this.randomiseOrder) {
djmoffat@718 1678 this.audioElements = randomiseOrder(this.audioElements);
djmoffat@718 1679 }
djmoffat@718 1680
djmoffat@718 1681 // Check only one anchor and one reference per audioNode
djmoffat@718 1682 var anchor = [];
djmoffat@718 1683 var reference = [];
djmoffat@718 1684 this.anchorId = null;
djmoffat@718 1685 this.referenceId = null;
djmoffat@718 1686 for (var i=0; i<this.audioElements.length; i++)
djmoffat@718 1687 {
djmoffat@718 1688 if (this.audioElements[i].anchor == true) {anchor.push(i);}
djmoffat@718 1689 if (this.audioElements[i].reference == true) {reference.push(i);}
djmoffat@718 1690 }
djmoffat@718 1691
djmoffat@718 1692 if (anchor.length > 1) {
djmoffat@718 1693 console.log('Error - cannot have more than one anchor!');
djmoffat@718 1694 console.log('Each anchor node will be a normal mode to continue the test');
djmoffat@718 1695 for (var i=0; i<anchor.length; i++)
djmoffat@718 1696 {
djmoffat@718 1697 this.audioElements[anchor[i]].anchor = false;
djmoffat@718 1698 this.audioElements[anchor[i]].value = undefined;
djmoffat@718 1699 }
djmoffat@718 1700 } else {this.anchorId = anchor[0];}
djmoffat@718 1701 if (reference.length > 1) {
djmoffat@718 1702 console.log('Error - cannot have more than one anchor!');
djmoffat@718 1703 console.log('Each anchor node will be a normal mode to continue the test');
djmoffat@718 1704 for (var i=0; i<reference.length; i++)
djmoffat@718 1705 {
djmoffat@718 1706 this.audioElements[reference[i]].reference = false;
djmoffat@718 1707 this.audioElements[reference[i]].value = undefined;
djmoffat@718 1708 }
djmoffat@718 1709 } else {this.referenceId = reference[0];}
djmoffat@718 1710
djmoffat@718 1711 this.commentQuestions = [];
djmoffat@718 1712 var commentQuestionsDOM = xml.getElementsByTagName('CommentQuestion');
djmoffat@718 1713 for (var i=0; i<commentQuestionsDOM.length; i++) {
djmoffat@718 1714 this.commentQuestions.push(new this.commentQuestionNode(commentQuestionsDOM[i]));
djmoffat@718 1715 }
djmoffat@718 1716 };
djmoffat@718 1717 }
djmoffat@718 1718
djmoffat@718 1719 function Interface(specificationObject) {
djmoffat@718 1720 // This handles the bindings between the interface and the audioEngineContext;
djmoffat@718 1721 this.specification = specificationObject;
djmoffat@718 1722 this.insertPoint = document.getElementById("topLevelBody");
djmoffat@718 1723
djmoffat@718 1724 // Bounded by interface!!
djmoffat@718 1725 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
djmoffat@718 1726 // For example, APE returns the slider position normalised in a <value> tag.
djmoffat@718 1727 this.interfaceObjects = [];
djmoffat@718 1728 this.interfaceObject = function(){};
djmoffat@718 1729
djmoffat@718 1730 this.resizeWindow = function(event)
djmoffat@718 1731 {
djmoffat@718 1732 for(var i=0; i<this.commentBoxes.length; i++)
djmoffat@718 1733 {this.commentBoxes[i].resize();}
djmoffat@718 1734 for(var i=0; i<this.commentQuestions.length; i++)
djmoffat@718 1735 {this.commentQuestions[i].resize();}
djmoffat@718 1736 try
djmoffat@718 1737 {
djmoffat@718 1738 resizeWindow(event);
djmoffat@718 1739 }
djmoffat@718 1740 catch(err)
djmoffat@718 1741 {
djmoffat@718 1742 console.log("Warning - Interface does not have Resize option");
djmoffat@718 1743 console.log(err);
djmoffat@718 1744 }
djmoffat@718 1745 };
djmoffat@718 1746
djmoffat@718 1747 this.commentBoxes = [];
djmoffat@718 1748 this.elementCommentBox = function(audioObject) {
djmoffat@718 1749 var element = audioObject.specification;
djmoffat@718 1750 this.audioObject = audioObject;
djmoffat@718 1751 this.id = audioObject.id;
djmoffat@718 1752 var audioHolderObject = audioObject.specification.parent;
djmoffat@718 1753 // Create document objects to hold the comment boxes
djmoffat@718 1754 this.trackComment = document.createElement('div');
djmoffat@718 1755 this.trackComment.className = 'comment-div';
djmoffat@718 1756 this.trackComment.id = 'comment-div-'+audioObject.id;
djmoffat@718 1757 // Create a string next to each comment asking for a comment
djmoffat@718 1758 this.trackString = document.createElement('span');
djmoffat@718 1759 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.id;
djmoffat@718 1760 // Create the HTML5 comment box 'textarea'
djmoffat@718 1761 this.trackCommentBox = document.createElement('textarea');
djmoffat@718 1762 this.trackCommentBox.rows = '4';
djmoffat@718 1763 this.trackCommentBox.cols = '100';
djmoffat@718 1764 this.trackCommentBox.name = 'trackComment'+audioObject.id;
djmoffat@718 1765 this.trackCommentBox.className = 'trackComment';
djmoffat@718 1766 var br = document.createElement('br');
djmoffat@718 1767 // Add to the holder.
djmoffat@718 1768 this.trackComment.appendChild(this.trackString);
djmoffat@718 1769 this.trackComment.appendChild(br);
djmoffat@718 1770 this.trackComment.appendChild(this.trackCommentBox);
djmoffat@718 1771
djmoffat@718 1772 this.exportXMLDOM = function() {
djmoffat@718 1773 var root = document.createElement('comment');
djmoffat@718 1774 if (this.audioObject.specification.parent.elementComments) {
djmoffat@718 1775 var question = document.createElement('question');
djmoffat@718 1776 question.textContent = this.trackString.textContent;
djmoffat@718 1777 var response = document.createElement('response');
djmoffat@718 1778 response.textContent = this.trackCommentBox.value;
djmoffat@718 1779 console.log("Comment frag-"+this.id+": "+response.textContent);
djmoffat@718 1780 root.appendChild(question);
djmoffat@718 1781 root.appendChild(response);
djmoffat@718 1782 }
djmoffat@718 1783 return root;
djmoffat@718 1784 };
djmoffat@718 1785 this.resize = function()
djmoffat@718 1786 {
djmoffat@718 1787 var boxwidth = (window.innerWidth-100)/2;
djmoffat@718 1788 if (boxwidth >= 600)
djmoffat@718 1789 {
djmoffat@718 1790 boxwidth = 600;
djmoffat@718 1791 }
djmoffat@718 1792 else if (boxwidth < 400)
djmoffat@718 1793 {
djmoffat@718 1794 boxwidth = 400;
djmoffat@718 1795 }
djmoffat@718 1796 this.trackComment.style.width = boxwidth+"px";
djmoffat@718 1797 this.trackCommentBox.style.width = boxwidth-6+"px";
djmoffat@718 1798 };
djmoffat@718 1799 this.resize();
djmoffat@718 1800 };
djmoffat@718 1801
djmoffat@718 1802 this.commentQuestions = [];
djmoffat@718 1803
djmoffat@718 1804 this.commentBox = function(commentQuestion) {
djmoffat@718 1805 this.specification = commentQuestion;
djmoffat@718 1806 // Create document objects to hold the comment boxes
djmoffat@718 1807 this.holder = document.createElement('div');
djmoffat@718 1808 this.holder.className = 'comment-div';
djmoffat@718 1809 // Create a string next to each comment asking for a comment
djmoffat@718 1810 this.string = document.createElement('span');
djmoffat@718 1811 this.string.innerHTML = commentQuestion.question;
djmoffat@718 1812 // Create the HTML5 comment box 'textarea'
djmoffat@718 1813 this.textArea = document.createElement('textarea');
djmoffat@718 1814 this.textArea.rows = '4';
djmoffat@718 1815 this.textArea.cols = '100';
djmoffat@718 1816 this.textArea.className = 'trackComment';
djmoffat@718 1817 var br = document.createElement('br');
djmoffat@718 1818 // Add to the holder.
djmoffat@718 1819 this.holder.appendChild(this.string);
djmoffat@718 1820 this.holder.appendChild(br);
djmoffat@718 1821 this.holder.appendChild(this.textArea);
djmoffat@718 1822
djmoffat@718 1823 this.exportXMLDOM = function() {
djmoffat@718 1824 var root = document.createElement('comment');
djmoffat@718 1825 root.id = this.specification.id;
djmoffat@718 1826 root.setAttribute('type',this.specification.type);
djmoffat@718 1827 root.textContent = this.textArea.value;
djmoffat@718 1828 console.log("Question: "+this.string.textContent);
djmoffat@718 1829 console.log("Response: "+root.textContent);
djmoffat@718 1830 return root;
djmoffat@718 1831 };
djmoffat@718 1832 this.resize = function()
djmoffat@718 1833 {
djmoffat@718 1834 var boxwidth = (window.innerWidth-100)/2;
djmoffat@718 1835 if (boxwidth >= 600)
djmoffat@718 1836 {
djmoffat@718 1837 boxwidth = 600;
djmoffat@718 1838 }
djmoffat@718 1839 else if (boxwidth < 400)
djmoffat@718 1840 {
djmoffat@718 1841 boxwidth = 400;
djmoffat@718 1842 }
djmoffat@718 1843 this.holder.style.width = boxwidth+"px";
djmoffat@718 1844 this.textArea.style.width = boxwidth-6+"px";
djmoffat@718 1845 };
djmoffat@718 1846 this.resize();
djmoffat@718 1847 };
djmoffat@718 1848
djmoffat@718 1849 this.radioBox = function(commentQuestion) {
djmoffat@718 1850 this.specification = commentQuestion;
djmoffat@718 1851 // Create document objects to hold the comment boxes
djmoffat@718 1852 this.holder = document.createElement('div');
djmoffat@718 1853 this.holder.className = 'comment-div';
djmoffat@718 1854 // Create a string next to each comment asking for a comment
djmoffat@718 1855 this.string = document.createElement('span');
djmoffat@718 1856 this.string.innerHTML = commentQuestion.statement;
djmoffat@718 1857 var br = document.createElement('br');
djmoffat@718 1858 // Add to the holder.
djmoffat@718 1859 this.holder.appendChild(this.string);
djmoffat@718 1860 this.holder.appendChild(br);
djmoffat@718 1861 this.options = [];
djmoffat@718 1862 this.inputs = document.createElement('div');
djmoffat@718 1863 this.span = document.createElement('div');
djmoffat@718 1864 this.inputs.align = 'center';
djmoffat@718 1865 this.inputs.style.marginLeft = '12px';
djmoffat@718 1866 this.span.style.marginLeft = '12px';
djmoffat@718 1867 this.span.align = 'center';
djmoffat@718 1868 this.span.style.marginTop = '15px';
djmoffat@718 1869
djmoffat@718 1870 var optCount = commentQuestion.options.length;
djmoffat@718 1871 for (var i=0; i<optCount; i++)
djmoffat@718 1872 {
djmoffat@718 1873 var div = document.createElement('div');
djmoffat@718 1874 div.style.width = '80px';
djmoffat@718 1875 div.style.float = 'left';
djmoffat@718 1876 var input = document.createElement('input');
djmoffat@718 1877 input.type = 'radio';
djmoffat@718 1878 input.name = commentQuestion.id;
djmoffat@718 1879 input.setAttribute('setvalue',commentQuestion.options[i].name);
djmoffat@718 1880 input.className = 'comment-radio';
djmoffat@718 1881 div.appendChild(input);
djmoffat@718 1882 this.inputs.appendChild(div);
djmoffat@718 1883
djmoffat@718 1884
djmoffat@718 1885 div = document.createElement('div');
djmoffat@718 1886 div.style.width = '80px';
djmoffat@718 1887 div.style.float = 'left';
djmoffat@718 1888 div.align = 'center';
djmoffat@718 1889 var span = document.createElement('span');
djmoffat@718 1890 span.textContent = commentQuestion.options[i].text;
djmoffat@718 1891 span.className = 'comment-radio-span';
djmoffat@718 1892 div.appendChild(span);
djmoffat@718 1893 this.span.appendChild(div);
djmoffat@718 1894 this.options.push(input);
djmoffat@718 1895 }
djmoffat@718 1896 this.holder.appendChild(this.span);
djmoffat@718 1897 this.holder.appendChild(this.inputs);
djmoffat@718 1898
djmoffat@718 1899 this.exportXMLDOM = function() {
djmoffat@718 1900 var root = document.createElement('comment');
djmoffat@718 1901 root.id = this.specification.id;
djmoffat@718 1902 root.setAttribute('type',this.specification.type);
djmoffat@718 1903 var question = document.createElement('question');
djmoffat@718 1904 question.textContent = this.string.textContent;
djmoffat@718 1905 var response = document.createElement('response');
djmoffat@718 1906 var i=0;
djmoffat@718 1907 while(this.options[i].checked == false) {
djmoffat@718 1908 i++;
djmoffat@718 1909 if (i >= this.options.length) {
djmoffat@718 1910 break;
djmoffat@718 1911 }
djmoffat@718 1912 }
djmoffat@718 1913 if (i >= this.options.length) {
djmoffat@718 1914 response.textContent = 'null';
djmoffat@718 1915 } else {
djmoffat@718 1916 response.textContent = this.options[i].getAttribute('setvalue');
djmoffat@718 1917 response.setAttribute('number',i);
djmoffat@718 1918 }
djmoffat@718 1919 console.log('Comment: '+question.textContent);
djmoffat@718 1920 console.log('Response: '+response.textContent);
djmoffat@718 1921 root.appendChild(question);
djmoffat@718 1922 root.appendChild(response);
djmoffat@718 1923 return root;
djmoffat@718 1924 };
djmoffat@718 1925 this.resize = function()
djmoffat@718 1926 {
djmoffat@718 1927 var boxwidth = (window.innerWidth-100)/2;
djmoffat@718 1928 if (boxwidth >= 600)
djmoffat@718 1929 {
djmoffat@718 1930 boxwidth = 600;
djmoffat@718 1931 }
djmoffat@718 1932 else if (boxwidth < 400)
djmoffat@718 1933 {
djmoffat@718 1934 boxwidth = 400;
djmoffat@718 1935 }
djmoffat@718 1936 this.holder.style.width = boxwidth+"px";
djmoffat@718 1937 var text = this.holder.children[2];
djmoffat@718 1938 var options = this.holder.children[3];
djmoffat@718 1939 var optCount = options.children.length;
djmoffat@718 1940 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
djmoffat@718 1941 var options = options.firstChild;
djmoffat@718 1942 var text = text.firstChild;
djmoffat@718 1943 options.style.marginRight = spanMargin;
djmoffat@718 1944 options.style.marginLeft = spanMargin;
djmoffat@718 1945 text.style.marginRight = spanMargin;
djmoffat@718 1946 text.style.marginLeft = spanMargin;
djmoffat@718 1947 while(options.nextSibling != undefined)
djmoffat@718 1948 {
djmoffat@718 1949 options = options.nextSibling;
djmoffat@718 1950 text = text.nextSibling;
djmoffat@718 1951 options.style.marginRight = spanMargin;
djmoffat@718 1952 options.style.marginLeft = spanMargin;
djmoffat@718 1953 text.style.marginRight = spanMargin;
djmoffat@718 1954 text.style.marginLeft = spanMargin;
djmoffat@718 1955 }
djmoffat@718 1956 };
djmoffat@718 1957 this.resize();
djmoffat@718 1958 };
djmoffat@718 1959
djmoffat@718 1960 this.checkboxBox = function(commentQuestion) {
djmoffat@718 1961 this.specification = commentQuestion;
djmoffat@718 1962 // Create document objects to hold the comment boxes
djmoffat@718 1963 this.holder = document.createElement('div');
djmoffat@718 1964 this.holder.className = 'comment-div';
djmoffat@718 1965 // Create a string next to each comment asking for a comment
djmoffat@718 1966 this.string = document.createElement('span');
djmoffat@718 1967 this.string.innerHTML = commentQuestion.statement;
djmoffat@718 1968 var br = document.createElement('br');
djmoffat@718 1969 // Add to the holder.
djmoffat@718 1970 this.holder.appendChild(this.string);
djmoffat@718 1971 this.holder.appendChild(br);
djmoffat@718 1972 this.options = [];
djmoffat@718 1973 this.inputs = document.createElement('div');
djmoffat@718 1974 this.span = document.createElement('div');
djmoffat@718 1975 this.inputs.align = 'center';
djmoffat@718 1976 this.inputs.style.marginLeft = '12px';
djmoffat@718 1977 this.span.style.marginLeft = '12px';
djmoffat@718 1978 this.span.align = 'center';
djmoffat@718 1979 this.span.style.marginTop = '15px';
djmoffat@718 1980
djmoffat@718 1981 var optCount = commentQuestion.options.length;
djmoffat@718 1982 for (var i=0; i<optCount; i++)
djmoffat@718 1983 {
djmoffat@718 1984 var div = document.createElement('div');
djmoffat@718 1985 div.style.width = '80px';
djmoffat@718 1986 div.style.float = 'left';
djmoffat@718 1987 var input = document.createElement('input');
djmoffat@718 1988 input.type = 'checkbox';
djmoffat@718 1989 input.name = commentQuestion.id;
djmoffat@718 1990 input.setAttribute('setvalue',commentQuestion.options[i].name);
djmoffat@718 1991 input.className = 'comment-radio';
djmoffat@718 1992 div.appendChild(input);
djmoffat@718 1993 this.inputs.appendChild(div);
djmoffat@718 1994
djmoffat@718 1995
djmoffat@718 1996 div = document.createElement('div');
djmoffat@718 1997 div.style.width = '80px';
djmoffat@718 1998 div.style.float = 'left';
djmoffat@718 1999 div.align = 'center';
djmoffat@718 2000 var span = document.createElement('span');
djmoffat@718 2001 span.textContent = commentQuestion.options[i].text;
djmoffat@718 2002 span.className = 'comment-radio-span';
djmoffat@718 2003 div.appendChild(span);
djmoffat@718 2004 this.span.appendChild(div);
djmoffat@718 2005 this.options.push(input);
djmoffat@718 2006 }
djmoffat@718 2007 this.holder.appendChild(this.span);
djmoffat@718 2008 this.holder.appendChild(this.inputs);
djmoffat@718 2009
djmoffat@718 2010 this.exportXMLDOM = function() {
djmoffat@718 2011 var root = document.createElement('comment');
djmoffat@718 2012 root.id = this.specification.id;
djmoffat@718 2013 root.setAttribute('type',this.specification.type);
djmoffat@718 2014 var question = document.createElement('question');
djmoffat@718 2015 question.textContent = this.string.textContent;
djmoffat@718 2016 root.appendChild(question);
djmoffat@718 2017 console.log('Comment: '+question.textContent);
djmoffat@718 2018 for (var i=0; i<this.options.length; i++) {
djmoffat@718 2019 var response = document.createElement('response');
djmoffat@718 2020 response.textContent = this.options[i].checked;
djmoffat@718 2021 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
djmoffat@718 2022 root.appendChild(response);
djmoffat@718 2023 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
djmoffat@718 2024 }
djmoffat@718 2025 return root;
djmoffat@718 2026 };
djmoffat@718 2027 this.resize = function()
djmoffat@718 2028 {
djmoffat@718 2029 var boxwidth = (window.innerWidth-100)/2;
djmoffat@718 2030 if (boxwidth >= 600)
djmoffat@718 2031 {
djmoffat@718 2032 boxwidth = 600;
djmoffat@718 2033 }
djmoffat@718 2034 else if (boxwidth < 400)
djmoffat@718 2035 {
djmoffat@718 2036 boxwidth = 400;
djmoffat@718 2037 }
djmoffat@718 2038 this.holder.style.width = boxwidth+"px";
djmoffat@718 2039 var text = this.holder.children[2];
djmoffat@718 2040 var options = this.holder.children[3];
djmoffat@718 2041 var optCount = options.children.length;
djmoffat@718 2042 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
djmoffat@718 2043 var options = options.firstChild;
djmoffat@718 2044 var text = text.firstChild;
djmoffat@718 2045 options.style.marginRight = spanMargin;
djmoffat@718 2046 options.style.marginLeft = spanMargin;
djmoffat@718 2047 text.style.marginRight = spanMargin;
djmoffat@718 2048 text.style.marginLeft = spanMargin;
djmoffat@718 2049 while(options.nextSibling != undefined)
djmoffat@718 2050 {
djmoffat@718 2051 options = options.nextSibling;
djmoffat@718 2052 text = text.nextSibling;
djmoffat@718 2053 options.style.marginRight = spanMargin;
djmoffat@718 2054 options.style.marginLeft = spanMargin;
djmoffat@718 2055 text.style.marginRight = spanMargin;
djmoffat@718 2056 text.style.marginLeft = spanMargin;
djmoffat@718 2057 }
djmoffat@718 2058 };
djmoffat@718 2059 this.resize();
djmoffat@718 2060 };
djmoffat@718 2061
djmoffat@718 2062 this.createCommentBox = function(audioObject) {
djmoffat@718 2063 var node = new this.elementCommentBox(audioObject);
djmoffat@718 2064 this.commentBoxes.push(node);
djmoffat@718 2065 audioObject.commentDOM = node;
djmoffat@718 2066 return node;
djmoffat@718 2067 };
djmoffat@718 2068
djmoffat@718 2069 this.sortCommentBoxes = function() {
djmoffat@718 2070 var holder = [];
djmoffat@718 2071 while (this.commentBoxes.length > 0) {
djmoffat@718 2072 var node = this.commentBoxes.pop(0);
djmoffat@718 2073 holder[node.id] = node;
djmoffat@718 2074 }
djmoffat@718 2075 this.commentBoxes = holder;
djmoffat@718 2076 };
djmoffat@718 2077
djmoffat@718 2078 this.showCommentBoxes = function(inject, sort) {
djmoffat@718 2079 if (sort) {interfaceContext.sortCommentBoxes();}
djmoffat@718 2080 for (var i=0; i<interfaceContext.commentBoxes.length; i++) {
djmoffat@718 2081 inject.appendChild(this.commentBoxes[i].trackComment);
djmoffat@718 2082 }
djmoffat@718 2083 };
djmoffat@718 2084
djmoffat@718 2085 this.deleteCommentBoxes = function() {
djmoffat@718 2086 this.commentBoxes = [];
djmoffat@718 2087 };
djmoffat@718 2088
djmoffat@718 2089 this.createCommentQuestion = function(element) {
djmoffat@718 2090 var node;
djmoffat@718 2091 if (element.type == 'text') {
djmoffat@718 2092 node = new this.commentBox(element);
djmoffat@718 2093 } else if (element.type == 'radio') {
djmoffat@718 2094 node = new this.radioBox(element);
djmoffat@718 2095 } else if (element.type == 'checkbox') {
djmoffat@718 2096 node = new this.checkboxBox(element);
djmoffat@718 2097 }
djmoffat@718 2098 this.commentQuestions.push(node);
djmoffat@718 2099 return node;
djmoffat@718 2100 };
djmoffat@718 2101
djmoffat@718 2102 this.deleteCommentQuestions = function()
djmoffat@718 2103 {
djmoffat@718 2104 this.commentQuestions = [];
djmoffat@718 2105 };
djmoffat@718 2106
djmoffat@718 2107 this.playhead = new function()
djmoffat@718 2108 {
djmoffat@718 2109 this.object = document.createElement('div');
djmoffat@718 2110 this.object.className = 'playhead';
djmoffat@718 2111 this.object.align = 'left';
djmoffat@718 2112 var curTime = document.createElement('div');
djmoffat@718 2113 curTime.style.width = '50px';
djmoffat@718 2114 this.curTimeSpan = document.createElement('span');
djmoffat@718 2115 this.curTimeSpan.textContent = '00:00';
djmoffat@718 2116 curTime.appendChild(this.curTimeSpan);
djmoffat@718 2117 this.object.appendChild(curTime);
djmoffat@718 2118 this.scrubberTrack = document.createElement('div');
djmoffat@718 2119 this.scrubberTrack.className = 'playhead-scrub-track';
djmoffat@718 2120
djmoffat@718 2121 this.scrubberHead = document.createElement('div');
djmoffat@718 2122 this.scrubberHead.id = 'playhead-scrubber';
djmoffat@718 2123 this.scrubberTrack.appendChild(this.scrubberHead);
djmoffat@718 2124 this.object.appendChild(this.scrubberTrack);
djmoffat@718 2125
djmoffat@718 2126 this.timePerPixel = 0;
djmoffat@718 2127 this.maxTime = 0;
djmoffat@718 2128
djmoffat@718 2129 this.playbackObject;
djmoffat@718 2130
djmoffat@718 2131 this.setTimePerPixel = function(audioObject) {
djmoffat@718 2132 //maxTime must be in seconds
djmoffat@718 2133 this.playbackObject = audioObject;
djmoffat@718 2134 this.maxTime = audioObject.buffer.duration;
djmoffat@718 2135 var width = 490; //500 - 10, 5 each side of the tracker head
djmoffat@718 2136 this.timePerPixel = this.maxTime/490;
djmoffat@718 2137 if (this.maxTime < 60) {
djmoffat@718 2138 this.curTimeSpan.textContent = '0.00';
djmoffat@718 2139 } else {
djmoffat@718 2140 this.curTimeSpan.textContent = '00:00';
djmoffat@718 2141 }
djmoffat@718 2142 };
djmoffat@718 2143
djmoffat@718 2144 this.update = function() {
djmoffat@718 2145 // Update the playhead position, startPlay must be called
djmoffat@718 2146 if (this.timePerPixel > 0) {
djmoffat@718 2147 var time = this.playbackObject.getCurrentPosition();
djmoffat@718 2148 if (time > 0) {
djmoffat@718 2149 var width = 490;
djmoffat@718 2150 var pix = Math.floor(time/this.timePerPixel);
djmoffat@718 2151 this.scrubberHead.style.left = pix+'px';
djmoffat@718 2152 if (this.maxTime > 60.0) {
djmoffat@718 2153 var secs = time%60;
djmoffat@718 2154 var mins = Math.floor((time-secs)/60);
djmoffat@718 2155 secs = secs.toString();
djmoffat@718 2156 secs = secs.substr(0,2);
djmoffat@718 2157 mins = mins.toString();
djmoffat@718 2158 this.curTimeSpan.textContent = mins+':'+secs;
djmoffat@718 2159 } else {
djmoffat@718 2160 time = time.toString();
djmoffat@718 2161 this.curTimeSpan.textContent = time.substr(0,4);
djmoffat@718 2162 }
djmoffat@718 2163 } else {
djmoffat@718 2164 this.scrubberHead.style.left = '0px';
djmoffat@718 2165 if (this.maxTime < 60) {
djmoffat@718 2166 this.curTimeSpan.textContent = '0.00';
djmoffat@718 2167 } else {
djmoffat@718 2168 this.curTimeSpan.textContent = '00:00';
djmoffat@718 2169 }
djmoffat@718 2170 }
djmoffat@718 2171 }
djmoffat@718 2172 };
djmoffat@718 2173
djmoffat@718 2174 this.interval = undefined;
djmoffat@718 2175
djmoffat@718 2176 this.start = function() {
djmoffat@718 2177 if (this.playbackObject != undefined && this.interval == undefined) {
djmoffat@718 2178 if (this.maxTime < 60) {
djmoffat@718 2179 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
djmoffat@718 2180 } else {
djmoffat@718 2181 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
djmoffat@718 2182 }
djmoffat@718 2183 }
djmoffat@718 2184 };
djmoffat@718 2185 this.stop = function() {
djmoffat@718 2186 clearInterval(this.interval);
djmoffat@718 2187 this.interval = undefined;
djmoffat@718 2188 if (this.maxTime < 60) {
djmoffat@718 2189 this.curTimeSpan.textContent = '0.00';
djmoffat@718 2190 } else {
djmoffat@718 2191 this.curTimeSpan.textContent = '00:00';
djmoffat@718 2192 }
djmoffat@718 2193 };
djmoffat@718 2194 };
djmoffat@718 2195
djmoffat@718 2196 // Global Checkers
djmoffat@718 2197 // These functions will help enforce the checkers
djmoffat@718 2198 this.checkHiddenAnchor = function()
djmoffat@718 2199 {
djmoffat@718 2200 var audioHolder = testState.currentStateMap[testState.currentIndex];
djmoffat@718 2201 if (audioHolder.anchorId != null)
djmoffat@718 2202 {
djmoffat@718 2203 var audioObject = audioEngineContext.audioObjects[audioHolder.anchorId];
djmoffat@718 2204 if (audioObject.interfaceDOM.getValue() > audioObject.specification.marker && audioObject.interfaceDOM.enforce == true)
djmoffat@718 2205 {
djmoffat@718 2206 // Anchor is not set below
djmoffat@718 2207 console.log('Anchor node not below marker value');
djmoffat@718 2208 alert('Please keep listening');
djmoffat@718 2209 return false;
djmoffat@718 2210 }
djmoffat@718 2211 }
djmoffat@718 2212 return true;
djmoffat@718 2213 };
djmoffat@718 2214
djmoffat@718 2215 this.checkHiddenReference = function()
djmoffat@718 2216 {
djmoffat@718 2217 var audioHolder = testState.currentStateMap[testState.currentIndex];
djmoffat@718 2218 if (audioHolder.referenceId != null)
djmoffat@718 2219 {
djmoffat@718 2220 var audioObject = audioEngineContext.audioObjects[audioHolder.referenceId];
djmoffat@718 2221 if (audioObject.interfaceDOM.getValue() < audioObject.specification.marker && audioObject.interfaceDOM.enforce == true)
djmoffat@718 2222 {
djmoffat@718 2223 // Anchor is not set below
djmoffat@718 2224 console.log('Reference node not above marker value');
djmoffat@718 2225 alert('Please keep listening');
djmoffat@718 2226 return false;
djmoffat@718 2227 }
djmoffat@718 2228 }
djmoffat@718 2229 return true;
djmoffat@718 2230 };
djmoffat@718 2231 }