annotate ape.js @ 1608:a21deccefe7d

Currently playing sample red.
author Brecht De Man <b.deman@qmul.ac.uk>
date Sat, 16 May 2015 17:52:51 +0100
parents
children c2fe27b91359
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@1608 169 playback.innerHTML = 'Start';
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@1608 174 if (audioEngineContext.status == 0) {
b@1608 175 audioEngineContext.play();
b@1608 176 this.innerHTML = 'Stop';
b@1608 177 } else {
b@1608 178 audioEngineContext.stop();
b@1608 179 this.innerHTML = 'Start';
b@1608 180 }
b@1608 181 };
b@1608 182 // Create Submit (save) button
b@1608 183 var submit = document.createElement("button");
b@1608 184 submit.innerHTML = 'Submit';
b@1608 185 submit.onclick = buttonSubmitClick;
b@1608 186 submit.id = 'submit-button';
b@1608 187 // Append the interface buttons into the interfaceButtons object.
b@1608 188 interfaceButtons.appendChild(playback);
b@1608 189 interfaceButtons.appendChild(submit);
b@1608 190 interfaceButtons.appendChild(downloadPoint);
b@1608 191
b@1608 192 // Now create the slider and HTML5 canvas boxes
b@1608 193
b@1608 194 // Create the div box to center align
b@1608 195 var sliderBox = document.createElement('div');
b@1608 196 sliderBox.className = 'sliderCanvasDiv';
b@1608 197 sliderBox.id = 'sliderCanvasHolder';
b@1608 198 sliderBox.align = 'center';
b@1608 199
b@1608 200 // Create the slider box to hold the slider elements
b@1608 201 var canvas = document.createElement('div');
b@1608 202 canvas.id = 'slider';
b@1608 203 // Must have a known EXACT width, as this is used later to determine the ratings
b@1608 204 canvas.style.width = width - 100 +"px";
b@1608 205 canvas.align = "left";
b@1608 206 sliderBox.appendChild(canvas);
b@1608 207
b@1608 208 // Create the div to hold any scale objects
b@1608 209 var scale = document.createElement('div');
b@1608 210 scale.className = 'sliderScale';
b@1608 211 scale.id = 'sliderScaleHolder';
b@1608 212 scale.align = 'left';
b@1608 213 sliderBox.appendChild(scale);
b@1608 214
b@1608 215 // Global parent for the comment boxes on the page
b@1608 216 var feedbackHolder = document.createElement('div');
b@1608 217 feedbackHolder.id = 'feedbackHolder';
b@1608 218
b@1608 219 testContent.style.zIndex = 1;
b@1608 220 insertPoint.innerHTML = null; // Clear the current schema
b@1608 221
b@1608 222 // Create pre and post test questions
b@1608 223 var blank = document.createElement('div');
b@1608 224 blank.className = 'testHalt';
b@1608 225
b@1608 226 var popupHolder = document.createElement('div');
b@1608 227 popupHolder.id = 'popupHolder';
b@1608 228 popupHolder.className = 'popupHolder';
b@1608 229 popupHolder.style.position = 'absolute';
b@1608 230 popupHolder.style.left = (window.innerWidth/2)-250 + 'px';
b@1608 231 popupHolder.style.top = (window.innerHeight/2)-125 + 'px';
b@1608 232 insertPoint.appendChild(popupHolder);
b@1608 233 insertPoint.appendChild(blank);
b@1608 234 hidePopup();
b@1608 235
b@1608 236 var preTest = xmlSetup.find('PreTest');
b@1608 237 var postTest = xmlSetup.find('PostTest');
b@1608 238 preTest = preTest[0];
b@1608 239 postTest = postTest[0];
b@1608 240
b@1608 241 currentState = 'preTest';
b@1608 242
b@1608 243 // Create Pre-Test Box
b@1608 244 if (preTest != undefined && preTest.children.length >= 1)
b@1608 245 {
b@1608 246 showPopup();
b@1608 247 preTestPopupStart(preTest);
b@1608 248 }
b@1608 249
b@1608 250 // Inject into HTML
b@1608 251 testContent.appendChild(title); // Insert the title
b@1608 252 testContent.appendChild(pagetitle);
b@1608 253 testContent.appendChild(interfaceButtons);
b@1608 254 testContent.appendChild(sliderBox);
b@1608 255 testContent.appendChild(feedbackHolder);
b@1608 256 insertPoint.appendChild(testContent);
b@1608 257
b@1608 258 // Load the full interface
b@1608 259
b@1608 260 }
b@1608 261
b@1608 262 function loadTest(id)
b@1608 263 {
b@1608 264 // Used to load a specific test page
b@1608 265 var textXML = testXMLSetups[id];
b@1608 266
b@1608 267 var feedbackHolder = document.getElementById('feedbackHolder');
b@1608 268 var canvas = document.getElementById('slider');
b@1608 269 feedbackHolder.innerHTML = null;
b@1608 270 canvas.innerHTML = null;
b@1608 271
b@1608 272 // Setup question title
b@1608 273 var interfaceObj = $(textXML).find('interface');
b@1608 274 var titleNode = interfaceObj.find('title');
b@1608 275 if (titleNode[0] != undefined)
b@1608 276 {
b@1608 277 document.getElementById('pageTitle').textContent = titleNode[0].textContent;
b@1608 278 }
b@1608 279 var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2);
b@1608 280 var offset = 50-8; // Half the offset of the slider (window width -100) minus the body padding of 8
b@1608 281 // TODO: AUTOMATE ABOVE!!
b@1608 282 var scale = document.getElementById('sliderScaleHolder');
b@1608 283 scale.innerHTML = null;
b@1608 284 interfaceObj.find('scale').each(function(index,scaleObj){
b@1608 285 var position = Number(scaleObj.attributes['position'].value)*0.01;
b@1608 286 var pixelPosition = (position*positionScale)+offset;
b@1608 287 var scaleDOM = document.createElement('span');
b@1608 288 scaleDOM.textContent = scaleObj.textContent;
b@1608 289 scale.appendChild(scaleDOM);
b@1608 290 scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px';
b@1608 291 });
b@1608 292
b@1608 293 // Extract the hostURL attribute. If not set, create an empty string.
b@1608 294 var hostURL = textXML.attributes['hostURL'];
b@1608 295 if (hostURL == undefined) {
b@1608 296 hostURL = "";
b@1608 297 } else {
b@1608 298 hostURL = hostURL.value;
b@1608 299 }
b@1608 300 // Extract the sampleRate. If set, convert the string to a Number.
b@1608 301 var hostFs = textXML.attributes['sampleRate'];
b@1608 302 if (hostFs != undefined) {
b@1608 303 hostFs = Number(hostFs.value);
b@1608 304 }
b@1608 305
b@1608 306 /// CHECK FOR SAMPLE RATE COMPATIBILITY
b@1608 307 if (hostFs != undefined) {
b@1608 308 if (Number(hostFs) != audioContext.sampleRate) {
b@1608 309 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 310 alert(errStr);
b@1608 311 return;
b@1608 312 }
b@1608 313 }
b@1608 314
b@1608 315 var commentShow = textXML.attributes['elementComments'];
b@1608 316 if (commentShow != undefined) {
b@1608 317 if (commentShow.value == 'false') {commentShow = false;}
b@1608 318 else {commentShow = true;}
b@1608 319 } else {commentShow = true;}
b@1608 320
b@1608 321 var loopPlayback = textXML.attributes['loop'];
b@1608 322 if (loopPlayback != undefined)
b@1608 323 {
b@1608 324 loopPlayback = loopPlayback.value;
b@1608 325 if (loopPlayback == 'true') {
b@1608 326 loopPlayback = true;
b@1608 327 } else {
b@1608 328 loopPlayback = false;
b@1608 329 }
b@1608 330 } else {
b@1608 331 loopPlayback = false;
b@1608 332 }
b@1608 333 audioEngineContext.loopPlayback = loopPlayback;
b@1608 334
b@1608 335 // Create AudioEngine bindings for playback
b@1608 336 if (loopPlayback) {
b@1608 337 audioEngineContext.play = function() {
b@1608 338 // Send play command to all playback buffers for synchronised start
b@1608 339 // Also start timer callbacks to detect if playback has finished
b@1608 340 if (this.status == 0) {
b@1608 341 this.timer.startTest();
b@1608 342 // First get current clock
b@1608 343 var timer = audioContext.currentTime;
b@1608 344 // Add 3 seconds
b@1608 345 timer += 3.0;
b@1608 346 // Send play to all tracks
b@1608 347 for (var i=0; i<this.audioObjects.length; i++)
b@1608 348 {
b@1608 349 this.audioObjects[i].play(timer);
b@1608 350 }
b@1608 351 this.status = 1;
b@1608 352 }
b@1608 353 };
b@1608 354
b@1608 355 audioEngineContext.stop = function() {
b@1608 356 // Send stop and reset command to all playback buffers
b@1608 357 if (this.status == 1) {
b@1608 358 if (this.loopPlayback) {
b@1608 359 for (var i=0; i<this.audioObjects.length; i++)
b@1608 360 {
b@1608 361 this.audioObjects[i].stop();
b@1608 362 }
b@1608 363 }
b@1608 364 this.status = 0;
b@1608 365 }
b@1608 366 };
b@1608 367
b@1608 368 audioEngineContext.selectedTrack = function(id) {
b@1608 369 for (var i=0; i<this.audioObjects.length; i++)
b@1608 370 {
b@1608 371 if (id == i) {
b@1608 372 this.audioObjects[i].outputGain.gain.value = 1.0;
b@1608 373 } else {
b@1608 374 this.audioObjects[i].outputGain.gain.value = 0.0;
b@1608 375 }
b@1608 376 }
b@1608 377 };
b@1608 378 } else {
b@1608 379 audioEngineContext.play = function() {
b@1608 380 // Send play command to all playback buffers for synchronised start
b@1608 381 // Also start timer callbacks to detect if playback has finished
b@1608 382 if (this.status == 0) {
b@1608 383 this.timer.startTest();
b@1608 384 this.status = 1;
b@1608 385 }
b@1608 386 };
b@1608 387
b@1608 388 audioEngineContext.stop = function() {
b@1608 389 // Send stop and reset command to all playback buffers
b@1608 390 if (this.status == 1) {
b@1608 391 if (this.loopPlayback) {
b@1608 392 for (var i=0; i<this.audioObjects.length; i++)
b@1608 393 {
b@1608 394 this.audioObjects[i].stop();
b@1608 395 }
b@1608 396 }
b@1608 397 this.status = 0;
b@1608 398 }
b@1608 399 };
b@1608 400
b@1608 401 audioEngineContext.selectedTrack = function(id) {
b@1608 402 for (var i=0; i<this.audioObjects.length; i++)
b@1608 403 {
b@1608 404 if (id == i) {
b@1608 405 this.audioObjects[i].outputGain.gain.value = 1.0;
b@1608 406 this.audioObjects[i].play(audioContext.currentTime+0.01);
b@1608 407 } else {
b@1608 408 this.audioObjects[i].outputGain.gain.value = 0.0;
b@1608 409 this.audioObjects[i].stop();
b@1608 410 }
b@1608 411 }
b@1608 412 };
b@1608 413 }
b@1608 414
b@1608 415 currentTestHolder = document.createElement('audioHolder');
b@1608 416 currentTestHolder.id = textXML.id;
b@1608 417 currentTestHolder.repeatCount = textXML.attributes['repeatCount'].value;
b@1608 418 var currentPreTestHolder = document.createElement('preTest');
b@1608 419 var currentPostTestHolder = document.createElement('postTest');
b@1608 420 currentTestHolder.appendChild(currentPreTestHolder);
b@1608 421 currentTestHolder.appendChild(currentPostTestHolder);
b@1608 422
b@1608 423 var randomise = textXML.attributes['randomiseOrder'];
b@1608 424 if (randomise != undefined) {randomise = randomise.value;}
b@1608 425 else {randomise = false;}
b@1608 426
b@1608 427 var audioElements = $(textXML).find('audioElements');
b@1608 428 currentTrackOrder = [];
b@1608 429 audioElements.each(function(index,element){
b@1608 430 // Find any blind-repeats
b@1608 431 // Not implemented yet, but just in case
b@1608 432 currentTrackOrder[index] = element;
b@1608 433 });
b@1608 434 if (randomise) {
b@1608 435 currentTrackOrder = randomiseOrder(currentTrackOrder);
b@1608 436 }
b@1608 437
b@1608 438 // Delete any previous audioObjects associated with the audioEngine
b@1608 439 audioEngineContext.audioObjects = [];
b@1608 440
b@1608 441 // Find all the audioElements from the audioHolder
b@1608 442 $(currentTrackOrder).each(function(index,element){
b@1608 443 // Find URL of track
b@1608 444 // In this jQuery loop, variable 'this' holds the current audioElement.
b@1608 445
b@1608 446 // Now load each audio sample. First create the new track by passing the full URL
b@1608 447 var trackURL = hostURL + this.attributes['url'].value;
b@1608 448 audioEngineContext.newTrack(trackURL);
b@1608 449
b@1608 450 if (commentShow) {
b@1608 451 // Create document objects to hold the comment boxes
b@1608 452 var trackComment = document.createElement('div');
b@1608 453 trackComment.className = 'comment-div';
b@1608 454 // Create a string next to each comment asking for a comment
b@1608 455 var trackString = document.createElement('span');
b@1608 456 trackString.innerHTML = 'Comment on track '+index;
b@1608 457 // Create the HTML5 comment box 'textarea'
b@1608 458 var trackCommentBox = document.createElement('textarea');
b@1608 459 trackCommentBox.rows = '4';
b@1608 460 trackCommentBox.cols = '100';
b@1608 461 trackCommentBox.name = 'trackComment'+index;
b@1608 462 trackCommentBox.className = 'trackComment';
b@1608 463 var br = document.createElement('br');
b@1608 464 // Add to the holder.
b@1608 465 trackComment.appendChild(trackString);
b@1608 466 trackComment.appendChild(br);
b@1608 467 trackComment.appendChild(trackCommentBox);
b@1608 468 feedbackHolder.appendChild(trackComment);
b@1608 469 }
b@1608 470
b@1608 471 // Create a slider per track
b@1608 472
b@1608 473 var trackSliderObj = document.createElement('div');
b@1608 474 trackSliderObj.className = 'track-slider';
b@1608 475 trackSliderObj.id = 'track-slider-'+index;
b@1608 476 // Distribute it randomnly
b@1608 477 var w = window.innerWidth - 100;
b@1608 478 w = Math.random()*w;
b@1608 479 trackSliderObj.style.left = Math.floor(w)+50+'px';
b@1608 480 trackSliderObj.innerHTML = '<span>'+index+'</span>';
b@1608 481 trackSliderObj.draggable = true;
b@1608 482 trackSliderObj.ondragend = dragEnd;
b@1608 483 trackSliderObj.ondragstart = function()
b@1608 484 {
b@1608 485 var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99!
b@1608 486 audioEngineContext.metric.sliderMoveStart(id);
b@1608 487 };
b@1608 488
b@1608 489 // Onclick, switch playback to that track
b@1608 490 trackSliderObj.onclick = function() {
b@1608 491 // Get the track ID from the object ID
b@1608 492 var id = Number(this.id.substr(13,2)); // Maximum theoretical tracks is 99!
b@1608 493 audioEngineContext.metric.sliderPlayed(id);
b@1608 494 audioEngineContext.selectedTrack(id);
b@1608 495 // Currently playing track red, rest green
b@1608 496 document.getElementById('track-slider-'+index).style.backgroundColor = "#FF0000";
b@1608 497 for (var i = 0; i<$(currentTrackOrder).length; i++)
b@1608 498 {
b@1608 499 if (i!=index)
b@1608 500 {
b@1608 501 document.getElementById('track-slider-'+i).style.backgroundColor = "rgb(100,200,100)";
b@1608 502 }
b@1608 503
b@1608 504 }
b@1608 505 };
b@1608 506
b@1608 507 canvas.appendChild(trackSliderObj);
b@1608 508 });
b@1608 509
b@1608 510 // Append any commentQuestion boxes
b@1608 511 var commentQuestions = $(textXML).find('CommentQuestion');
b@1608 512 $(commentQuestions).each(function(index,element) {
b@1608 513 // Create document objects to hold the comment boxes
b@1608 514 var trackComment = document.createElement('div');
b@1608 515 trackComment.className = 'comment-div commentQuestion';
b@1608 516 trackComment.id = element.attributes['id'].value;
b@1608 517 // Create a string next to each comment asking for a comment
b@1608 518 var trackString = document.createElement('span');
b@1608 519 trackString.innerHTML = element.textContent;
b@1608 520 // Create the HTML5 comment box 'textarea'
b@1608 521 var trackCommentBox = document.createElement('textarea');
b@1608 522 trackCommentBox.rows = '4';
b@1608 523 trackCommentBox.cols = '100';
b@1608 524 trackCommentBox.name = 'commentQuestion'+index;
b@1608 525 trackCommentBox.className = 'trackComment';
b@1608 526 var br = document.createElement('br');
b@1608 527 // Add to the holder.
b@1608 528 trackComment.appendChild(trackString);
b@1608 529 trackComment.appendChild(br);
b@1608 530 trackComment.appendChild(trackCommentBox);
b@1608 531 feedbackHolder.appendChild(trackComment);
b@1608 532 });
b@1608 533
b@1608 534 // Now process any pre-test commands
b@1608 535
b@1608 536 var preTest = $(testXMLSetups[id]).find('PreTest')[0];
b@1608 537 if (preTest.children.length > 0)
b@1608 538 {
b@1608 539 currentState = 'testRunPre-'+id;
b@1608 540 preTestPopupStart(preTest);
b@1608 541 showPopup();
b@1608 542 } else {
b@1608 543 currentState = 'testRun-'+id;
b@1608 544 }
b@1608 545 }
b@1608 546
b@1608 547 function preTestPopupStart(preTest)
b@1608 548 {
b@1608 549 var popupHolder = document.getElementById('popupHolder');
b@1608 550 popupHolder.innerHTML = null;
b@1608 551 // Parse the first box
b@1608 552 var preTestOption = document.createElement('div');
b@1608 553 preTestOption.id = 'preTest';
b@1608 554 preTestOption.style.marginTop = '25px';
b@1608 555 preTestOption.align = "center";
b@1608 556 var child = preTest.children[0];
b@1608 557 if (child.nodeName == 'statement')
b@1608 558 {
b@1608 559 preTestOption.innerHTML = '<span>'+child.innerHTML+'</span>';
b@1608 560 } else if (child.nodeName == 'question')
b@1608 561 {
b@1608 562 var questionId = child.attributes['id'].value;
b@1608 563 var textHold = document.createElement('span');
b@1608 564 textHold.innerHTML = child.innerHTML;
b@1608 565 textHold.id = questionId + 'response';
b@1608 566 var textEnter = document.createElement('textarea');
b@1608 567 preTestOption.appendChild(textHold);
b@1608 568 preTestOption.appendChild(textEnter);
b@1608 569 }
b@1608 570 var nextButton = document.createElement('button');
b@1608 571 nextButton.className = 'popupButton';
b@1608 572 nextButton.value = '0';
b@1608 573 nextButton.innerHTML = 'Next';
b@1608 574 nextButton.onclick = popupButtonClick;
b@1608 575
b@1608 576 popupHolder.appendChild(preTestOption);
b@1608 577 popupHolder.appendChild(nextButton);
b@1608 578 }
b@1608 579
b@1608 580 function popupButtonClick()
b@1608 581 {
b@1608 582 // Global call from the 'Next' button click
b@1608 583 if (currentState == 'preTest')
b@1608 584 {
b@1608 585 // At the start of the preTest routine!
b@1608 586 var xmlTree = projectXML.find('setup');
b@1608 587 var preTest = xmlTree.find('PreTest')[0];
b@1608 588 this.value = preTestButtonClick(preTest,this.value);
b@1608 589 } else if (currentState.substr(0,10) == 'testRunPre')
b@1608 590 {
b@1608 591 //Specific test pre-test
b@1608 592 var testId = currentState.substr(11,currentState.length-10);
b@1608 593 var preTest = $(testXMLSetups[testId]).find('PreTest')[0];
b@1608 594 this.value = preTestButtonClick(preTest,this.value);
b@1608 595 } else if (currentState.substr(0,11) == 'testRunPost')
b@1608 596 {
b@1608 597 // Specific test post-test
b@1608 598 var testId = currentState.substr(12,currentState.length-11);
b@1608 599 var preTest = $(testXMLSetups[testId]).find('PostTest')[0];
b@1608 600 this.value = preTestButtonClick(preTest,this.value);
b@1608 601 } else if (currentState == 'postTest')
b@1608 602 {
b@1608 603 // At the end of the test, running global post test
b@1608 604 var xmlTree = projectXML.find('setup');
b@1608 605 var PostTest = xmlTree.find('PostTest')[0];
b@1608 606 this.value = preTestButtonClick(PostTest,this.value);
b@1608 607 }
b@1608 608 }
b@1608 609
b@1608 610 function preTestButtonClick(preTest,index)
b@1608 611 {
b@1608 612 // Called on click of pre-test button
b@1608 613 // Need to find and parse preTest again!
b@1608 614 var preTestOption = document.getElementById('preTest');
b@1608 615 // Check if current state is a question!
b@1608 616 if (preTest.children[index].nodeName == 'question') {
b@1608 617 var questionId = preTest.children[index].attributes['id'].value;
b@1608 618 var questionHold = document.createElement('comment');
b@1608 619 var questionResponse = document.getElementById(questionId + 'response');
b@1608 620 var mandatory = preTest.children[index].attributes['mandatory'];
b@1608 621 if (mandatory != undefined){
b@1608 622 if (mandatory.value == 'true') {mandatory = true;}
b@1608 623 else {mandatory = false;}
b@1608 624 } else {mandatory = false;}
b@1608 625 if (mandatory == true && questionResponse.value.length == 0) {
b@1608 626 return index;
b@1608 627 }
b@1608 628 questionHold.id = questionId;
b@1608 629 questionHold.innerHTML = questionResponse.value;
b@1608 630 postPopupResponse(questionHold);
b@1608 631 }
b@1608 632 index++;
b@1608 633 if (index < preTest.children.length)
b@1608 634 {
b@1608 635 // More to process
b@1608 636 var child = preTest.children[index];
b@1608 637 if (child.nodeName == 'statement')
b@1608 638 {
b@1608 639 preTestOption.innerHTML = '<span>'+child.innerHTML+'</span>';
b@1608 640 } else if (child.nodeName == 'question')
b@1608 641 {
b@1608 642 var textHold = document.createElement('span');
b@1608 643 textHold.innerHTML = child.innerHTML;
b@1608 644 var textEnter = document.createElement('textarea');
b@1608 645 textEnter.id = child.attributes['id'].value + 'response';
b@1608 646 var br = document.createElement('br');
b@1608 647 preTestOption.innerHTML = null;
b@1608 648 preTestOption.appendChild(textHold);
b@1608 649 preTestOption.appendChild(br);
b@1608 650 preTestOption.appendChild(textEnter);
b@1608 651 }
b@1608 652 } else {
b@1608 653 // Time to clear
b@1608 654 preTestOption.innerHTML = null;
b@1608 655 if (currentState != 'postTest') {
b@1608 656 hidePopup();
b@1608 657 // Progress the state!
b@1608 658 advanceState();
b@1608 659 } else {
b@1608 660 a = createProjectSave(projectReturn);
b@1608 661 preTestOption.appendChild(a);
b@1608 662 }
b@1608 663 }
b@1608 664 return index;
b@1608 665 }
b@1608 666
b@1608 667 function postPopupResponse(response)
b@1608 668 {
b@1608 669 if (currentState == 'preTest') {
b@1608 670 preTestQuestions.appendChild(response);
b@1608 671 } else if (currentState == 'postTest') {
b@1608 672 postTestQuestions.appendChild(response);
b@1608 673 } else {
b@1608 674 // Inside a specific test
b@1608 675 if (currentState.substr(0,10) == 'testRunPre') {
b@1608 676 // Pre Test
b@1608 677 var store = $(currentTestHolder).find('preTest');
b@1608 678 } else {
b@1608 679 // Post Test
b@1608 680 var store = $(currentTestHolder).find('postTest');
b@1608 681 }
b@1608 682 store[0].appendChild(response);
b@1608 683 }
b@1608 684 }
b@1608 685
b@1608 686 function showPopup()
b@1608 687 {
b@1608 688 var popupHolder = document.getElementById('popupHolder');
b@1608 689 popupHolder.style.zIndex = 3;
b@1608 690 popupHolder.style.visibility = 'visible';
b@1608 691 var blank = document.getElementsByClassName('testHalt')[0];
b@1608 692 blank.style.zIndex = 2;
b@1608 693 blank.style.visibility = 'visible';
b@1608 694 }
b@1608 695
b@1608 696 function hidePopup()
b@1608 697 {
b@1608 698 var popupHolder = document.getElementById('popupHolder');
b@1608 699 popupHolder.style.zIndex = -1;
b@1608 700 popupHolder.style.visibility = 'hidden';
b@1608 701 var blank = document.getElementsByClassName('testHalt')[0];
b@1608 702 blank.style.zIndex = -2;
b@1608 703 blank.style.visibility = 'hidden';
b@1608 704 }
b@1608 705
b@1608 706 function dragEnd(ev) {
b@1608 707 // Function call when a div has been dropped
b@1608 708 var slider = document.getElementById('slider');
b@1608 709 var w = slider.style.width;
b@1608 710 w = Number(w.substr(0,w.length-2));
b@1608 711 var x = ev.x;
b@1608 712 if (x >= 42 && x < w+42) {
b@1608 713 this.style.left = (x)+'px';
b@1608 714 } else {
b@1608 715 if (x<42) {
b@1608 716 this.style.left = '42px';
b@1608 717 } else {
b@1608 718 this.style.left = (w+42) + 'px';
b@1608 719 }
b@1608 720 }
b@1608 721 audioEngineContext.metric.sliderMoved();
b@1608 722 }
b@1608 723
b@1608 724 function advanceState()
b@1608 725 {
b@1608 726 console.log(currentState);
b@1608 727 if (currentState == 'preTest')
b@1608 728 {
b@1608 729 // End of pre-test, begin the test
b@1608 730 loadTest(0);
b@1608 731 } else if (currentState.substr(0,10) == 'testRunPre')
b@1608 732 {
b@1608 733 // Start the test
b@1608 734 var testId = currentState.substr(11,currentState.length-10);
b@1608 735 currentState = 'testRun-'+testId;
b@1608 736 } else if (currentState.substr(0,11) == 'testRunPost')
b@1608 737 {
b@1608 738 var testId = currentState.substr(12,currentState.length-11);
b@1608 739 testEnded(testId);
b@1608 740 } else if (currentState.substr(0,7) == 'testRun')
b@1608 741 {
b@1608 742 var testId = currentState.substr(8,currentState.length-7);
b@1608 743 // Check if we have any post tests to perform
b@1608 744 var postXML = $(testXMLSetups[testId]).find('PostTest')[0];
b@1608 745 if (postXML == undefined) {
b@1608 746 testEnded(testId);
b@1608 747 }
b@1608 748 else if (postXML.children.length > 0)
b@1608 749 {
b@1608 750 currentState = 'testRunPost-'+testId;
b@1608 751 showPopup();
b@1608 752 preTestPopupStart(postXML);
b@1608 753 }
b@1608 754 else {
b@1608 755
b@1608 756
b@1608 757 // No post tests, check if we have another test to perform instead
b@1608 758
b@1608 759 }
b@1608 760 }
b@1608 761 console.log(currentState);
b@1608 762 }
b@1608 763
b@1608 764 function testEnded(testId)
b@1608 765 {
b@1608 766 pageXMLSave(testId);
b@1608 767 if (testXMLSetups.length-1 > testId)
b@1608 768 {
b@1608 769 // Yes we have another test to perform
b@1608 770 testId = (Number(testId)+1);
b@1608 771 currentState = 'testRun-'+testId;
b@1608 772 loadTest(testId);
b@1608 773 } else {
b@1608 774 console.log('Testing Completed!');
b@1608 775 currentState = 'postTest';
b@1608 776 // Check for any post tests
b@1608 777 var xmlSetup = projectXML.find('setup');
b@1608 778 var postTest = xmlSetup.find('PostTest')[0];
b@1608 779 showPopup();
b@1608 780 preTestPopupStart(postTest);
b@1608 781 }
b@1608 782 }
b@1608 783
b@1608 784 function buttonSubmitClick()
b@1608 785 {
b@1608 786 if (audioEngineContext.status == 1) {
b@1608 787 var playback = document.getElementById('playback-button');
b@1608 788 playback.click();
b@1608 789 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
b@1608 790 } else
b@1608 791 {
b@1608 792 if (audioEngineContext.timer.testStarted == false)
b@1608 793 {
b@1608 794 alert('You have not started the test! Please press start to begin the test!');
b@1608 795 return;
b@1608 796 }
b@1608 797 }
b@1608 798 if (currentState.substr(0,7) == 'testRun')
b@1608 799 {
b@1608 800 audioEngineContext.timer.stopTest();
b@1608 801 advanceState();
b@1608 802 }
b@1608 803 }
b@1608 804
b@1608 805 function convSliderPosToRate(id)
b@1608 806 {
b@1608 807 var w = document.getElementById('slider').style.width;
b@1608 808 var maxPix = w.substr(0,w.length-2);
b@1608 809 var slider = document.getElementsByClassName('track-slider')[id];
b@1608 810 var pix = slider.style.left;
b@1608 811 pix = pix.substr(0,pix.length-2);
b@1608 812 var rate = (pix-42)/maxPix;
b@1608 813 return rate;
b@1608 814 }
b@1608 815
b@1608 816 function pageXMLSave(testId)
b@1608 817 {
b@1608 818 // Saves a specific test page
b@1608 819 var xmlDoc = currentTestHolder;
b@1608 820 // Check if any session wide metrics are enabled
b@1608 821
b@1608 822 var commentShow = testXMLSetups[testId].attributes['elementComments'];
b@1608 823 if (commentShow != undefined) {
b@1608 824 if (commentShow.value == 'false') {commentShow = false;}
b@1608 825 else {commentShow = true;}
b@1608 826 } else {commentShow = true;}
b@1608 827
b@1608 828 var metric = document.createElement('metric');
b@1608 829 if (audioEngineContext.metric.enableTestTimer)
b@1608 830 {
b@1608 831 var testTime = document.createElement('metricResult');
b@1608 832 testTime.id = 'testTime';
b@1608 833 testTime.textContent = audioEngineContext.timer.testDuration;
b@1608 834 metric.appendChild(testTime);
b@1608 835 }
b@1608 836 xmlDoc.appendChild(metric);
b@1608 837 var trackSliderObjects = document.getElementsByClassName('track-slider');
b@1608 838 var commentObjects = document.getElementsByClassName('comment-div');
b@1608 839 for (var i=0; i<trackSliderObjects.length; i++)
b@1608 840 {
b@1608 841 var audioElement = document.createElement('audioElement');
b@1608 842 audioElement.id = currentTrackOrder[i].attributes['id'].value;
b@1608 843 audioElement.url = currentTrackOrder[i].attributes['url'].value;
b@1608 844 var value = document.createElement('value');
b@1608 845 value.innerHTML = convSliderPosToRate(i);
b@1608 846 if (commentShow) {
b@1608 847 var comment = document.createElement("comment");
b@1608 848 var question = document.createElement("question");
b@1608 849 var response = document.createElement("response");
b@1608 850 question.textContent = commentObjects[i].children[0].textContent;
b@1608 851 response.textContent = commentObjects[i].children[2].value;
b@1608 852 comment.appendChild(question);
b@1608 853 comment.appendChild(response);
b@1608 854 audioElement.appendChild(comment);
b@1608 855 }
b@1608 856 audioElement.appendChild(value);
b@1608 857 // Check for any per element metrics
b@1608 858 var metric = document.createElement('metric');
b@1608 859 var elementMetric = audioEngineContext.audioObjects[i].metric;
b@1608 860 if (audioEngineContext.metric.enableElementTimer) {
b@1608 861 var elementTimer = document.createElement('metricResult');
b@1608 862 elementTimer.id = 'elementTimer';
b@1608 863 elementTimer.textContent = elementMetric.listenedTimer;
b@1608 864 metric.appendChild(elementTimer);
b@1608 865 }
b@1608 866 if (audioEngineContext.metric.enableElementTracker) {
b@1608 867 var elementTrackerFull = document.createElement('metricResult');
b@1608 868 elementTrackerFull.id = 'elementTrackerFull';
b@1608 869 var data = elementMetric.movementTracker;
b@1608 870 for (var k=0; k<data.length; k++)
b@1608 871 {
b@1608 872 var timePos = document.createElement('timePos');
b@1608 873 timePos.id = k;
b@1608 874 var time = document.createElement('time');
b@1608 875 time.textContent = data[k][0];
b@1608 876 var position = document.createElement('position');
b@1608 877 position.textContent = data[k][1];
b@1608 878 timePos.appendChild(time);
b@1608 879 timePos.appendChild(position);
b@1608 880 elementTrackerFull.appendChild(timePos);
b@1608 881 }
b@1608 882 metric.appendChild(elementTrackerFull);
b@1608 883 }
b@1608 884 if (audioEngineContext.metric.enableElementInitialPosition) {
b@1608 885 var elementInitial = document.createElement('metricResult');
b@1608 886 elementInitial.id = 'elementInitialPosition';
b@1608 887 elementInitial.textContent = elementMetric.initialPosition;
b@1608 888 metric.appendChild(elementInitial);
b@1608 889 }
b@1608 890 if (audioEngineContext.metric.enableFlagListenedTo) {
b@1608 891 var flagListenedTo = document.createElement('metricResult');
b@1608 892 flagListenedTo.id = 'elementFlagListenedTo';
b@1608 893 flagListenedTo.textContent = elementMetric.wasListenedTo;
b@1608 894 metric.appendChild(flagListenedTo);
b@1608 895 }
b@1608 896 if (audioEngineContext.metric.enableFlagMoved) {
b@1608 897 var flagMoved = document.createElement('metricResult');
b@1608 898 flagMoved.id = 'elementFlagMoved';
b@1608 899 flagMoved.textContent = elementMetric.wasMoved;
b@1608 900 metric.appendChild(flagMoved);
b@1608 901 }
b@1608 902 if (audioEngineContext.metric.enableFlagComments) {
b@1608 903 var flagComments = document.createElement('metricResult');
b@1608 904 flagComments.id = 'elementFlagComments';
b@1608 905 if (response.textContent.length == 0) {flag.textContent = 'false';}
b@1608 906 else {flag.textContet = 'true';}
b@1608 907 metric.appendChild(flagComments);
b@1608 908 }
b@1608 909 audioElement.appendChild(metric);
b@1608 910 xmlDoc.appendChild(audioElement);
b@1608 911 }
b@1608 912 var commentQuestion = document.getElementsByClassName('commentQuestion');
b@1608 913 for (var i=0; i<commentQuestion.length; i++)
b@1608 914 {
b@1608 915 var cqHolder = document.createElement('CommentQuestion');
b@1608 916 var comment = document.createElement('comment');
b@1608 917 var question = document.createElement('question');
b@1608 918 cqHolder.id = commentQuestion[i].id;
b@1608 919 comment.textContent = commentQuestion[i].children[2].value;
b@1608 920 question.textContent = commentQuestion[i].children[0].textContent;
b@1608 921 cqHolder.appendChild(question);
b@1608 922 cqHolder.appendChild(comment);
b@1608 923 xmlDoc.appendChild(cqHolder);
b@1608 924 }
b@1608 925 testResultsHolders[testId] = xmlDoc;
b@1608 926 }
b@1608 927
b@1608 928 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
b@1608 929 function interfaceXMLSave(){
b@1608 930 // Create the XML string to be exported with results
b@1608 931 var xmlDoc = document.createElement("BrowserEvaluationResult");
b@1608 932 for (var i=0; i<testResultsHolders.length; i++)
b@1608 933 {
b@1608 934 xmlDoc.appendChild(testResultsHolders[i]);
b@1608 935 }
b@1608 936 // Append Pre/Post Questions
b@1608 937 xmlDoc.appendChild(preTestQuestions);
b@1608 938 xmlDoc.appendChild(postTestQuestions);
b@1608 939
b@1608 940 return xmlDoc;
b@1608 941 }