annotate interfaces/ABX.js @ 1255:dcbf87684e99

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