annotate interfaces/ABX.js @ 1253:8aa21ebd9b15

WIP. ABX Framework. Minor core.js modifications.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Wed, 16 Mar 2016 13:31:42 +0000
parents
children 85f2fc7a17ca
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@1253 13 // Custom comparator Object
n@1253 14 Interface.prototype.comparator = null;
n@1253 15
n@1253 16 // The injection point into the HTML page
n@1253 17 interfaceContext.insertPoint = document.getElementById("topLevelBody");
n@1253 18 var testContent = document.createElement('div');
n@1253 19 testContent.id = 'testContent';
n@1253 20
n@1253 21 // Create the top div for the Title element
n@1253 22 var titleAttr = specification.title;
n@1253 23 var title = document.createElement('div');
n@1253 24 title.className = "title";
n@1253 25 title.align = "center";
n@1253 26 var titleSpan = document.createElement('span');
n@1253 27
n@1253 28 // Set title to that defined in XML, else set to default
n@1253 29 if (titleAttr != undefined) {
n@1253 30 titleSpan.textContent = titleAttr;
n@1253 31 } else {
n@1253 32 titleSpan.textContent = 'Listening test';
n@1253 33 }
n@1253 34 // Insert the titleSpan element into the title div element.
n@1253 35 title.appendChild(titleSpan);
n@1253 36
n@1253 37 var pagetitle = document.createElement('div');
n@1253 38 pagetitle.className = "pageTitle";
n@1253 39 pagetitle.align = "center";
n@1253 40 var titleSpan = document.createElement('span');
n@1253 41 titleSpan.id = "pageTitle";
n@1253 42 pagetitle.appendChild(titleSpan);
n@1253 43
n@1253 44 // Create Interface buttons!
n@1253 45 var interfaceButtons = document.createElement('div');
n@1253 46 interfaceButtons.id = 'interface-buttons';
n@1253 47 interfaceButtons.style.height = '25px';
n@1253 48
n@1253 49 // Create playback start/stop points
n@1253 50 var playback = document.createElement("button");
n@1253 51 playback.innerHTML = 'Stop';
n@1253 52 playback.id = 'playback-button';
n@1253 53 playback.style.float = 'left';
n@1253 54 // onclick function. Check if it is playing or not, call the correct function in the
n@1253 55 // audioEngine, change the button text to reflect the next state.
n@1253 56 playback.onclick = function() {
n@1253 57 if (audioEngineContext.status == 1) {
n@1253 58 audioEngineContext.stop();
n@1253 59 this.innerHTML = 'Stop';
n@1253 60 var time = audioEngineContext.timer.getTestTime();
n@1253 61 console.log('Stopped at ' + time); // DEBUG/SAFETY
n@1253 62 }
n@1253 63 };
n@1253 64 // Append the interface buttons into the interfaceButtons object.
n@1253 65 interfaceButtons.appendChild(playback);
n@1253 66
n@1253 67 // Global parent for the comment boxes on the page
n@1253 68 var feedbackHolder = document.createElement('div');
n@1253 69 feedbackHolder.id = 'feedbackHolder';
n@1253 70
n@1253 71 // Construct the AB Boxes
n@1253 72 var boxes = document.createElement('div');
n@1253 73 boxes.align = "center";
n@1253 74 boxes.id = "box-holders";
n@1253 75 boxes.style.float = "left";
n@1253 76
n@1253 77 var submit = document.createElement('button');
n@1253 78 submit.id = "submit";
n@1253 79 submit.onclick = buttonSubmitClick;
n@1253 80 submit.className = "big-button";
n@1253 81 submit.textContent = "submit";
n@1253 82 submit.style.position = "relative";
n@1253 83 submit.style.left = (window.innerWidth-250)/2 + 'px';
n@1253 84
n@1253 85 feedbackHolder.appendChild(boxes);
n@1253 86
n@1253 87 // Inject into HTML
n@1253 88 testContent.appendChild(title); // Insert the title
n@1253 89 testContent.appendChild(pagetitle);
n@1253 90 testContent.appendChild(interfaceButtons);
n@1253 91 testContent.appendChild(feedbackHolder);
n@1253 92 testContent.appendChild(submit);
n@1253 93 interfaceContext.insertPoint.appendChild(testContent);
n@1253 94
n@1253 95 // Load the full interface
n@1253 96 testState.initialise();
n@1253 97 testState.advanceState();
n@1253 98 };
n@1253 99
n@1253 100 function loadTest(page)
n@1253 101 {
n@1253 102 // Called each time a new test page is to be build. The page specification node is the only item passed in
n@1253 103 interfaceContext.comparator = new comparator(page);
n@1253 104 }
n@1253 105
n@1253 106 function comparator(page)
n@1253 107 {
n@1253 108 // Build prototype constructor
n@1253 109 this.interfaceObject = function(element,label)
n@1253 110 {
n@1253 111 // An example node, you can make this however you want for each audioElement.
n@1253 112 // However, every audioObject (audioEngineContext.audioObject) MUST have an interface object with the following
n@1253 113 // You attach them by calling audioObject.bindInterface( )
n@1253 114 this.parent = element;
n@1253 115 this.id = element.id;
n@1253 116 this.value = 0;
n@1253 117 this.disabled = true;
n@1253 118 this.box = document.createElement('div');
n@1253 119 this.box.className = 'comparator-holder';
n@1253 120 this.box.setAttribute('track-id',element.id);
n@1253 121 this.box.id = 'comparator-'+label;
n@1253 122 this.selector = document.createElement('div');
n@1253 123 this.selector.className = 'comparator-selector disabled';
n@1253 124 var selectorText = document.createElement('span');
n@1253 125 selectorText.textContent = label;
n@1253 126 this.selector.appendChild(selectorText);
n@1253 127 this.playback = document.createElement('button');
n@1253 128 this.playback.className = 'comparator-button';
n@1253 129 this.playback.disabled = true;
n@1253 130 this.playback.textContent = "Listen";
n@1253 131 this.box.appendChild(this.selector);
n@1253 132 this.box.appendChild(this.playback);
n@1253 133 this.selector.onclick = function(event)
n@1253 134 {
n@1253 135 var label = event.currentTarget.children[0].textContent;
n@1253 136 if (label == "X" || label == "x") {return;}
n@1253 137 var time = audioEngineContext.timer.getTestTime();
n@1253 138 if ($(event.currentTarget).hasClass('disabled'))
n@1253 139 {
n@1253 140 console.log("Please wait until sample has loaded");
n@1253 141 return;
n@1253 142 }
n@1253 143 if (audioEngineContext.status == 0)
n@1253 144 {
n@1253 145 alert("Please listen to the samples before making a selection");
n@1253 146 console.log("Please listen to the samples before making a selection");
n@1253 147 return;
n@1253 148 }
n@1253 149 var id = event.currentTarget.parentElement.getAttribute('track-id');
n@1253 150 interfaceContext.comparator.selected = id;
n@1253 151 if ($(event.currentTarget).hasClass("selected")) {
n@1253 152 $(".comparator-selector").removeClass('selected');
n@1253 153 for (var i=0; i<interfaceContext.comparator.comparators.length; i++)
n@1253 154 {
n@1253 155 var obj = interfaceContext.comparator.comparators[i];
n@1253 156 obj.parent.metric.moved(time,0);
n@1253 157 }
n@1253 158 } else {
n@1253 159 $(".comparator-selector").removeClass('selected');
n@1253 160 $(event.currentTarget).addClass('selected');
n@1253 161 for (var i=0; i<interfaceContext.comparator.comparators.length; i++)
n@1253 162 {
n@1253 163 var obj = interfaceContext.comparator.comparators[i];
n@1253 164 if (i == id) {
n@1253 165 obj.value = 1;
n@1253 166 } else {
n@1253 167 obj.value = 0;
n@1253 168 }
n@1253 169 obj.parent.metric.moved(time,obj.value);
n@1253 170 }
n@1253 171 console.log("Selected "+id+' ('+time+')');
n@1253 172 }
n@1253 173 };
n@1253 174 this.playback.setAttribute("playstate","ready");
n@1253 175 this.playback.onclick = function(event)
n@1253 176 {
n@1253 177 var id = event.currentTarget.parentElement.getAttribute('track-id');
n@1253 178 if (event.currentTarget.getAttribute("playstate") == "ready")
n@1253 179 {
n@1253 180 audioEngineContext.play(id);
n@1253 181 } else if (event.currentTarget.getAttribute("playstate") == "playing") {
n@1253 182 audioEngineContext.stop();
n@1253 183 }
n@1253 184
n@1253 185 };
n@1253 186 this.enable = function()
n@1253 187 {
n@1253 188 // This is used to tell the interface object that playback of this node is ready
n@1253 189 if (this.parent.state == 1)
n@1253 190 {
n@1253 191 $(this.selector).removeClass('disabled');
n@1253 192 this.playback.disabled = false;
n@1253 193 }
n@1253 194 };
n@1253 195 this.updateLoading = function(progress)
n@1253 196 {
n@1253 197 // progress is a value from 0 to 100 indicating the current download state of media files
n@1253 198 if (progress != 100)
n@1253 199 {
n@1253 200 progress = String(progress);
n@1253 201 progress = progress.split('.')[0];
n@1253 202 this.playback.textContent = progress+'%';
n@1253 203 } else {
n@1253 204 this.playback.textContent = "Play";
n@1253 205 }
n@1253 206 };
n@1253 207 this.error = function() {
n@1253 208 // audioObject has an error!!
n@1253 209 this.playback.textContent = "Error";
n@1253 210 $(this.playback).addClass("error-colour");
n@1253 211 };
n@1253 212 this.startPlayback = function()
n@1253 213 {
n@1253 214 // Called when playback has begun
n@1253 215 $('.comparator-button').text('Listen');
n@1253 216 $(this.playback).text('Stop');
n@1253 217 this.playback.setAttribute("playstate","playing");
n@1253 218 };
n@1253 219 this.stopPlayback = function()
n@1253 220 {
n@1253 221 // Called when playback has stopped. This gets called even if playback never started!
n@1253 222 $(this.playback).text('Listen');
n@1253 223 this.playback.setAttribute("playstate","ready");
n@1253 224 };
n@1253 225 this.getValue = function()
n@1253 226 {
n@1253 227 // Return the current value of the object. If there is no value, return 0
n@1253 228 return this.value;
n@1253 229 };
n@1253 230 this.getPresentedId = function()
n@1253 231 {
n@1253 232 // Return the presented ID of the object. For instance, the APE has sliders starting from 0. Whilst AB has alphabetical scale
n@1253 233 return this.selector.children[0].textContent;
n@1253 234 };
n@1253 235 this.canMove = function()
n@1253 236 {
n@1253 237 // 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 238 // These are checked primarily if the interface check option 'fragmentMoved' is enabled.
n@1253 239 return false;
n@1253 240 };
n@1253 241 this.exportXMLDOM = function(audioObject) {
n@1253 242 // Called by the audioObject holding this element to export the interface <value> node.
n@1253 243 // If there is no value node (such as outside reference), return null
n@1253 244 // 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 245 // Use storage.document.createElement('value'); to generate the XML node.
n@1253 246 var node = storage.document.createElement('value');
n@1253 247 node.textContent = this.value;
n@1253 248 return node;
n@1253 249
n@1253 250 };
n@1253 251 this.error = function() {
n@1253 252 // If there is an error with the audioObject, this will be called to indicate a failure
n@1253 253 }
n@1253 254 };
n@1253 255 // Ensure there are only two comparisons per page
n@1253 256 if (page.audioElements.length != 2) {
n@1253 257 console.error('FATAL - There must be 2 <audioelement> nodes on each <page>: '+page.id);
n@1253 258 return;
n@1253 259 }
n@1253 260 // Build the three audio elements
n@1253 261 this.pair = [];
n@1253 262 this.X = null;
n@1253 263 this.boxHolders = document.getElementById('box-holders');
n@1253 264 for (var index=0; index<page.audioElements.length; index++) {
n@1253 265 var element = page.audioElements[index];
n@1253 266 if (element.type != 'normal')
n@1253 267 {
n@1253 268 console.log("WARNING - ABX can only have normal elements. Page "+page.id+", Element "+element.id);
n@1253 269 element.type = "normal";
n@1253 270 }
n@1253 271 var audioObject = audioEngineContext.newTrack(element);
n@1253 272 var label;
n@1253 273 switch(audioObject.specification.parent.label) {
n@1253 274 case "none":
n@1253 275 label = "";
n@1253 276 break;
n@1253 277 case "number":
n@1253 278 label = ""+index;
n@1253 279 break;
n@1253 280 case "letter":
n@1253 281 label = String.fromCharCode(97 + index);
n@1253 282 break;
n@1253 283 default:
n@1253 284 label = String.fromCharCode(65 + index);
n@1253 285 break;
n@1253 286 }
n@1253 287 var node = new this.interfaceObject(audioObject,label);
n@1253 288 audioObject.bindInterface(node);
n@1253 289 this.pair.push(node);
n@1253 290 this.boxHolders.appendChild(node.box);
n@1253 291 }
n@1253 292 var elementId = Math.floor(Math.random() * 2); //Randomly pick A or B to be X
n@1253 293 var element = new page.audioElementNode();
n@1253 294 for (var atr in page.audioElements[elementId]) {
n@1253 295 eval("element."+atr+" = page.audioElements[elementId]."+atr);
n@1253 296 }
n@1253 297 element.id += "-X";
n@1253 298 if (typeof element.name == "string") {element.name+="-X";}
n@1253 299 page.audioElements.push(element);
n@1253 300 // Create the save place-holder for the 'X' element
n@1253 301 var root = testState.currentStore.XMLDOM;
n@1253 302 var aeNode = storage.document.createElement('audioelement');
n@1253 303 aeNode.setAttribute('ref',element.id);
n@1253 304 if (typeof element.name == "string"){aeNode.setAttribute('name',element.name);}
n@1253 305 aeNode.setAttribute('type','normal');
n@1253 306 aeNode.setAttribute('url',element.url);
n@1253 307 aeNode.setAttribute('gain',element.gain);
n@1253 308 aeNode.appendChild(storage.document.createElement('metric'));
n@1253 309 root.appendChild(aeNode);
n@1253 310 // Build the 'X' element
n@1253 311 var audioObject = audioEngineContext.newTrack(element);
n@1253 312 var label;
n@1253 313 switch(audioObject.specification.parent.label) {
n@1253 314 case "letter":
n@1253 315 label = "x";
n@1253 316 break;
n@1253 317 default:
n@1253 318 label = "X";
n@1253 319 break;
n@1253 320 }
n@1253 321 var node = new this.interfaceObject(audioObject,label);
n@1253 322 audioObject.bindInterface(node);
n@1253 323 this.X = node;
n@1253 324 this.boxHolders.appendChild(node.box);
n@1253 325 }
n@1253 326
n@1253 327 function resizeWindow(event)
n@1253 328 {
n@1253 329 // Called on every window resize event, use this to scale your page properly
n@1253 330 }
n@1253 331
n@1253 332 function buttonSubmitClick()
n@1253 333 {
n@1253 334 testState.advanceState();
n@1253 335 }
n@1253 336
n@1253 337 function pageXMLSave(store, pageSpecification)
n@1253 338 {
n@1253 339 // MANDATORY
n@1253 340 // Saves a specific test page
n@1253 341 // You can use this space to add any extra nodes to your XML <audioHolder> saves
n@1253 342 // Get the current <page> information in store (remember to appendChild your data to it)
n@1253 343 // pageSpecification is the current page node configuration
n@1253 344 // To create new XML nodes, use storage.document.createElement();
n@1253 345 }