annotate ape.js @ 1606:45d3860b350a

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