annotate ape.js @ 1607:e5f1caa513d8

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