annotate ape.js @ 1614:4f3ddd805ff3

Bug #1226: Moved check to audioObjects and audioEngine so available for all interfaces. Also interface pop-up now states all outstanding fragments to listen.
author Nicholas Jillings <nickjillings@users.noreply.github.com>
date Sun, 24 May 2015 09:57:35 +0100
parents 207a14aac2c6
children 17561e292409
rev   line source
b@1608 1 /**
b@1608 2 * ape.js
b@1608 3 * Create the APE interface
b@1608 4 */
b@1608 5
b@1608 6 var currentState; // Keep track of the current state (pre/post test, which test, final test? first test?)
b@1608 7 // preTest - In preTest state
b@1608 8 // testRun-ID - In test running, test Id number at the end 'testRun-2'
b@1608 9 // testRunPost-ID - Post test of test ID
b@1608 10 // testRunPre-ID - Pre-test of test ID
b@1608 11 // postTest - End of test, final submission!
b@1608 12
b@1608 13
b@1608 14 // Once this is loaded and parsed, begin execution
b@1608 15 loadInterface(projectXML);
b@1608 16
b@1608 17 function loadInterface(xmlDoc) {
b@1608 18
b@1608 19 // Get the dimensions of the screen available to the page
b@1608 20 var width = window.innerWidth;
b@1608 21 var height = window.innerHeight;
b@1608 22
b@1608 23 // The injection point into the HTML page
b@1608 24 var insertPoint = document.getElementById("topLevelBody");
b@1608 25 var testContent = document.createElement('div');
b@1608 26 testContent.id = 'testContent';
b@1608 27
b@1608 28
b@1608 29 // Decode parts of the xmlDoc that are needed
b@1608 30 // xmlDoc MUST already be parsed by jQuery!
b@1608 31 var xmlSetup = xmlDoc.find('setup');
b@1608 32 // Should put in an error function here incase of malprocessed or malformed XML
b@1608 33
b@1608 34 // Extract the different test XML DOM trees
b@1608 35 var audioHolders = xmlDoc.find('audioHolder');
b@1608 36 audioHolders.each(function(index,element) {
b@1608 37 var repeatN = element.attributes['repeatCount'].value;
b@1608 38 for (var r=0; r<=repeatN; r++) {
b@1608 39 testXMLSetups[testXMLSetups.length] = element;
b@1608 40 }
b@1608 41 });
b@1608 42
b@1608 43 // New check if we need to randomise the test order
b@1608 44 var randomise = xmlSetup[0].attributes['randomiseOrder'];
b@1608 45 if (randomise != undefined) {
b@1608 46 randomise = Boolean(randomise.value);
b@1608 47 } else {
b@1608 48 randomise = false;
b@1608 49 }
b@1608 50 if (randomise)
b@1608 51 {
b@1608 52 testXMLSetups = randomiseOrder(testXMLSetups);
b@1608 53 }
b@1608 54
b@1608 55 // Obtain the metrics enabled
b@1608 56 var metricNode = xmlSetup.find('Metric');
b@1608 57 var metricNode = metricNode.find('metricEnable');
b@1608 58 metricNode.each(function(index,node){
b@1608 59 var enabled = node.textContent;
b@1608 60 switch(enabled)
b@1608 61 {
b@1608 62 case 'testTimer':
b@1608 63 sessionMetrics.prototype.enableTestTimer = true;
b@1608 64 break;
b@1608 65 case 'elementTimer':
b@1608 66 sessionMetrics.prototype.enableElementTimer = true;
b@1608 67 break;
b@1608 68 case 'elementTracker':
b@1608 69 sessionMetrics.prototype.enableElementTracker = true;
b@1608 70 break;
b@1608 71 case 'elementInitalPosition':
b@1608 72 sessionMetrics.prototype.enableElementInitialPosition = true;
b@1608 73 break;
b@1608 74 case 'elementFlagListenedTo':
b@1608 75 sessionMetrics.prototype.enableFlagListenedTo = true;
b@1608 76 break;
b@1608 77 case 'elementFlagMoved':
b@1608 78 sessionMetrics.prototype.enableFlagMoved = true;
b@1608 79 break;
b@1608 80 case 'elementFlagComments':
b@1608 81 sessionMetrics.prototype.enableFlagComments = true;
b@1608 82 break;
b@1608 83 }
b@1608 84 });
b@1608 85
b@1608 86 // Create APE specific metric functions
b@1608 87 audioEngineContext.metric.initialiseTest = function()
b@1608 88 {
b@1608 89 var sliders = document.getElementsByClassName('track-slider');
b@1608 90 for (var i=0; i<sliders.length; i++)
b@1608 91 {
b@1608 92 audioEngineContext.audioObjects[i].metric.initialised(convSliderPosToRate(i));
b@1608 93 }
b@1608 94 };
b@1608 95
b@1608 96 audioEngineContext.metric.sliderMoveStart = function(id)
b@1608 97 {
b@1608 98 if (this.data == -1)
b@1608 99 {
b@1608 100 this.data = id;
b@1608 101 } else {
b@1608 102 console.log('ERROR: Metric tracker detecting two moves!');
b@1608 103 this.data = -1;
b@1608 104 }
b@1608 105 };
b@1608 106 audioEngineContext.metric.sliderMoved = function()
b@1608 107 {
b@1608 108 var time = audioEngineContext.timer.getTestTime();
b@1608 109 var id = this.data;
b@1608 110 this.data = -1;
b@1608 111 var position = convSliderPosToRate(id);
b@1608 112 if (audioEngineContext.timer.testStarted)
b@1608 113 {
b@1608 114 audioEngineContext.audioObjects[id].metric.moved(time,position);
b@1608 115 }
b@1608 116 };
b@1608 117
b@1608 118 audioEngineContext.metric.sliderPlayed = function(id)
b@1608 119 {
b@1608 120 var time = audioEngineContext.timer.getTestTime();
b@1608 121 if (audioEngineContext.timer.testStarted)
b@1608 122 {
b@1608 123 if (this.lastClicked >= 0)
b@1608 124 {
b@1608 125 audioEngineContext.audioObjects[this.lastClicked].metric.listening(time);
b@1608 126 }
b@1608 127 this.lastClicked = id;
b@1608 128 audioEngineContext.audioObjects[id].metric.listening(time);
b@1608 129 }
b@1608 130 };
b@1608 131
b@1608 132 // Create the top div for the Title element
b@1608 133 var titleAttr = xmlSetup[0].attributes['title'];
b@1608 134 var title = document.createElement('div');
b@1608 135 title.className = "title";
b@1608 136 title.align = "center";
b@1608 137 var titleSpan = document.createElement('span');
b@1608 138
b@1608 139 // Set title to that defined in XML, else set to default
b@1608 140 if (titleAttr != undefined) {
b@1608 141 titleSpan.innerHTML = titleAttr.value;
b@1608 142 } else {
b@1608 143 titleSpan.innerHTML = 'Listening test';
b@1608 144 }
b@1608 145 // Insert the titleSpan element into the title div element.
b@1608 146 title.appendChild(titleSpan);
b@1608 147
b@1608 148 var pagetitle = document.createElement('div');
b@1608 149 pagetitle.className = "pageTitle";
b@1608 150 pagetitle.align = "center";
b@1608 151 var titleSpan = document.createElement('span');
b@1608 152 titleSpan.id = "pageTitle";
b@1608 153 pagetitle.appendChild(titleSpan);
b@1608 154
b@1608 155 // Store the return URL path in global projectReturn
b@1608 156 projectReturn = xmlSetup[0].attributes['projectReturn'].value;
b@1608 157
b@1608 158 // Create Interface buttons!
b@1608 159 var interfaceButtons = document.createElement('div');
b@1608 160 interfaceButtons.id = 'interface-buttons';
b@1608 161
b@1608 162 // MANUAL DOWNLOAD POINT
b@1608 163 // If project return is null, this MUST be specified as the location to create the download link
b@1608 164 var downloadPoint = document.createElement('div');
b@1608 165 downloadPoint.id = 'download-point';
b@1608 166
b@1608 167 // Create playback start/stop points
b@1608 168 var playback = document.createElement("button");
b@1609 169 playback.innerHTML = 'Stop';
b@1608 170 playback.id = 'playback-button';
b@1608 171 // onclick function. Check if it is playing or not, call the correct function in the
b@1608 172 // audioEngine, change the button text to reflect the next state.
b@1608 173 playback.onclick = function() {
b@1609 174 if (audioEngineContext.status == 1) {
b@1609 175 audioEngineContext.stop();
b@1608 176 this.innerHTML = 'Stop';
b@1608 177 }
b@1608 178 };
b@1608 179 // Create Submit (save) button
b@1608 180 var submit = document.createElement("button");
b@1608 181 submit.innerHTML = 'Submit';
b@1608 182 submit.onclick = buttonSubmitClick;
b@1608 183 submit.id = 'submit-button';
b@1608 184 // Append the interface buttons into the interfaceButtons object.
b@1608 185 interfaceButtons.appendChild(playback);
b@1608 186 interfaceButtons.appendChild(submit);
b@1608 187 interfaceButtons.appendChild(downloadPoint);
b@1608 188
b@1608 189 // Now create the slider and HTML5 canvas boxes
b@1608 190
b@1608 191 // Create the div box to center align
b@1608 192 var sliderBox = document.createElement('div');
b@1608 193 sliderBox.className = 'sliderCanvasDiv';
b@1608 194 sliderBox.id = 'sliderCanvasHolder';
b@1608 195 sliderBox.align = 'center';
b@1608 196
b@1608 197 // Create the slider box to hold the slider elements
b@1608 198 var canvas = document.createElement('div');
b@1608 199 canvas.id = 'slider';
b@1608 200 // Must have a known EXACT width, as this is used later to determine the ratings
b@1608 201 canvas.style.width = width - 100 +"px";
b@1608 202 canvas.align = "left";
b@1608 203 sliderBox.appendChild(canvas);
b@1608 204
b@1608 205 // Create the div to hold any scale objects
b@1608 206 var scale = document.createElement('div');
b@1608 207 scale.className = 'sliderScale';
b@1608 208 scale.id = 'sliderScaleHolder';
b@1608 209 scale.align = 'left';
b@1608 210 sliderBox.appendChild(scale);
b@1608 211
b@1608 212 // Global parent for the comment boxes on the page
b@1608 213 var feedbackHolder = document.createElement('div');
b@1608 214 feedbackHolder.id = 'feedbackHolder';
b@1608 215
b@1608 216 testContent.style.zIndex = 1;
b@1608 217 insertPoint.innerHTML = null; // Clear the current schema
b@1608 218
b@1608 219 // Create pre and post test questions
b@1608 220 var blank = document.createElement('div');
b@1608 221 blank.className = 'testHalt';
b@1608 222
b@1608 223 var popupHolder = document.createElement('div');
b@1608 224 popupHolder.id = 'popupHolder';
b@1608 225 popupHolder.className = 'popupHolder';
b@1608 226 popupHolder.style.position = 'absolute';
b@1608 227 popupHolder.style.left = (window.innerWidth/2)-250 + 'px';
b@1608 228 popupHolder.style.top = (window.innerHeight/2)-125 + 'px';
b@1608 229 insertPoint.appendChild(popupHolder);
b@1608 230 insertPoint.appendChild(blank);
b@1608 231 hidePopup();
b@1608 232
b@1608 233 var preTest = xmlSetup.find('PreTest');
b@1608 234 var postTest = xmlSetup.find('PostTest');
b@1608 235 preTest = preTest[0];
b@1608 236 postTest = postTest[0];
b@1608 237
b@1608 238 currentState = 'preTest';
b@1608 239
b@1608 240 // Create Pre-Test Box
nickjillings@1612 241 if (preTest != undefined && preTest.childElementCount >= 1)
b@1608 242 {
b@1608 243 showPopup();
b@1608 244 preTestPopupStart(preTest);
b@1608 245 }
b@1608 246
b@1608 247 // Inject into HTML
b@1608 248 testContent.appendChild(title); // Insert the title
b@1608 249 testContent.appendChild(pagetitle);
b@1608 250 testContent.appendChild(interfaceButtons);
b@1608 251 testContent.appendChild(sliderBox);
b@1608 252 testContent.appendChild(feedbackHolder);
b@1608 253 insertPoint.appendChild(testContent);
b@1608 254
b@1608 255 // Load the full interface
b@1608 256
b@1608 257 }
b@1608 258
b@1608 259 function loadTest(id)
b@1608 260 {
b@1608 261 // Used to load a specific test page
b@1608 262 var textXML = testXMLSetups[id];
b@1608 263
b@1608 264 var feedbackHolder = document.getElementById('feedbackHolder');
b@1608 265 var canvas = document.getElementById('slider');
b@1608 266 feedbackHolder.innerHTML = null;
b@1608 267 canvas.innerHTML = null;
b@1608 268
b@1608 269 // Setup question title
b@1608 270 var interfaceObj = $(textXML).find('interface');
b@1608 271 var titleNode = interfaceObj.find('title');
b@1608 272 if (titleNode[0] != undefined)
b@1608 273 {
b@1608 274 document.getElementById('pageTitle').textContent = titleNode[0].textContent;
b@1608 275 }
b@1608 276 var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2);
b@1608 277 var offset = 50-8; // Half the offset of the slider (window width -100) minus the body padding of 8
b@1608 278 // TODO: AUTOMATE ABOVE!!
b@1608 279 var scale = document.getElementById('sliderScaleHolder');
b@1608 280 scale.innerHTML = null;
b@1608 281 interfaceObj.find('scale').each(function(index,scaleObj){
b@1608 282 var position = Number(scaleObj.attributes['position'].value)*0.01;
b@1608 283 var pixelPosition = (position*positionScale)+offset;
b@1608 284 var scaleDOM = document.createElement('span');
b@1608 285 scaleDOM.textContent = scaleObj.textContent;
b@1608 286 scale.appendChild(scaleDOM);
b@1608 287 scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px';
b@1608 288 });
b@1608 289
b@1608 290 // Extract the hostURL attribute. If not set, create an empty string.
b@1608 291 var hostURL = textXML.attributes['hostURL'];
b@1608 292 if (hostURL == undefined) {
b@1608 293 hostURL = "";
b@1608 294 } else {
b@1608 295 hostURL = hostURL.value;
b@1608 296 }
b@1608 297 // Extract the sampleRate. If set, convert the string to a Number.
b@1608 298 var hostFs = textXML.attributes['sampleRate'];
b@1608 299 if (hostFs != undefined) {
b@1608 300 hostFs = Number(hostFs.value);
b@1608 301 }
b@1608 302
b@1608 303 /// CHECK FOR SAMPLE RATE COMPATIBILITY
b@1608 304 if (hostFs != undefined) {
b@1608 305 if (Number(hostFs) != audioContext.sampleRate) {
b@1608 306 var errStr = 'Sample rates do not match! Requested '+Number(hostFs)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
b@1608 307 alert(errStr);
b@1608 308 return;
b@1608 309 }
b@1608 310 }
b@1608 311
b@1608 312 var commentShow = textXML.attributes['elementComments'];
b@1608 313 if (commentShow != undefined) {
b@1608 314 if (commentShow.value == 'false') {commentShow = false;}
b@1608 315 else {commentShow = true;}
b@1608 316 } else {commentShow = true;}
b@1608 317
b@1608 318 var loopPlayback = textXML.attributes['loop'];
b@1608 319 if (loopPlayback != undefined)
b@1608 320 {
b@1608 321 loopPlayback = loopPlayback.value;
b@1608 322 if (loopPlayback == 'true') {
b@1608 323 loopPlayback = true;
b@1608 324 } else {
b@1608 325 loopPlayback = false;
b@1608 326 }
b@1608 327 } else {
b@1608 328 loopPlayback = false;
b@1608 329 }
b@1608 330 audioEngineContext.loopPlayback = loopPlayback;
b@1608 331
b@1608 332 // Create AudioEngine bindings for playback
b@1608 333 if (loopPlayback) {
b@1608 334 audioEngineContext.play = function() {
b@1608 335 // Send play command to all playback buffers for synchronised start
b@1608 336 // Also start timer callbacks to detect if playback has finished
b@1608 337 if (this.status == 0) {
b@1608 338 this.timer.startTest();
b@1608 339 // First get current clock
b@1608 340 var timer = audioContext.currentTime;
b@1608 341 // Add 3 seconds
b@1608 342 timer += 3.0;
b@1608 343 // Send play to all tracks
b@1608 344 for (var i=0; i<this.audioObjects.length; i++)
b@1608 345 {
b@1608 346 this.audioObjects[i].play(timer);
b@1608 347 }
b@1608 348 this.status = 1;
b@1608 349 }
b@1608 350 };
b@1608 351
b@1608 352 audioEngineContext.stop = function() {
b@1608 353 // Send stop and reset command to all playback buffers
b@1608 354 if (this.status == 1) {
b@1608 355 if (this.loopPlayback) {
b@1608 356 for (var i=0; i<this.audioObjects.length; i++)
b@1608 357 {
b@1608 358 this.audioObjects[i].stop();
b@1608 359 }
b@1608 360 }
b@1608 361 this.status = 0;
b@1608 362 }
b@1608 363 };
b@1608 364
b@1608 365 audioEngineContext.selectedTrack = function(id) {
b@1608 366 for (var i=0; i<this.audioObjects.length; i++)
b@1608 367 {
b@1608 368 if (id == i) {
b@1608 369 this.audioObjects[i].outputGain.gain.value = 1.0;
b@1608 370 } else {
b@1608 371 this.audioObjects[i].outputGain.gain.value = 0.0;
b@1608 372 }
b@1608 373 }
b@1608 374 };
b@1608 375 } else {
b@1608 376 audioEngineContext.play = function() {
b@1608 377 // Send play command to all playback buffers for synchronised start
b@1608 378 // Also start timer callbacks to detect if playback has finished
b@1608 379 if (this.status == 0) {
b@1608 380 this.timer.startTest();
b@1608 381 this.status = 1;
b@1608 382 }
b@1608 383 };
b@1608 384
b@1608 385 audioEngineContext.stop = function() {
b@1608 386 // Send stop and reset command to all playback buffers
b@1608 387 if (this.status == 1) {
b@1607 388 for (var i=0; i<this.audioObjects.length; i++)
b@1607 389 {
b@1607 390 this.audioObjects[i].stop();
b@1608 391 }
b@1608 392 this.status = 0;
b@1608 393 }
b@1608 394 };
b@1608 395
b@1608 396 audioEngineContext.selectedTrack = function(id) {
b@1608 397 for (var i=0; i<this.audioObjects.length; i++)
b@1608 398 {
b@1607 399 this.audioObjects[i].outputGain.gain.value = 0.0;
b@1607 400 this.audioObjects[i].stop();
b@1607 401 }
b@1607 402 if (this.status == 1) {
b@1607 403 this.audioObjects[id].outputGain.gain.value = 1.0;
b@1607 404 this.audioObjects[id].play(audioContext.currentTime+0.01);
b@1608 405 }
b@1608 406 };
b@1608 407 }
b@1608 408
b@1608 409 currentTestHolder = document.createElement('audioHolder');
b@1608 410 currentTestHolder.id = textXML.id;
b@1608 411 currentTestHolder.repeatCount = textXML.attributes['repeatCount'].value;
b@1608 412 var currentPreTestHolder = document.createElement('preTest');
b@1608 413 var currentPostTestHolder = document.createElement('postTest');
b@1608 414 currentTestHolder.appendChild(currentPreTestHolder);
b@1608 415 currentTestHolder.appendChild(currentPostTestHolder);
b@1608 416
b@1608 417 var randomise = textXML.attributes['randomiseOrder'];
b@1608 418 if (randomise != undefined) {randomise = randomise.value;}
b@1608 419 else {randomise = false;}
b@1608 420
b@1608 421 var audioElements = $(textXML).find('audioElements');
b@1608 422 currentTrackOrder = [];
b@1608 423 audioElements.each(function(index,element){
b@1608 424 // Find any blind-repeats
b@1608 425 // Not implemented yet, but just in case
b@1608 426 currentTrackOrder[index] = element;
b@1608 427 });
b@1608 428 if (randomise) {
b@1608 429 currentTrackOrder = randomiseOrder(currentTrackOrder);
b@1608 430 }
b@1608 431
b@1608 432 // Delete any previous audioObjects associated with the audioEngine
b@1608 433 audioEngineContext.audioObjects = [];
b@1608 434
b@1608 435 // Find all the audioElements from the audioHolder
b@1608 436 $(currentTrackOrder).each(function(index,element){
b@1608 437 // Find URL of track
b@1608 438 // In this jQuery loop, variable 'this' holds the current audioElement.
b@1608 439
b@1608 440 // Now load each audio sample. First create the new track by passing the full URL
b@1608 441 var trackURL = hostURL + this.attributes['url'].value;
b@1608 442 audioEngineContext.newTrack(trackURL);
b@1608 443
b@1608 444 if (commentShow) {
b@1608 445 // Create document objects to hold the comment boxes
b@1608 446 var trackComment = document.createElement('div');
b@1608 447 trackComment.className = 'comment-div';
b@1608 448 // Create a string next to each comment asking for a comment
b@1608 449 var trackString = document.createElement('span');
b@1608 450 trackString.innerHTML = 'Comment on track '+index;
b@1608 451 // Create the HTML5 comment box 'textarea'
b@1608 452 var trackCommentBox = document.createElement('textarea');
b@1608 453 trackCommentBox.rows = '4';
b@1608 454 trackCommentBox.cols = '100';
b@1608 455 trackCommentBox.name = 'trackComment'+index;
b@1608 456 trackCommentBox.className = 'trackComment';
b@1608 457 var br = document.createElement('br');
b@1608 458 // Add to the holder.
b@1608 459 trackComment.appendChild(trackString);
b@1608 460 trackComment.appendChild(br);
b@1608 461 trackComment.appendChild(trackCommentBox);
b@1608 462 feedbackHolder.appendChild(trackComment);
b@1608 463 }
b@1608 464
b@1608 465 // Create a slider per track
b@1608 466
b@1608 467 var trackSliderObj = document.createElement('div');
b@1608 468 trackSliderObj.className = 'track-slider';
b@1608 469 trackSliderObj.id = 'track-slider-'+index;
b@1608 470 // Distribute it randomnly
b@1608 471 var w = window.innerWidth - 100;
b@1608 472 w = Math.random()*w;
b@1608 473 trackSliderObj.style.left = Math.floor(w)+50+'px';
b@1608 474 trackSliderObj.innerHTML = '<span>'+index+'</span>';
b@1608 475 trackSliderObj.draggable = true;
b@1608 476 trackSliderObj.ondragend = dragEnd;
b@1608 477 trackSliderObj.ondragstart = function()
b@1608 478 {
b@1608 479 var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99!
b@1608 480 audioEngineContext.metric.sliderMoveStart(id);
b@1608 481 };
b@1608 482
b@1608 483 // Onclick, switch playback to that track
b@1608 484 trackSliderObj.onclick = function() {
b@1608 485 // Get the track ID from the object ID
b@1608 486 var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99!
b@1608 487 audioEngineContext.metric.sliderPlayed(id);
b@1608 488 audioEngineContext.selectedTrack(id);
b@1608 489 // Currently playing track red, rest green
b@1608 490 document.getElementById('track-slider-'+index).style.backgroundColor = "#FF0000";
b@1608 491 for (var i = 0; i<$(currentTrackOrder).length; i++)
b@1608 492 {
b@1610 493 if (i!=index) // Make all other sliders green
b@1608 494 {
b@1608 495 document.getElementById('track-slider-'+i).style.backgroundColor = "rgb(100,200,100)";
b@1608 496 }
b@1608 497
b@1608 498 }
b@1609 499 audioEngineContext.play();
b@1608 500 };
b@1608 501
b@1608 502 canvas.appendChild(trackSliderObj);
b@1610 503
b@1608 504 });
b@1608 505
b@1608 506 // Append any commentQuestion boxes
b@1608 507 var commentQuestions = $(textXML).find('CommentQuestion');
b@1608 508 $(commentQuestions).each(function(index,element) {
b@1608 509 // Create document objects to hold the comment boxes
b@1608 510 var trackComment = document.createElement('div');
b@1608 511 trackComment.className = 'comment-div commentQuestion';
b@1608 512 trackComment.id = element.attributes['id'].value;
b@1608 513 // Create a string next to each comment asking for a comment
b@1608 514 var trackString = document.createElement('span');
b@1608 515 trackString.innerHTML = element.textContent;
b@1608 516 // Create the HTML5 comment box 'textarea'
b@1608 517 var trackCommentBox = document.createElement('textarea');
b@1608 518 trackCommentBox.rows = '4';
b@1608 519 trackCommentBox.cols = '100';
b@1608 520 trackCommentBox.name = 'commentQuestion'+index;
b@1608 521 trackCommentBox.className = 'trackComment';
b@1608 522 var br = document.createElement('br');
b@1608 523 // Add to the holder.
b@1608 524 trackComment.appendChild(trackString);
b@1608 525 trackComment.appendChild(br);
b@1608 526 trackComment.appendChild(trackCommentBox);
b@1608 527 feedbackHolder.appendChild(trackComment);
b@1608 528 });
b@1608 529
b@1608 530 // Now process any pre-test commands
b@1608 531
b@1608 532 var preTest = $(testXMLSetups[id]).find('PreTest')[0];
nickjillings@1612 533 if (preTest.childElementCount > 0)
b@1608 534 {
b@1608 535 currentState = 'testRunPre-'+id;
b@1608 536 preTestPopupStart(preTest);
b@1608 537 showPopup();
b@1608 538 } else {
b@1608 539 currentState = 'testRun-'+id;
b@1608 540 }
b@1608 541 }
b@1608 542
b@1608 543 function preTestPopupStart(preTest)
b@1608 544 {
b@1608 545 var popupHolder = document.getElementById('popupHolder');
b@1608 546 popupHolder.innerHTML = null;
b@1608 547 // Parse the first box
b@1608 548 var preTestOption = document.createElement('div');
b@1608 549 preTestOption.id = 'preTest';
b@1608 550 preTestOption.style.marginTop = '25px';
b@1608 551 preTestOption.align = "center";
nickjillings@1612 552 var child = $(preTest).children()[0];
b@1608 553 if (child.nodeName == 'statement')
b@1608 554 {
nickjillings@1612 555 preTestOption.innerHTML = '<span>'+child.textContent+'</span>';
b@1608 556 } else if (child.nodeName == 'question')
b@1608 557 {
b@1608 558 var questionId = child.attributes['id'].value;
b@1608 559 var textHold = document.createElement('span');
nickjillings@1612 560 textHold.innerHTML = child.textContent;
b@1608 561 textHold.id = questionId + 'response';
b@1608 562 var textEnter = document.createElement('textarea');
b@1608 563 preTestOption.appendChild(textHold);
b@1608 564 preTestOption.appendChild(textEnter);
b@1608 565 }
b@1608 566 var nextButton = document.createElement('button');
b@1608 567 nextButton.className = 'popupButton';
b@1608 568 nextButton.value = '0';
b@1608 569 nextButton.innerHTML = 'Next';
b@1608 570 nextButton.onclick = popupButtonClick;
b@1608 571
b@1608 572 popupHolder.appendChild(preTestOption);
b@1608 573 popupHolder.appendChild(nextButton);
b@1608 574 }
b@1608 575
b@1608 576 function popupButtonClick()
b@1608 577 {
b@1608 578 // Global call from the 'Next' button click
b@1608 579 if (currentState == 'preTest')
b@1608 580 {
b@1608 581 // At the start of the preTest routine!
b@1608 582 var xmlTree = projectXML.find('setup');
b@1608 583 var preTest = xmlTree.find('PreTest')[0];
b@1608 584 this.value = preTestButtonClick(preTest,this.value);
b@1608 585 } else if (currentState.substr(0,10) == 'testRunPre')
b@1608 586 {
b@1608 587 //Specific test pre-test
b@1608 588 var testId = currentState.substr(11,currentState.length-10);
b@1608 589 var preTest = $(testXMLSetups[testId]).find('PreTest')[0];
b@1608 590 this.value = preTestButtonClick(preTest,this.value);
b@1608 591 } else if (currentState.substr(0,11) == 'testRunPost')
b@1608 592 {
b@1608 593 // Specific test post-test
b@1608 594 var testId = currentState.substr(12,currentState.length-11);
b@1608 595 var preTest = $(testXMLSetups[testId]).find('PostTest')[0];
b@1608 596 this.value = preTestButtonClick(preTest,this.value);
b@1608 597 } else if (currentState == 'postTest')
b@1608 598 {
b@1608 599 // At the end of the test, running global post test
b@1608 600 var xmlTree = projectXML.find('setup');
b@1608 601 var PostTest = xmlTree.find('PostTest')[0];
b@1608 602 this.value = preTestButtonClick(PostTest,this.value);
b@1608 603 }
b@1608 604 }
b@1608 605
b@1608 606 function preTestButtonClick(preTest,index)
b@1608 607 {
b@1608 608 // Called on click of pre-test button
b@1608 609 // Need to find and parse preTest again!
b@1608 610 var preTestOption = document.getElementById('preTest');
b@1608 611 // Check if current state is a question!
nickjillings@1612 612 if ($(preTest).children()[index].nodeName == 'question') {
nickjillings@1612 613 var questionId = $(preTest).children()[index].attributes['id'].value;
b@1608 614 var questionHold = document.createElement('comment');
b@1608 615 var questionResponse = document.getElementById(questionId + 'response');
nickjillings@1612 616 var mandatory = $(preTest).children()[index].attributes['mandatory'];
b@1608 617 if (mandatory != undefined){
b@1608 618 if (mandatory.value == 'true') {mandatory = true;}
b@1608 619 else {mandatory = false;}
b@1608 620 } else {mandatory = false;}
b@1608 621 if (mandatory == true && questionResponse.value.length == 0) {
b@1608 622 return index;
b@1608 623 }
b@1608 624 questionHold.id = questionId;
b@1608 625 questionHold.innerHTML = questionResponse.value;
b@1608 626 postPopupResponse(questionHold);
b@1608 627 }
b@1608 628 index++;
nickjillings@1612 629 if (index < preTest.childElementCount)
b@1608 630 {
b@1608 631 // More to process
nickjillings@1612 632 var child = $(preTest).children()[index];
b@1608 633 if (child.nodeName == 'statement')
b@1608 634 {
nickjillings@1612 635 preTestOption.innerHTML = '<span>'+child.textContent+'</span>';
b@1608 636 } else if (child.nodeName == 'question')
b@1608 637 {
b@1608 638 var textHold = document.createElement('span');
nickjillings@1612 639 textHold.innerHTML = child.textContent;
b@1608 640 var textEnter = document.createElement('textarea');
b@1608 641 textEnter.id = child.attributes['id'].value + 'response';
b@1608 642 var br = document.createElement('br');
b@1608 643 preTestOption.innerHTML = null;
b@1608 644 preTestOption.appendChild(textHold);
b@1608 645 preTestOption.appendChild(br);
b@1608 646 preTestOption.appendChild(textEnter);
b@1608 647 }
b@1608 648 } else {
b@1608 649 // Time to clear
b@1608 650 preTestOption.innerHTML = null;
b@1608 651 if (currentState != 'postTest') {
b@1608 652 hidePopup();
b@1608 653 // Progress the state!
b@1608 654 advanceState();
b@1608 655 } else {
b@1608 656 a = createProjectSave(projectReturn);
b@1608 657 preTestOption.appendChild(a);
b@1608 658 }
b@1608 659 }
b@1608 660 return index;
b@1608 661 }
b@1608 662
b@1608 663 function postPopupResponse(response)
b@1608 664 {
b@1608 665 if (currentState == 'preTest') {
b@1608 666 preTestQuestions.appendChild(response);
b@1608 667 } else if (currentState == 'postTest') {
b@1608 668 postTestQuestions.appendChild(response);
b@1608 669 } else {
b@1608 670 // Inside a specific test
b@1608 671 if (currentState.substr(0,10) == 'testRunPre') {
b@1608 672 // Pre Test
b@1608 673 var store = $(currentTestHolder).find('preTest');
b@1608 674 } else {
b@1608 675 // Post Test
b@1608 676 var store = $(currentTestHolder).find('postTest');
b@1608 677 }
b@1608 678 store[0].appendChild(response);
b@1608 679 }
b@1608 680 }
b@1608 681
b@1608 682 function showPopup()
b@1608 683 {
b@1608 684 var popupHolder = document.getElementById('popupHolder');
b@1608 685 popupHolder.style.zIndex = 3;
b@1608 686 popupHolder.style.visibility = 'visible';
b@1608 687 var blank = document.getElementsByClassName('testHalt')[0];
b@1608 688 blank.style.zIndex = 2;
b@1608 689 blank.style.visibility = 'visible';
b@1608 690 }
b@1608 691
b@1608 692 function hidePopup()
b@1608 693 {
b@1608 694 var popupHolder = document.getElementById('popupHolder');
b@1608 695 popupHolder.style.zIndex = -1;
b@1608 696 popupHolder.style.visibility = 'hidden';
b@1608 697 var blank = document.getElementsByClassName('testHalt')[0];
b@1608 698 blank.style.zIndex = -2;
b@1608 699 blank.style.visibility = 'hidden';
b@1608 700 }
b@1608 701
b@1608 702 function dragEnd(ev) {
b@1608 703 // Function call when a div has been dropped
b@1608 704 var slider = document.getElementById('slider');
b@1608 705 var w = slider.style.width;
b@1608 706 w = Number(w.substr(0,w.length-2));
b@1608 707 var x = ev.x;
b@1608 708 if (x >= 42 && x < w+42) {
b@1608 709 this.style.left = (x)+'px';
b@1608 710 } else {
b@1608 711 if (x<42) {
b@1608 712 this.style.left = '42px';
b@1608 713 } else {
b@1608 714 this.style.left = (w+42) + 'px';
b@1608 715 }
b@1608 716 }
b@1608 717 audioEngineContext.metric.sliderMoved();
b@1608 718 }
b@1608 719
b@1608 720 function advanceState()
b@1608 721 {
b@1608 722 console.log(currentState);
b@1608 723 if (currentState == 'preTest')
b@1608 724 {
b@1608 725 // End of pre-test, begin the test
b@1608 726 loadTest(0);
b@1608 727 } else if (currentState.substr(0,10) == 'testRunPre')
b@1608 728 {
b@1608 729 // Start the test
b@1608 730 var testId = currentState.substr(11,currentState.length-10);
b@1608 731 currentState = 'testRun-'+testId;
nickjillings@1614 732 //audioEngineContext.timer.startTest();
nickjillings@1614 733 audioEngineContext.play();
b@1608 734 } else if (currentState.substr(0,11) == 'testRunPost')
b@1608 735 {
b@1608 736 var testId = currentState.substr(12,currentState.length-11);
b@1608 737 testEnded(testId);
b@1608 738 } else if (currentState.substr(0,7) == 'testRun')
b@1608 739 {
b@1608 740 var testId = currentState.substr(8,currentState.length-7);
b@1608 741 // Check if we have any post tests to perform
b@1608 742 var postXML = $(testXMLSetups[testId]).find('PostTest')[0];
b@1608 743 if (postXML == undefined) {
b@1608 744 testEnded(testId);
b@1608 745 }
nickjillings@1612 746 else if (postXML.childElementCount > 0)
b@1608 747 {
b@1608 748 currentState = 'testRunPost-'+testId;
b@1608 749 showPopup();
b@1608 750 preTestPopupStart(postXML);
b@1608 751 }
b@1608 752 else {
b@1608 753
b@1608 754
b@1608 755 // No post tests, check if we have another test to perform instead
b@1608 756
b@1608 757 }
b@1608 758 }
b@1608 759 console.log(currentState);
b@1608 760 }
b@1608 761
b@1608 762 function testEnded(testId)
b@1608 763 {
b@1608 764 pageXMLSave(testId);
b@1608 765 if (testXMLSetups.length-1 > testId)
b@1608 766 {
b@1608 767 // Yes we have another test to perform
b@1608 768 testId = (Number(testId)+1);
b@1608 769 currentState = 'testRun-'+testId;
b@1608 770 loadTest(testId);
b@1608 771 } else {
b@1608 772 console.log('Testing Completed!');
b@1608 773 currentState = 'postTest';
b@1608 774 // Check for any post tests
b@1608 775 var xmlSetup = projectXML.find('setup');
b@1608 776 var postTest = xmlSetup.find('PostTest')[0];
b@1608 777 showPopup();
b@1608 778 preTestPopupStart(postTest);
b@1608 779 }
b@1608 780 }
b@1608 781
b@1609 782 function buttonSubmitClick() // TODO: Only when all songs have been played!
b@1608 783 {
nickjillings@1614 784 hasBeenPlayed = audioEngineContext.checkAllPlayed();
nickjillings@1614 785 if (hasBeenPlayed.length == 0) {
nickjillings@1614 786 if (audioEngineContext.status == 1) {
nickjillings@1614 787 var playback = document.getElementById('playback-button');
nickjillings@1614 788 playback.click();
nickjillings@1614 789 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
nickjillings@1614 790 } else
nickjillings@1614 791 {
nickjillings@1614 792 if (audioEngineContext.timer.testStarted == false)
nickjillings@1614 793 {
nickjillings@1614 794 alert('You have not started the test! Please press start to begin the test!');
nickjillings@1614 795 return;
nickjillings@1614 796 }
nickjillings@1614 797 }
nickjillings@1614 798 if (currentState.substr(0,7) == 'testRun')
nickjillings@1614 799 {
nickjillings@1614 800 hasBeenPlayed = []; // clear array to prepare for next test
nickjillings@1614 801 audioEngineContext.timer.stopTest();
nickjillings@1614 802 advanceState();
nickjillings@1614 803 }
b@1610 804 } else // if a fragment has not been played yet
b@1610 805 {
nickjillings@1614 806 str = "";
nickjillings@1614 807 if (hasBeenPlayed.length > 1) {
nickjillings@1614 808 for (var i=0; i<hasBeenPlayed.length; i++) {
nickjillings@1614 809 str = str + hasBeenPlayed[i];
nickjillings@1614 810 if (i < hasBeenPlayed.length-2){
nickjillings@1614 811 str += ", ";
nickjillings@1614 812 } else if (i == hasBeenPlayed.length-2) {
nickjillings@1614 813 str += " or ";
nickjillings@1614 814 }
nickjillings@1614 815 }
nickjillings@1614 816 alert('You have not played fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.');
nickjillings@1614 817 } else {
nickjillings@1614 818 alert('You have not played fragment ' + hasBeenPlayed[0] + ' yet. Please listen, rate and comment all samples before submitting.');
nickjillings@1614 819 }
b@1610 820 return;
b@1610 821 }
b@1608 822 }
b@1608 823
b@1608 824 function convSliderPosToRate(id)
b@1608 825 {
b@1608 826 var w = document.getElementById('slider').style.width;
b@1608 827 var maxPix = w.substr(0,w.length-2);
b@1608 828 var slider = document.getElementsByClassName('track-slider')[id];
b@1608 829 var pix = slider.style.left;
b@1608 830 pix = pix.substr(0,pix.length-2);
b@1608 831 var rate = (pix-42)/maxPix;
b@1608 832 return rate;
b@1608 833 }
b@1608 834
b@1608 835 function pageXMLSave(testId)
b@1608 836 {
b@1608 837 // Saves a specific test page
b@1608 838 var xmlDoc = currentTestHolder;
b@1608 839 // Check if any session wide metrics are enabled
b@1608 840
b@1608 841 var commentShow = testXMLSetups[testId].attributes['elementComments'];
b@1608 842 if (commentShow != undefined) {
b@1608 843 if (commentShow.value == 'false') {commentShow = false;}
b@1608 844 else {commentShow = true;}
b@1608 845 } else {commentShow = true;}
b@1608 846
b@1608 847 var metric = document.createElement('metric');
b@1608 848 if (audioEngineContext.metric.enableTestTimer)
b@1608 849 {
b@1608 850 var testTime = document.createElement('metricResult');
b@1608 851 testTime.id = 'testTime';
b@1608 852 testTime.textContent = audioEngineContext.timer.testDuration;
b@1608 853 metric.appendChild(testTime);
b@1608 854 }
b@1608 855 xmlDoc.appendChild(metric);
b@1608 856 var trackSliderObjects = document.getElementsByClassName('track-slider');
b@1608 857 var commentObjects = document.getElementsByClassName('comment-div');
b@1608 858 for (var i=0; i<trackSliderObjects.length; i++)
b@1608 859 {
b@1608 860 var audioElement = document.createElement('audioElement');
b@1608 861 audioElement.id = currentTrackOrder[i].attributes['id'].value;
b@1608 862 audioElement.url = currentTrackOrder[i].attributes['url'].value;
b@1608 863 var value = document.createElement('value');
b@1608 864 value.innerHTML = convSliderPosToRate(i);
b@1608 865 if (commentShow) {
b@1608 866 var comment = document.createElement("comment");
b@1608 867 var question = document.createElement("question");
b@1608 868 var response = document.createElement("response");
b@1608 869 question.textContent = commentObjects[i].children[0].textContent;
b@1608 870 response.textContent = commentObjects[i].children[2].value;
b@1608 871 comment.appendChild(question);
b@1608 872 comment.appendChild(response);
b@1608 873 audioElement.appendChild(comment);
b@1608 874 }
b@1608 875 audioElement.appendChild(value);
b@1608 876 // Check for any per element metrics
b@1608 877 var metric = document.createElement('metric');
b@1608 878 var elementMetric = audioEngineContext.audioObjects[i].metric;
b@1608 879 if (audioEngineContext.metric.enableElementTimer) {
b@1608 880 var elementTimer = document.createElement('metricResult');
b@1608 881 elementTimer.id = 'elementTimer';
b@1608 882 elementTimer.textContent = elementMetric.listenedTimer;
b@1608 883 metric.appendChild(elementTimer);
b@1608 884 }
b@1608 885 if (audioEngineContext.metric.enableElementTracker) {
b@1608 886 var elementTrackerFull = document.createElement('metricResult');
b@1608 887 elementTrackerFull.id = 'elementTrackerFull';
b@1608 888 var data = elementMetric.movementTracker;
b@1608 889 for (var k=0; k<data.length; k++)
b@1608 890 {
b@1608 891 var timePos = document.createElement('timePos');
b@1608 892 timePos.id = k;
b@1608 893 var time = document.createElement('time');
b@1608 894 time.textContent = data[k][0];
b@1608 895 var position = document.createElement('position');
b@1608 896 position.textContent = data[k][1];
b@1608 897 timePos.appendChild(time);
b@1608 898 timePos.appendChild(position);
b@1608 899 elementTrackerFull.appendChild(timePos);
b@1608 900 }
b@1608 901 metric.appendChild(elementTrackerFull);
b@1608 902 }
b@1608 903 if (audioEngineContext.metric.enableElementInitialPosition) {
b@1608 904 var elementInitial = document.createElement('metricResult');
b@1608 905 elementInitial.id = 'elementInitialPosition';
b@1608 906 elementInitial.textContent = elementMetric.initialPosition;
b@1608 907 metric.appendChild(elementInitial);
b@1608 908 }
b@1608 909 if (audioEngineContext.metric.enableFlagListenedTo) {
b@1608 910 var flagListenedTo = document.createElement('metricResult');
b@1608 911 flagListenedTo.id = 'elementFlagListenedTo';
b@1608 912 flagListenedTo.textContent = elementMetric.wasListenedTo;
b@1608 913 metric.appendChild(flagListenedTo);
b@1608 914 }
b@1608 915 if (audioEngineContext.metric.enableFlagMoved) {
b@1608 916 var flagMoved = document.createElement('metricResult');
b@1608 917 flagMoved.id = 'elementFlagMoved';
b@1608 918 flagMoved.textContent = elementMetric.wasMoved;
b@1608 919 metric.appendChild(flagMoved);
b@1608 920 }
b@1608 921 if (audioEngineContext.metric.enableFlagComments) {
b@1608 922 var flagComments = document.createElement('metricResult');
b@1608 923 flagComments.id = 'elementFlagComments';
b@1608 924 if (response.textContent.length == 0) {flag.textContent = 'false';}
b@1608 925 else {flag.textContet = 'true';}
b@1608 926 metric.appendChild(flagComments);
b@1608 927 }
b@1608 928 audioElement.appendChild(metric);
b@1608 929 xmlDoc.appendChild(audioElement);
b@1608 930 }
b@1608 931 var commentQuestion = document.getElementsByClassName('commentQuestion');
b@1608 932 for (var i=0; i<commentQuestion.length; i++)
b@1608 933 {
b@1608 934 var cqHolder = document.createElement('CommentQuestion');
b@1608 935 var comment = document.createElement('comment');
b@1608 936 var question = document.createElement('question');
b@1608 937 cqHolder.id = commentQuestion[i].id;
b@1608 938 comment.textContent = commentQuestion[i].children[2].value;
b@1608 939 question.textContent = commentQuestion[i].children[0].textContent;
b@1608 940 cqHolder.appendChild(question);
b@1608 941 cqHolder.appendChild(comment);
b@1608 942 xmlDoc.appendChild(cqHolder);
b@1608 943 }
b@1608 944 testResultsHolders[testId] = xmlDoc;
b@1608 945 }
b@1608 946
b@1608 947 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
b@1608 948 function interfaceXMLSave(){
b@1608 949 // Create the XML string to be exported with results
b@1608 950 var xmlDoc = document.createElement("BrowserEvaluationResult");
b@1608 951 for (var i=0; i<testResultsHolders.length; i++)
b@1608 952 {
b@1608 953 xmlDoc.appendChild(testResultsHolders[i]);
b@1608 954 }
b@1608 955 // Append Pre/Post Questions
b@1608 956 xmlDoc.appendChild(preTestQuestions);
b@1608 957 xmlDoc.appendChild(postTestQuestions);
b@1608 958
b@1608 959 return xmlDoc;
b@1608 960 }