annotate mushra.js @ 1318:64541cd9265d

Added schema for Test Specification Document for validation (wip)
author Nicholas Jillings <nickjillings@users.noreply.github.com>
date Wed, 23 Dec 2015 12:41:08 +0000
parents
children 41ef099fbb6a
rev   line source
nickjillings@1318 1 /**
nickjillings@1318 2 * mushra.js
nickjillings@1318 3 * Create the MUSHRA interface
nickjillings@1318 4 */
nickjillings@1318 5
nickjillings@1318 6 // Once this is loaded and parsed, begin execution
nickjillings@1318 7 loadInterface();
nickjillings@1318 8
nickjillings@1318 9 function loadInterface() {
nickjillings@1318 10 // Get the dimensions of the screen available to the page
nickjillings@1318 11 var width = window.innerWidth;
nickjillings@1318 12 var height = window.innerHeight;
nickjillings@1318 13
nickjillings@1318 14 // The injection point into the HTML page
nickjillings@1318 15 interfaceContext.insertPoint = document.getElementById("topLevelBody");
nickjillings@1318 16 var testContent = document.createElement('div');
nickjillings@1318 17 testContent.id = 'testContent';
nickjillings@1318 18
nickjillings@1318 19 // Create the top div for the Title element
nickjillings@1318 20 var titleAttr = specification.title;
nickjillings@1318 21 var title = document.createElement('div');
nickjillings@1318 22 title.className = "title";
nickjillings@1318 23 title.align = "center";
nickjillings@1318 24 var titleSpan = document.createElement('span');
nickjillings@1318 25
nickjillings@1318 26 // Set title to that defined in XML, else set to default
nickjillings@1318 27 if (titleAttr != undefined) {
nickjillings@1318 28 titleSpan.textContent = titleAttr;
nickjillings@1318 29 } else {
nickjillings@1318 30 titleSpan.textContent = 'Listening test';
nickjillings@1318 31 }
nickjillings@1318 32 // Insert the titleSpan element into the title div element.
nickjillings@1318 33 title.appendChild(titleSpan);
nickjillings@1318 34
nickjillings@1318 35 var pagetitle = document.createElement('div');
nickjillings@1318 36 pagetitle.className = "pageTitle";
nickjillings@1318 37 pagetitle.align = "center";
nickjillings@1318 38 var titleSpan = document.createElement('span');
nickjillings@1318 39 titleSpan.id = "pageTitle";
nickjillings@1318 40 pagetitle.appendChild(titleSpan);
nickjillings@1318 41
nickjillings@1318 42 // Create Interface buttons!
nickjillings@1318 43 var interfaceButtons = document.createElement('div');
nickjillings@1318 44 interfaceButtons.id = 'interface-buttons';
nickjillings@1318 45
nickjillings@1318 46 // Create playback start/stop points
nickjillings@1318 47 var playback = document.createElement("button");
nickjillings@1318 48 playback.innerHTML = 'Stop';
nickjillings@1318 49 playback.id = 'playback-button';
nickjillings@1318 50 playback.style.float = 'left';
nickjillings@1318 51 // onclick function. Check if it is playing or not, call the correct function in the
nickjillings@1318 52 // audioEngine, change the button text to reflect the next state.
nickjillings@1318 53 playback.onclick = function() {
nickjillings@1318 54 if (audioEngineContext.status == 1) {
nickjillings@1318 55 audioEngineContext.stop();
nickjillings@1318 56 this.innerHTML = 'Stop';
nickjillings@1318 57 var time = audioEngineContext.timer.getTestTime();
nickjillings@1318 58 console.log('Stopped at ' + time); // DEBUG/SAFETY
nickjillings@1318 59 }
nickjillings@1318 60 };
nickjillings@1318 61 // Create Submit (save) button
nickjillings@1318 62 var submit = document.createElement("button");
nickjillings@1318 63 submit.innerHTML = 'Submit';
nickjillings@1318 64 submit.onclick = buttonSubmitClick;
nickjillings@1318 65 submit.id = 'submit-button';
nickjillings@1318 66 submit.style.float = 'left';
nickjillings@1318 67 // Append the interface buttons into the interfaceButtons object.
nickjillings@1318 68 interfaceButtons.appendChild(playback);
nickjillings@1318 69 interfaceButtons.appendChild(submit);
nickjillings@1318 70
nickjillings@1318 71 // Create a slider box
nickjillings@1318 72 var sliderBox = document.createElement('div');
nickjillings@1318 73 sliderBox.style.width = "100%";
nickjillings@1318 74 sliderBox.style.height = window.innerHeight - 200+12 + 'px';
nickjillings@1318 75 sliderBox.style.marginBottom = '10px';
nickjillings@1318 76 sliderBox.id = 'slider';
nickjillings@1318 77 var scaleHolder = document.createElement('div');
nickjillings@1318 78 scaleHolder.id = "scale-holder";
nickjillings@1318 79 sliderBox.appendChild(scaleHolder);
nickjillings@1318 80 var sliderObjectHolder = document.createElement('div');
nickjillings@1318 81 sliderObjectHolder.id = 'slider-holder';
nickjillings@1318 82 sliderObjectHolder.align = "center";
nickjillings@1318 83 sliderBox.appendChild(sliderObjectHolder);
nickjillings@1318 84
nickjillings@1318 85 // Global parent for the comment boxes on the page
nickjillings@1318 86 var feedbackHolder = document.createElement('div');
nickjillings@1318 87 feedbackHolder.id = 'feedbackHolder';
nickjillings@1318 88
nickjillings@1318 89 testContent.style.zIndex = 1;
nickjillings@1318 90 interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
nickjillings@1318 91
nickjillings@1318 92 // Inject into HTML
nickjillings@1318 93 testContent.appendChild(title); // Insert the title
nickjillings@1318 94 testContent.appendChild(pagetitle);
nickjillings@1318 95 testContent.appendChild(interfaceButtons);
nickjillings@1318 96 testContent.appendChild(sliderBox);
nickjillings@1318 97 testContent.appendChild(feedbackHolder);
nickjillings@1318 98 interfaceContext.insertPoint.appendChild(testContent);
nickjillings@1318 99
nickjillings@1318 100 // Load the full interface
nickjillings@1318 101 testState.initialise();
nickjillings@1318 102 testState.advanceState();
nickjillings@1318 103 }
nickjillings@1318 104
nickjillings@1318 105 function loadTest(audioHolderObject)
nickjillings@1318 106 {
nickjillings@1318 107 var id = audioHolderObject.id;
nickjillings@1318 108
nickjillings@1318 109 var feedbackHolder = document.getElementById('feedbackHolder');
nickjillings@1318 110 var interfaceObj = audioHolderObject.interfaces;
nickjillings@1318 111 if (interfaceObj.length > 1)
nickjillings@1318 112 {
nickjillings@1318 113 console.log("WARNING - This interface only supports one <interface> node per page. Using first interface node");
nickjillings@1318 114 }
nickjillings@1318 115 interfaceObj = interfaceObj[0];
nickjillings@1318 116 if(interfaceObj.title != null)
nickjillings@1318 117 {
nickjillings@1318 118 document.getElementById("pageTitle").textContent = interfaceObj.title;
nickjillings@1318 119 }
nickjillings@1318 120
nickjillings@1318 121 // Delete outside reference
nickjillings@1318 122 var outsideReferenceHolder = document.getElementById('outside-reference');
nickjillings@1318 123 if (outsideReferenceHolder != null) {
nickjillings@1318 124 document.getElementById('interface-buttons').removeChild(outsideReferenceHolder);
nickjillings@1318 125 }
nickjillings@1318 126
nickjillings@1318 127 var sliderBox = document.getElementById('slider-holder');
nickjillings@1318 128 feedbackHolder.innerHTML = null;
nickjillings@1318 129 sliderBox.innerHTML = null;
nickjillings@1318 130
nickjillings@1318 131 var commentBoxPrefix = "Comment on track";
nickjillings@1318 132 if (interfaceObj.commentBoxPrefix != undefined) {
nickjillings@1318 133 commentBoxPrefix = interfaceObj.commentBoxPrefix;
nickjillings@1318 134 }
nickjillings@1318 135 var loopPlayback = audioHolderObject.loop;
nickjillings@1318 136
nickjillings@1318 137 currentTestHolder = document.createElement('audioHolder');
nickjillings@1318 138 currentTestHolder.id = audioHolderObject.id;
nickjillings@1318 139 currentTestHolder.repeatCount = audioHolderObject.repeatCount;
nickjillings@1318 140
nickjillings@1318 141 $(audioHolderObject.commentQuestions).each(function(index,element) {
nickjillings@1318 142 var node = interfaceContext.createCommentQuestion(element);
nickjillings@1318 143 feedbackHolder.appendChild(node.holder);
nickjillings@1318 144 });
nickjillings@1318 145
nickjillings@1318 146 // Find all the audioElements from the audioHolder
nickjillings@1318 147 $(audioHolderObject.audioElements).each(function(index,element){
nickjillings@1318 148 // Find URL of track
nickjillings@1318 149 // In this jQuery loop, variable 'this' holds the current audioElement.
nickjillings@1318 150
nickjillings@1318 151 // Check if an outside reference
nickjillings@1318 152 if (index == audioHolderObject.outsideReference)
nickjillings@1318 153 {
nickjillings@1318 154 return;
nickjillings@1318 155 }
nickjillings@1318 156
nickjillings@1318 157 var audioObject = audioEngineContext.newTrack(element);
nickjillings@1318 158
nickjillings@1318 159 var node = interfaceContext.createCommentBox(audioObject);
nickjillings@1318 160
nickjillings@1318 161 // Create a slider per track
nickjillings@1318 162 audioObject.interfaceDOM = new sliderObject(audioObject);
nickjillings@1318 163
nickjillings@1318 164 if (typeof audioHolderObject.initialPosition === "number")
nickjillings@1318 165 {
nickjillings@1318 166 // Set the values
nickjillings@1318 167 audioObject.interfaceDOM.slider.value = audioHolderObject.initalPosition;
nickjillings@1318 168 } else {
nickjillings@1318 169 // Distribute it randomnly
nickjillings@1318 170 audioObject.interfaceDOM.slider.value = Math.random();
nickjillings@1318 171 }
nickjillings@1318 172
nickjillings@1318 173 sliderBox.appendChild(audioObject.interfaceDOM.holder);
nickjillings@1318 174 audioObject.metric.initialised(audioObject.interfaceDOM.slider.value);
nickjillings@1318 175
nickjillings@1318 176 });
nickjillings@1318 177
nickjillings@1318 178 // Auto-align
nickjillings@1318 179 var numObj = audioHolderObject.audioElements.length;
nickjillings@1318 180 var totalWidth = (numObj-1)*150+100;
nickjillings@1318 181 var diff = (window.innerWidth - totalWidth)/2;
nickjillings@1318 182 sliderBox.style.marginLeft = diff + 'px';
nickjillings@1318 183
nickjillings@1318 184 // Construct outside reference;
nickjillings@1318 185 if (audioHolderObject.outsideReference != null) {
nickjillings@1318 186 var outsideReferenceHolder = document.createElement('div');
nickjillings@1318 187 outsideReferenceHolder.id = 'outside-reference';
nickjillings@1318 188 outsideReferenceHolder.className = 'outside-reference';
nickjillings@1318 189 outsideReferenceHolderspan = document.createElement('span');
nickjillings@1318 190 outsideReferenceHolderspan.textContent = 'Reference';
nickjillings@1318 191 outsideReferenceHolder.appendChild(outsideReferenceHolderspan);
nickjillings@1318 192
nickjillings@1318 193 var audioObject = audioEngineContext.newTrack(audioHolderObject.audioElements[audioHolderObject.outsideReference]);
nickjillings@1318 194
nickjillings@1318 195 outsideReferenceHolder.onclick = function(event)
nickjillings@1318 196 {
nickjillings@1318 197 audioEngineContext.play(audioEngineContext.audioObjects.length-1);
nickjillings@1318 198 $('.track-slider').removeClass('track-slider-playing');
nickjillings@1318 199 $('.comment-div').removeClass('comment-box-playing');
nickjillings@1318 200 if (event.currentTarget.nodeName == 'DIV') {
nickjillings@1318 201 $(event.currentTarget).addClass('track-slider-playing');
nickjillings@1318 202 } else {
nickjillings@1318 203 $(event.currentTarget.parentElement).addClass('track-slider-playing');
nickjillings@1318 204 }
nickjillings@1318 205 };
nickjillings@1318 206
nickjillings@1318 207 document.getElementById('interface-buttons').appendChild(outsideReferenceHolder);
nickjillings@1318 208 }
nickjillings@1318 209 }
nickjillings@1318 210
nickjillings@1318 211 function sliderObject(audioObject)
nickjillings@1318 212 {
nickjillings@1318 213 // Constructs the slider object. We use the HTML5 slider object
nickjillings@1318 214 this.parent = audioObject;
nickjillings@1318 215 this.holder = document.createElement('div');
nickjillings@1318 216 this.title = document.createElement('span');
nickjillings@1318 217 this.slider = document.createElement('input');
nickjillings@1318 218 this.play = document.createElement('button');
nickjillings@1318 219
nickjillings@1318 220 this.holder.className = 'track-slider';
nickjillings@1318 221 this.holder.style.height = window.innerHeight-200 + 'px';
nickjillings@1318 222 this.holder.appendChild(this.title);
nickjillings@1318 223 this.holder.appendChild(this.slider);
nickjillings@1318 224 this.holder.appendChild(this.play);
nickjillings@1318 225 this.holder.align = "center";
nickjillings@1318 226 if (audioObject.id == 0)
nickjillings@1318 227 {
nickjillings@1318 228 this.holder.style.marginLeft = '0px';
nickjillings@1318 229 }
nickjillings@1318 230 this.holder.setAttribute('trackIndex',audioObject.id);
nickjillings@1318 231
nickjillings@1318 232 this.title.textContent = audioObject.id;
nickjillings@1318 233 this.title.style.width = "100%";
nickjillings@1318 234 this.title.style.float = "left";
nickjillings@1318 235
nickjillings@1318 236 this.slider.type = "range";
nickjillings@1318 237 this.slider.className = "track-slider-range track-slider-not-moved";
nickjillings@1318 238 this.slider.min = "0";
nickjillings@1318 239 this.slider.max = "1";
nickjillings@1318 240 this.slider.step = "0.01";
nickjillings@1318 241 this.slider.setAttribute('orient','vertical');
nickjillings@1318 242 this.slider.style.height = window.innerHeight-250 + 'px';
nickjillings@1318 243 this.slider.onchange = function()
nickjillings@1318 244 {
nickjillings@1318 245 var time = audioEngineContext.timer.getTestTime();
nickjillings@1318 246 var id = Number(this.parentNode.getAttribute('trackIndex'));
nickjillings@1318 247 audioEngineContext.audioObjects[id].metric.moved(time,this.value);
nickjillings@1318 248 console.log('slider '+id+' moved to '+this.value+' ('+time+')');
nickjillings@1318 249 $(this).removeClass('track-slider-not-moved');
nickjillings@1318 250 };
nickjillings@1318 251
nickjillings@1318 252 this.play.textContent = "Loading...";
nickjillings@1318 253 this.play.value = audioObject.id;
nickjillings@1318 254 this.play.style.float = "left";
nickjillings@1318 255 this.play.style.width = "100%";
nickjillings@1318 256 this.play.disabled = true;
nickjillings@1318 257 this.play.onclick = function(event)
nickjillings@1318 258 {
nickjillings@1318 259 var id = Number(event.currentTarget.value);
nickjillings@1318 260 //audioEngineContext.metric.sliderPlayed(id);
nickjillings@1318 261 audioEngineContext.play(id);
nickjillings@1318 262 $(".track-slider").removeClass('track-slider-playing');
nickjillings@1318 263 $(event.currentTarget.parentElement).addClass('track-slider-playing');
nickjillings@1318 264 var outsideReference = document.getElementById('outside-reference');
nickjillings@1318 265 if (outsideReference != null) {
nickjillings@1318 266 $(outsideReference).removeClass('track-slider-playing');
nickjillings@1318 267 }
nickjillings@1318 268 };
nickjillings@1318 269
nickjillings@1318 270 this.enable = function() {
nickjillings@1318 271 this.play.disabled = false;
nickjillings@1318 272 this.play.textContent = "Play";
nickjillings@1318 273 $(this.slider).removeClass('track-slider-disabled');
nickjillings@1318 274 };
nickjillings@1318 275
nickjillings@1318 276 this.exportXMLDOM = function(audioObject) {
nickjillings@1318 277 // Called by the audioObject holding this element. Must be present
nickjillings@1318 278 var node = document.createElement('value');
nickjillings@1318 279 node.textContent = this.slider.value;
nickjillings@1318 280 return node;
nickjillings@1318 281 };
nickjillings@1318 282 this.getValue = function() {
nickjillings@1318 283 return this.slider.value;
nickjillings@1318 284 };
nickjillings@1318 285
nickjillings@1318 286 this.resize = function(event)
nickjillings@1318 287 {
nickjillings@1318 288 this.holder.style.height = window.innerHeight-200 + 'px';
nickjillings@1318 289 this.slider.style.height = window.innerHeight-250 + 'px';
nickjillings@1318 290 };
nickjillings@1318 291 this.updateLoading = function(progress)
nickjillings@1318 292 {
nickjillings@1318 293 progress = String(progress);
nickjillings@1318 294 progress = progress.substr(0,5);
nickjillings@1318 295 this.play.textContent = "Loading: "+progress+"%";
nickjillings@1318 296 };
nickjillings@1318 297
nickjillings@1318 298 if (this.parent.state == 1)
nickjillings@1318 299 {
nickjillings@1318 300 this.enable();
nickjillings@1318 301 }
nickjillings@1318 302 }
nickjillings@1318 303
nickjillings@1318 304 function resizeWindow(event)
nickjillings@1318 305 {
nickjillings@1318 306 // Function called when the window has been resized.
nickjillings@1318 307 // MANDATORY FUNCTION
nickjillings@1318 308
nickjillings@1318 309 // Auto-align
nickjillings@1318 310 var numObj = audioEngineContext.audioObjects.length;
nickjillings@1318 311 var totalWidth = (numObj-1)*150+100;
nickjillings@1318 312 var diff = (window.innerWidth - totalWidth)/2;
nickjillings@1318 313 document.getElementById('slider').style.height = window.innerHeight - 180 + 'px';
nickjillings@1318 314 if (diff <= 0){diff = 0;}
nickjillings@1318 315 document.getElementById('slider-holder').style.marginLeft = diff + 'px';
nickjillings@1318 316 for (var i in audioEngineContext.audioObjects)
nickjillings@1318 317 {
nickjillings@1318 318 audioEngineContext.audioObjects[i].interfaceDOM.resize(event);
nickjillings@1318 319 }
nickjillings@1318 320 }
nickjillings@1318 321
nickjillings@1318 322
nickjillings@1318 323 function buttonSubmitClick() // TODO: Only when all songs have been played!
nickjillings@1318 324 {
nickjillings@1318 325 var checks = testState.currentStateMap[testState.currentIndex].interfaces[0].options;
nickjillings@1318 326 var canContinue = true;
nickjillings@1318 327
nickjillings@1318 328 // Check that the anchor and reference objects are correctly placed
nickjillings@1318 329 if (interfaceContext.checkHiddenAnchor() == false) {return;}
nickjillings@1318 330 if (interfaceContext.checkHiddenReference() == false) {return;}
nickjillings@1318 331
nickjillings@1318 332 for (var i=0; i<checks.length; i++) {
nickjillings@1318 333 if (checks[i].type == 'check')
nickjillings@1318 334 {
nickjillings@1318 335 switch(checks[i].check) {
nickjillings@1318 336 case 'fragmentPlayed':
nickjillings@1318 337 // Check if all fragments have been played
nickjillings@1318 338 var checkState = interfaceContext.checkAllPlayed();
nickjillings@1318 339 if (checkState == false) {canContinue = false;}
nickjillings@1318 340 break;
nickjillings@1318 341 case 'fragmentFullPlayback':
nickjillings@1318 342 // Check all fragments have been played to their full length
nickjillings@1318 343 var checkState = interfaceContext.checkAllPlayed();
nickjillings@1318 344 if (checkState == false) {canContinue = false;}
nickjillings@1318 345 console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
nickjillings@1318 346 break;
nickjillings@1318 347 case 'fragmentMoved':
nickjillings@1318 348 // Check all fragment sliders have been moved.
nickjillings@1318 349 var checkState = interfaceContext.checkAllMoved();
nickjillings@1318 350 if (checkState == false) {canContinue = false;}
nickjillings@1318 351 break;
nickjillings@1318 352 case 'fragmentComments':
nickjillings@1318 353 // Check all fragment sliders have been moved.
nickjillings@1318 354 var checkState = interfaceContext.checkAllCommented();
nickjillings@1318 355 if (checkState == false) {canContinue = false;}
nickjillings@1318 356 break;
nickjillings@1318 357 //case 'scalerange':
nickjillings@1318 358 // Check the scale is used to its full width outlined by the node
nickjillings@1318 359 //var checkState = interfaceContext.checkScaleRange();
nickjillings@1318 360 //if (checkState == false) {canContinue = false;}
nickjillings@1318 361 // break;
nickjillings@1318 362 default:
nickjillings@1318 363 console.log("WARNING - Check option "+checks[i].check+" is not supported on this interface");
nickjillings@1318 364 break;
nickjillings@1318 365 }
nickjillings@1318 366
nickjillings@1318 367 }
nickjillings@1318 368 if (!canContinue) {break;}
nickjillings@1318 369 }
nickjillings@1318 370
nickjillings@1318 371 if (canContinue) {
nickjillings@1318 372 if (audioEngineContext.status == 1) {
nickjillings@1318 373 var playback = document.getElementById('playback-button');
nickjillings@1318 374 playback.click();
nickjillings@1318 375 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
nickjillings@1318 376 } else
nickjillings@1318 377 {
nickjillings@1318 378 if (audioEngineContext.timer.testStarted == false)
nickjillings@1318 379 {
nickjillings@1318 380 alert('You have not started the test! Please press start to begin the test!');
nickjillings@1318 381 return;
nickjillings@1318 382 }
nickjillings@1318 383 }
nickjillings@1318 384 testState.advanceState();
nickjillings@1318 385 }
nickjillings@1318 386 }
nickjillings@1318 387
nickjillings@1318 388 function pageXMLSave(store, testXML)
nickjillings@1318 389 {
nickjillings@1318 390 // MANDATORY
nickjillings@1318 391 // Saves a specific test page
nickjillings@1318 392 // You can use this space to add any extra nodes to your XML saves
nickjillings@1318 393 }