annotate interfaces/ABX.js @ 630:9dcfd654abad Dev_main

Fixed analysis.js table and latex generators.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Mon, 21 Mar 2016 16:07:57 +0000
parents 85f2fc7a17ca
children fd711d12dd14
rev   line source
n@619 1 /**
n@619 2 * WAET Blank Template
n@619 3 * Use this to start building your custom interface
n@619 4 */
n@619 5
n@619 6 // Once this is loaded and parsed, begin execution
n@619 7 loadInterface();
n@619 8
n@619 9 function loadInterface() {
n@619 10 // Use this to do any one-time page / element construction. For instance, placing any stationary text objects,
n@619 11 // holding div's, or setting up any nodes which are present for the entire test sequence
n@619 12
n@621 13 interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
n@621 14
n@619 15 // Custom comparator Object
n@619 16 Interface.prototype.comparator = null;
n@619 17
n@619 18 // The injection point into the HTML page
n@619 19 interfaceContext.insertPoint = document.getElementById("topLevelBody");
n@619 20 var testContent = document.createElement('div');
n@619 21 testContent.id = 'testContent';
n@619 22
n@619 23 // Create the top div for the Title element
n@619 24 var titleAttr = specification.title;
n@619 25 var title = document.createElement('div');
n@619 26 title.className = "title";
n@619 27 title.align = "center";
n@619 28 var titleSpan = document.createElement('span');
n@619 29
n@619 30 // Set title to that defined in XML, else set to default
n@619 31 if (titleAttr != undefined) {
n@619 32 titleSpan.textContent = titleAttr;
n@619 33 } else {
n@619 34 titleSpan.textContent = 'Listening test';
n@619 35 }
n@619 36 // Insert the titleSpan element into the title div element.
n@619 37 title.appendChild(titleSpan);
n@619 38
n@619 39 var pagetitle = document.createElement('div');
n@619 40 pagetitle.className = "pageTitle";
n@619 41 pagetitle.align = "center";
n@619 42 var titleSpan = document.createElement('span');
n@619 43 titleSpan.id = "pageTitle";
n@619 44 pagetitle.appendChild(titleSpan);
n@619 45
n@619 46 // Create Interface buttons!
n@619 47 var interfaceButtons = document.createElement('div');
n@619 48 interfaceButtons.id = 'interface-buttons';
n@619 49 interfaceButtons.style.height = '25px';
n@619 50
n@619 51 // Create playback start/stop points
n@619 52 var playback = document.createElement("button");
n@619 53 playback.innerHTML = 'Stop';
n@619 54 playback.id = 'playback-button';
n@619 55 playback.style.float = 'left';
n@619 56 // onclick function. Check if it is playing or not, call the correct function in the
n@619 57 // audioEngine, change the button text to reflect the next state.
n@619 58 playback.onclick = function() {
n@619 59 if (audioEngineContext.status == 1) {
n@619 60 audioEngineContext.stop();
n@619 61 this.innerHTML = 'Stop';
n@619 62 var time = audioEngineContext.timer.getTestTime();
n@619 63 console.log('Stopped at ' + time); // DEBUG/SAFETY
n@619 64 }
n@619 65 };
n@619 66 // Append the interface buttons into the interfaceButtons object.
n@619 67 interfaceButtons.appendChild(playback);
n@619 68
n@619 69 // Global parent for the comment boxes on the page
n@619 70 var feedbackHolder = document.createElement('div');
n@619 71 feedbackHolder.id = 'feedbackHolder';
n@619 72
n@619 73 // Construct the AB Boxes
n@619 74 var boxes = document.createElement('div');
n@619 75 boxes.align = "center";
n@619 76 boxes.id = "box-holders";
n@619 77 boxes.style.float = "left";
n@619 78
n@619 79 var submit = document.createElement('button');
n@619 80 submit.id = "submit";
n@619 81 submit.onclick = buttonSubmitClick;
n@619 82 submit.className = "big-button";
n@619 83 submit.textContent = "submit";
n@619 84 submit.style.position = "relative";
n@619 85 submit.style.left = (window.innerWidth-250)/2 + 'px';
n@619 86
n@619 87 feedbackHolder.appendChild(boxes);
n@619 88
n@619 89 // Inject into HTML
n@619 90 testContent.appendChild(title); // Insert the title
n@619 91 testContent.appendChild(pagetitle);
n@619 92 testContent.appendChild(interfaceButtons);
n@619 93 testContent.appendChild(feedbackHolder);
n@619 94 testContent.appendChild(submit);
n@619 95 interfaceContext.insertPoint.appendChild(testContent);
n@619 96
n@619 97 // Load the full interface
n@619 98 testState.initialise();
n@619 99 testState.advanceState();
n@619 100 };
n@619 101
n@619 102 function loadTest(page)
n@619 103 {
n@619 104 // Called each time a new test page is to be build. The page specification node is the only item passed in
n@621 105 document.getElementById('box-holders').innerHTML = null;
n@621 106
n@621 107 var interfaceObj = page.interfaces;
n@621 108 if (interfaceObj.length > 1)
n@621 109 {
n@621 110 console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
n@621 111 }
n@621 112 interfaceObj = interfaceObj[0];
n@621 113
n@621 114 if(interfaceObj.title != null)
n@621 115 {
n@621 116 document.getElementById("pageTitle").textContent = interfaceObj.title;
n@621 117 }
n@621 118
n@621 119 var interfaceOptions = specification.interfaces.options.concat(interfaceObj.options);
n@621 120 for (var option of interfaceOptions)
n@621 121 {
n@621 122 if (option.type == "show")
n@621 123 {
n@621 124 switch(option.name) {
n@621 125 case "playhead":
n@621 126 var playbackHolder = document.getElementById('playback-holder');
n@621 127 if (playbackHolder == null)
n@621 128 {
n@621 129 playbackHolder = document.createElement('div');
n@621 130 playbackHolder.style.width = "100%";
n@621 131 playbackHolder.style.float = "left";
n@621 132 playbackHolder.align = 'center';
n@621 133 playbackHolder.appendChild(interfaceContext.playhead.object);
n@621 134 feedbackHolder.appendChild(playbackHolder);
n@621 135 }
n@621 136 break;
n@621 137 case "page-count":
n@621 138 var pagecountHolder = document.getElementById('page-count');
n@621 139 if (pagecountHolder == null)
n@621 140 {
n@621 141 pagecountHolder = document.createElement('div');
n@621 142 pagecountHolder.id = 'page-count';
n@621 143 }
n@621 144 pagecountHolder.innerHTML = '<span>Page '+(testState.stateIndex+1)+' of '+testState.stateMap.length+'</span>';
n@621 145 var inject = document.getElementById('interface-buttons');
n@621 146 inject.appendChild(pagecountHolder);
n@621 147 break;
n@621 148 case "volume":
n@621 149 if (document.getElementById('master-volume-holder') == null)
n@621 150 {
n@621 151 feedbackHolder.appendChild(interfaceContext.volume.object);
n@621 152 }
n@621 153 break;
n@621 154 }
n@621 155 }
n@621 156 }
n@621 157
n@619 158 interfaceContext.comparator = new comparator(page);
n@621 159 resizeWindow(null);
n@619 160 }
n@619 161
n@619 162 function comparator(page)
n@619 163 {
n@619 164 // Build prototype constructor
n@619 165 this.interfaceObject = function(element,label)
n@619 166 {
n@619 167 // An example node, you can make this however you want for each audioElement.
n@619 168 // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
n@619 169 // You attach them by calling audioObject.bindInterface( )
n@619 170 this.parent = element;
n@619 171 this.id = element.id;
n@619 172 this.value = 0;
n@619 173 this.disabled = true;
n@619 174 this.box = document.createElement('div');
n@619 175 this.box.className = 'comparator-holder';
n@619 176 this.box.setAttribute('track-id',element.id);
n@619 177 this.box.id = 'comparator-'+label;
n@619 178 this.selector = document.createElement('div');
n@619 179 this.selector.className = 'comparator-selector disabled';
n@619 180 var selectorText = document.createElement('span');
n@619 181 selectorText.textContent = label;
n@619 182 this.selector.appendChild(selectorText);
n@619 183 this.playback = document.createElement('button');
n@619 184 this.playback.className = 'comparator-button';
n@619 185 this.playback.disabled = true;
n@619 186 this.playback.textContent = "Listen";
n@619 187 this.box.appendChild(this.selector);
n@619 188 this.box.appendChild(this.playback);
n@619 189 this.selector.onclick = function(event)
n@619 190 {
n@619 191 var label = event.currentTarget.children[0].textContent;
n@619 192 if (label == "X" || label == "x") {return;}
n@619 193 var time = audioEngineContext.timer.getTestTime();
n@619 194 if ($(event.currentTarget).hasClass('disabled'))
n@619 195 {
n@619 196 console.log("Please wait until sample has loaded");
n@619 197 return;
n@619 198 }
n@619 199 if (audioEngineContext.status == 0)
n@619 200 {
n@619 201 alert("Please listen to the samples before making a selection");
n@619 202 console.log("Please listen to the samples before making a selection");
n@619 203 return;
n@619 204 }
n@619 205 var id = event.currentTarget.parentElement.getAttribute('track-id');
n@619 206 interfaceContext.comparator.selected = id;
n@619 207 if ($(event.currentTarget).hasClass("selected")) {
n@619 208 $(".comparator-selector").removeClass('selected');
n@619 209 for (var i=0; i<interfaceContext.comparator.comparators.length; i++)
n@619 210 {
n@619 211 var obj = interfaceContext.comparator.comparators[i];
n@619 212 obj.parent.metric.moved(time,0);
n@619 213 }
n@619 214 } else {
n@619 215 $(".comparator-selector").removeClass('selected');
n@619 216 $(event.currentTarget).addClass('selected');
n@619 217 for (var i=0; i<interfaceContext.comparator.comparators.length; i++)
n@619 218 {
n@619 219 var obj = interfaceContext.comparator.comparators[i];
n@619 220 if (i == id) {
n@619 221 obj.value = 1;
n@619 222 } else {
n@619 223 obj.value = 0;
n@619 224 }
n@619 225 obj.parent.metric.moved(time,obj.value);
n@619 226 }
n@619 227 console.log("Selected "+id+' ('+time+')');
n@619 228 }
n@619 229 };
n@619 230 this.playback.setAttribute("playstate","ready");
n@619 231 this.playback.onclick = function(event)
n@619 232 {
n@619 233 var id = event.currentTarget.parentElement.getAttribute('track-id');
n@619 234 if (event.currentTarget.getAttribute("playstate") == "ready")
n@619 235 {
n@619 236 audioEngineContext.play(id);
n@619 237 } else if (event.currentTarget.getAttribute("playstate") == "playing") {
n@619 238 audioEngineContext.stop();
n@619 239 }
n@619 240
n@619 241 };
n@619 242 this.enable = function()
n@619 243 {
n@619 244 // This is used to tell the interface object that playback of this node is ready
n@619 245 if (this.parent.state == 1)
n@619 246 {
n@619 247 $(this.selector).removeClass('disabled');
n@619 248 this.playback.disabled = false;
n@619 249 }
n@619 250 };
n@619 251 this.updateLoading = function(progress)
n@619 252 {
n@619 253 // progress is a value from 0 to 100 indicating the current download state of media files
n@619 254 if (progress != 100)
n@619 255 {
n@619 256 progress = String(progress);
n@619 257 progress = progress.split('.')[0];
n@619 258 this.playback.textContent = progress+'%';
n@619 259 } else {
n@619 260 this.playback.textContent = "Play";
n@619 261 }
n@619 262 };
n@619 263 this.error = function() {
n@619 264 // audioObject has an error!!
n@619 265 this.playback.textContent = "Error";
n@619 266 $(this.playback).addClass("error-colour");
n@619 267 };
n@619 268 this.startPlayback = function()
n@619 269 {
n@619 270 // Called when playback has begun
n@619 271 $('.comparator-button').text('Listen');
n@619 272 $(this.playback).text('Stop');
n@619 273 this.playback.setAttribute("playstate","playing");
n@619 274 };
n@619 275 this.stopPlayback = function()
n@619 276 {
n@619 277 // Called when playback has stopped. This gets called even if playback never started!
n@619 278 $(this.playback).text('Listen');
n@619 279 this.playback.setAttribute("playstate","ready");
n@619 280 };
n@619 281 this.getValue = function()
n@619 282 {
n@619 283 // Return the current value of the object. If there is no value, return 0
n@619 284 return this.value;
n@619 285 };
n@619 286 this.getPresentedId = function()
n@619 287 {
n@619 288 // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
n@619 289 return this.selector.children[0].textContent;
n@619 290 };
n@619 291 this.canMove = function()
n@619 292 {
n@619 293 // Return either true or false if the interface object can be moved. AB / Reference cannot, whilst sliders can and therefore have a continuous scale.
n@619 294 // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
n@619 295 return false;
n@619 296 };
n@619 297 this.exportXMLDOM = function(audioObject) {
n@619 298 // Called by the audioObject holding this element to export the interface <value> node.
n@619 299 // If there is no value node (such as outside reference), return null
n@619 300 // If there are multiple value nodes (such as multiple scale / 2D scales), return an array of nodes with each value node having an 'interfaceName' attribute
n@619 301 // Use storage.document.createElement('value'); to generate the XML node.
n@619 302 var node = storage.document.createElement('value');
n@619 303 node.textContent = this.value;
n@619 304 return node;
n@619 305
n@619 306 };
n@619 307 this.error = function() {
n@619 308 // If there is an error with the audioObject, this will be called to indicate a failure
n@619 309 }
n@619 310 };
n@619 311 // Ensure there are only two comparisons per page
n@619 312 if (page.audioElements.length != 2) {
n@619 313 console.error('FATAL - There must be 2 <audioelement> nodes on each <page>: '+page.id);
n@619 314 return;
n@619 315 }
n@619 316 // Build the three audio elements
n@619 317 this.pair = [];
n@619 318 this.X = null;
n@619 319 this.boxHolders = document.getElementById('box-holders');
n@619 320 for (var index=0; index<page.audioElements.length; index++) {
n@619 321 var element = page.audioElements[index];
n@619 322 if (element.type != 'normal')
n@619 323 {
n@619 324 console.log("WARNING - ABX can only have normal elements. Page "+page.id+", Element "+element.id);
n@619 325 element.type = "normal";
n@619 326 }
n@619 327 var audioObject = audioEngineContext.newTrack(element);
n@619 328 var label;
n@619 329 switch(audioObject.specification.parent.label) {
n@619 330 case "none":
n@619 331 label = "";
n@619 332 break;
n@619 333 case "number":
n@619 334 label = ""+index;
n@619 335 break;
n@619 336 case "letter":
n@619 337 label = String.fromCharCode(97 + index);
n@619 338 break;
n@619 339 default:
n@619 340 label = String.fromCharCode(65 + index);
n@619 341 break;
n@619 342 }
n@619 343 var node = new this.interfaceObject(audioObject,label);
n@619 344 audioObject.bindInterface(node);
n@619 345 this.pair.push(node);
n@619 346 this.boxHolders.appendChild(node.box);
n@619 347 }
n@619 348 var elementId = Math.floor(Math.random() * 2); //Randomly pick A or B to be X
n@619 349 var element = new page.audioElementNode();
n@619 350 for (var atr in page.audioElements[elementId]) {
n@619 351 eval("element."+atr+" = page.audioElements[elementId]."+atr);
n@619 352 }
n@619 353 element.id += "-X";
n@619 354 if (typeof element.name == "string") {element.name+="-X";}
n@619 355 page.audioElements.push(element);
n@619 356 // Create the save place-holder for the 'X' element
n@619 357 var root = testState.currentStore.XMLDOM;
n@619 358 var aeNode = storage.document.createElement('audioelement');
n@619 359 aeNode.setAttribute('ref',element.id);
n@619 360 if (typeof element.name == "string"){aeNode.setAttribute('name',element.name);}
n@619 361 aeNode.setAttribute('type','normal');
n@619 362 aeNode.setAttribute('url',element.url);
n@619 363 aeNode.setAttribute('gain',element.gain);
n@619 364 aeNode.appendChild(storage.document.createElement('metric'));
n@619 365 root.appendChild(aeNode);
n@619 366 // Build the 'X' element
n@619 367 var audioObject = audioEngineContext.newTrack(element);
n@619 368 var label;
n@619 369 switch(audioObject.specification.parent.label) {
n@619 370 case "letter":
n@619 371 label = "x";
n@619 372 break;
n@619 373 default:
n@619 374 label = "X";
n@619 375 break;
n@619 376 }
n@619 377 var node = new this.interfaceObject(audioObject,label);
n@619 378 audioObject.bindInterface(node);
n@619 379 this.X = node;
n@619 380 this.boxHolders.appendChild(node.box);
n@619 381 }
n@619 382
n@619 383 function resizeWindow(event)
n@619 384 {
n@621 385 document.getElementById('submit').style.left = (window.innerWidth-250)/2 + 'px';
n@621 386 var numObj = 3;
n@621 387 var boxW = numObj*312;
n@621 388 var diff = window.innerWidth - boxW;
n@621 389 while (diff < 0)
n@621 390 {
n@621 391 numObj = Math.ceil(numObj/2);
n@621 392 boxW = numObj*312;
n@621 393 diff = window.innerWidth - boxW;
n@621 394 }
n@621 395 document.getElementById('box-holders').style.marginLeft = diff/2 + 'px';
n@621 396 document.getElementById('box-holders').style.marginRight = diff/2 + 'px';
n@621 397 document.getElementById('box-holders').style.width = boxW + 'px';
n@619 398 }
n@619 399
n@619 400 function buttonSubmitClick()
n@619 401 {
n@621 402 var checks = [];
n@621 403 checks = checks.concat(testState.currentStateMap.interfaces[0].options);
n@621 404 checks = checks.concat(specification.interfaces.options);
n@621 405 var canContinue = true;
n@621 406
n@621 407 for (var i=0; i<checks.length; i++) {
n@621 408 if (checks[i].type == 'check')
n@621 409 {
n@621 410 switch(checks[i].name) {
n@621 411 case 'fragmentPlayed':
n@621 412 // Check if all fragments have been played
n@621 413 var checkState = interfaceContext.checkAllPlayed();
n@621 414 if (checkState == false) {canContinue = false;}
n@621 415 break;
n@621 416 case 'fragmentFullPlayback':
n@621 417 // Check all fragments have been played to their full length
n@621 418 var checkState = interfaceContext.checkFragmentsFullyPlayed();
n@621 419 if (checkState == false) {canContinue = false;}
n@621 420 break;
n@621 421 case 'fragmentMoved':
n@621 422 // Check all fragment sliders have been moved.
n@621 423 var checkState = interfaceContext.checkAllMoved();
n@621 424 if (checkState == false) {canContinue = false;}
n@621 425 break;
n@621 426 case 'fragmentComments':
n@621 427 // Check all fragment sliders have been moved.
n@621 428 var checkState = interfaceContext.checkAllCommented();
n@621 429 if (checkState == false) {canContinue = false;}
n@621 430 break;
n@621 431 default:
n@621 432 console.log("WARNING - Check option "+checks[i].check+" is not supported on this interface");
n@621 433 break;
n@621 434 }
n@621 435
n@621 436 }
n@621 437 if (!canContinue) {break;}
n@621 438 }
n@621 439 if (canContinue)
n@621 440 {
n@621 441 if (audioEngineContext.status == 1) {
n@621 442 var playback = document.getElementById('playback-button');
n@621 443 playback.click();
n@621 444 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
n@621 445 } else
n@621 446 {
n@621 447 if (audioEngineContext.timer.testStarted == false)
n@621 448 {
n@621 449 alert('You have not started the test! Please press start to begin the test!');
n@621 450 return;
n@621 451 }
n@621 452 }
n@621 453 testState.advanceState();
n@621 454 }
n@619 455 }
n@619 456
n@619 457 function pageXMLSave(store, pageSpecification)
n@619 458 {
n@619 459 // MANDATORY
n@619 460 // Saves a specific test page
n@619 461 // You can use this space to add any extra nodes to your XML <audioHolder> saves
n@619 462 // Get the current <page> information in store (remember to appendChild your data to it)
n@619 463 // pageSpecification is the current page node configuration
n@619 464 // To create new XML nodes, use storage.document.createElement();
n@619 465 }