annotate mushra.js @ 1965:c6b94c7dcade

Paper: Now with images - minor edits
author Brecht De Man <b.deman@qmul.ac.uk>
date Thu, 15 Oct 2015 20:46:17 +0100
parents 82f43919f385
children 81246d594793
rev   line source
b@1478 1 /**
b@1478 2 * mushra.js
b@1478 3 * Create the MUSHRA interface
b@1478 4 */
b@1478 5
b@1478 6 // Once this is loaded and parsed, begin execution
b@1478 7 loadInterface();
b@1478 8
b@1478 9 function loadInterface() {
b@1478 10 // Get the dimensions of the screen available to the page
b@1478 11 var width = window.innerWidth;
b@1478 12 var height = window.innerHeight;
b@1478 13
b@1478 14 // The injection point into the HTML page
b@1478 15 interfaceContext.insertPoint = document.getElementById("topLevelBody");
b@1478 16 var testContent = document.createElement('div');
b@1478 17 testContent.id = 'testContent';
b@1478 18
b@1478 19 // Create the top div for the Title element
b@1478 20 var titleAttr = specification.title;
b@1478 21 var title = document.createElement('div');
b@1478 22 title.className = "title";
b@1478 23 title.align = "center";
b@1478 24 var titleSpan = document.createElement('span');
b@1478 25
b@1478 26 // Set title to that defined in XML, else set to default
b@1478 27 if (titleAttr != undefined) {
b@1478 28 titleSpan.textContent = titleAttr;
b@1478 29 } else {
b@1478 30 titleSpan.textContent = 'Listening test';
b@1478 31 }
b@1478 32 // Insert the titleSpan element into the title div element.
b@1478 33 title.appendChild(titleSpan);
b@1478 34
b@1478 35 var pagetitle = document.createElement('div');
b@1478 36 pagetitle.className = "pageTitle";
b@1478 37 pagetitle.align = "center";
b@1478 38 var titleSpan = document.createElement('span');
b@1478 39 titleSpan.id = "pageTitle";
b@1478 40 pagetitle.appendChild(titleSpan);
b@1478 41
b@1478 42 // Create Interface buttons!
b@1478 43 var interfaceButtons = document.createElement('div');
b@1478 44 interfaceButtons.id = 'interface-buttons';
b@1478 45
b@1478 46 // Create playback start/stop points
b@1478 47 var playback = document.createElement("button");
b@1478 48 playback.innerHTML = 'Stop';
b@1478 49 playback.id = 'playback-button';
b@1478 50 // onclick function. Check if it is playing or not, call the correct function in the
b@1478 51 // audioEngine, change the button text to reflect the next state.
b@1478 52 playback.onclick = function() {
b@1478 53 if (audioEngineContext.status == 1) {
b@1478 54 audioEngineContext.stop();
b@1478 55 this.innerHTML = 'Stop';
b@1478 56 var time = audioEngineContext.timer.getTestTime();
b@1478 57 console.log('Stopped at ' + time); // DEBUG/SAFETY
b@1478 58 }
b@1478 59 };
b@1478 60 // Create Submit (save) button
b@1478 61 var submit = document.createElement("button");
b@1478 62 submit.innerHTML = 'Submit';
b@1478 63 submit.onclick = buttonSubmitClick;
b@1478 64 submit.id = 'submit-button';
b@1478 65 // Append the interface buttons into the interfaceButtons object.
b@1478 66 interfaceButtons.appendChild(playback);
b@1478 67 interfaceButtons.appendChild(submit);
b@1478 68
b@1478 69 // Create a slider box
b@1478 70 var sliderBox = document.createElement('div');
b@1478 71 sliderBox.style.width = "100%";
b@1478 72 sliderBox.style.height = window.innerHeight - 180 + 'px';
b@1478 73 sliderBox.id = 'slider';
b@1478 74 sliderBox.align = "center";
b@1478 75
b@1478 76 // Global parent for the comment boxes on the page
b@1478 77 var feedbackHolder = document.createElement('div');
b@1478 78 feedbackHolder.id = 'feedbackHolder';
b@1478 79
b@1478 80 testContent.style.zIndex = 1;
b@1478 81 interfaceContext.insertPoint.innerHTML = null; // Clear the current schema
b@1478 82
b@1478 83 // Inject into HTML
b@1478 84 testContent.appendChild(title); // Insert the title
b@1478 85 testContent.appendChild(pagetitle);
b@1478 86 testContent.appendChild(interfaceButtons);
b@1478 87 testContent.appendChild(sliderBox);
b@1478 88 testContent.appendChild(feedbackHolder);
b@1478 89 interfaceContext.insertPoint.appendChild(testContent);
b@1478 90
b@1478 91 // Load the full interface
b@1478 92 testState.initialise();
b@1478 93 testState.advanceState();
b@1478 94 }
b@1478 95
b@1478 96 function loadTest(audioHolderObject)
b@1478 97 {
b@1478 98 // Reset audioEngineContext.Metric globals for new test
b@1478 99 audioEngineContext.newTestPage();
b@1478 100
b@1478 101 // Delete any previous audioObjects associated with the audioEngine
b@1478 102 audioEngineContext.audioObjects = [];
b@1478 103 interfaceContext.deleteCommentBoxes();
b@1478 104 interfaceContext.deleteCommentQuestions();
b@1478 105
b@1478 106 var id = audioHolderObject.id;
b@1478 107
b@1478 108 var feedbackHolder = document.getElementById('feedbackHolder');
b@1478 109 var interfaceObj = audioHolderObject.interfaces;
b@1478 110
b@1478 111 var sliderBox = document.getElementById('slider');
b@1478 112 feedbackHolder.innerHTML = null;
b@1478 113 sliderBox.innerHTML = null;
b@1478 114
b@1478 115 var commentBoxPrefix = "Comment on track";
b@1478 116 if (interfaceObj.commentBoxPrefix != undefined) {
b@1478 117 commentBoxPrefix = interfaceObj.commentBoxPrefix;
b@1478 118 }
b@1478 119
b@1478 120 /// CHECK FOR SAMPLE RATE COMPATIBILITY
b@1478 121 if (audioHolderObject.sampleRate != undefined) {
b@1478 122 if (Number(audioHolderObject.sampleRate) != audioContext.sampleRate) {
b@1478 123 var errStr = 'Sample rates do not match! Requested '+Number(audioHolderObject.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
b@1478 124 alert(errStr);
b@1478 125 return;
b@1478 126 }
b@1478 127 }
b@1478 128
b@1478 129 var loopPlayback = audioHolderObject.loop;
b@1478 130
b@1478 131 audioEngineContext.loopPlayback = loopPlayback;
b@1478 132
b@1478 133 currentTestHolder = document.createElement('audioHolder');
b@1478 134 currentTestHolder.id = audioHolderObject.id;
b@1478 135 currentTestHolder.repeatCount = audioHolderObject.repeatCount;
b@1478 136
b@1478 137 $(audioHolderObject.commentQuestions).each(function(index,element) {
b@1478 138 var node = interfaceContext.createCommentQuestion(element);
b@1478 139 feedbackHolder.appendChild(node.holder);
b@1478 140 });
b@1478 141
b@1478 142 // Find all the audioElements from the audioHolder
b@1478 143 $(audioHolderObject.audioElements).each(function(index,element){
b@1478 144 // Find URL of track
b@1478 145 // In this jQuery loop, variable 'this' holds the current audioElement.
b@1478 146
b@1478 147 // Now load each audio sample. First create the new track by passing the full URL
b@1478 148 var trackURL = audioHolderObject.hostURL + element.url;
b@1478 149 var audioObject = audioEngineContext.newTrack(element);
b@1478 150
b@1478 151 var node = interfaceContext.createCommentBox(audioObject);
b@1478 152
b@1478 153 // Create a slider per track
b@1478 154 audioObject.interfaceDOM = new sliderObject(audioObject);
b@1478 155
b@1478 156 // Distribute it randomnly
b@1478 157 audioObject.interfaceDOM.slider.value = Math.random();
b@1478 158
b@1478 159 sliderBox.appendChild(audioObject.interfaceDOM.holder);
b@1478 160 audioObject.metric.initialised(audioObject.interfaceDOM.slider.value);
b@1478 161
b@1478 162 });
b@1478 163
b@1478 164 // Auto-align
b@1478 165 var numObj = audioHolderObject.audioElements.length;
b@1478 166 var totalWidth = (numObj-1)*150+100;
b@1478 167 var diff = (window.innerWidth - totalWidth)/2;
b@1478 168 audioEngineContext.audioObjects[0].interfaceDOM.holder.style.marginLeft = diff + 'px';
b@1478 169 }
b@1478 170
b@1478 171 function sliderObject(audioObject)
b@1478 172 {
b@1478 173 // Constructs the slider object. We use the HTML5 slider object
b@1478 174 this.parent = audioObject;
b@1478 175 this.holder = document.createElement('div');
b@1478 176 this.title = document.createElement('span');
b@1478 177 this.slider = document.createElement('input');
b@1478 178 this.play = document.createElement('button');
b@1478 179
b@1478 180 this.holder.className = 'track-slider';
b@1478 181 this.holder.style.height = window.innerHeight-200 + 'px';
b@1478 182 this.holder.appendChild(this.title);
b@1478 183 this.holder.appendChild(this.slider);
b@1478 184 this.holder.appendChild(this.play);
b@1478 185 this.holder.align = "center";
b@1478 186 this.holder.style.marginLeft = "50px";
b@1478 187 this.holder.setAttribute('trackIndex',audioObject.id);
b@1478 188
b@1478 189 this.title.textContent = audioObject.id;
b@1478 190 this.title.style.width = "100%";
b@1478 191 this.title.style.float = "left";
b@1478 192
b@1478 193 this.slider.type = "range";
b@1478 194 this.slider.min = "0";
b@1478 195 this.slider.max = "1";
b@1478 196 this.slider.step = "0.01";
b@1478 197 this.slider.setAttribute('orient','vertical');
b@1478 198 this.slider.style.float = "left";
b@1478 199 this.slider.style.width = "100%";
b@1478 200 this.slider.style.height = window.innerHeight-250 + 'px';
b@1478 201 this.slider.onchange = function()
b@1478 202 {
b@1478 203 var time = audioEngineContext.timer.getTestTime();
b@1478 204 var id = Number(this.parentNode.getAttribute('trackIndex'));
b@1478 205 audioEngineContext.audioObjects[id].metric.moved(time,this.value);
b@1478 206 console.log('slider '+id+' moved to '+this.value+' ('+time+')');
b@1478 207 };
b@1478 208
b@1478 209 this.play.textContent = "Play";
b@1478 210 this.play.value = audioObject.id;
b@1478 211 this.play.style.float = "left";
b@1478 212 this.play.style.width = "100%";
b@1478 213 this.play.onclick = function()
b@1478 214 {
b@1478 215 audioEngineContext.play();
b@1478 216 if (audioEngineContext.audioObjectsReady) {
b@1478 217 var id = Number(event.srcElement.value);
b@1478 218 //audioEngineContext.metric.sliderPlayed(id);
b@1478 219 audioEngineContext.play(id);
b@1478 220 }
b@1478 221 };
b@1478 222
b@1478 223 this.enable = function() {
b@1478 224 if (this.parent.state == 1)
b@1478 225 {
b@1478 226 $(this.slider).removeClass('track-slider-disabled');
b@1478 227 }
b@1478 228 };
b@1478 229
b@1478 230 this.exportXMLDOM = function(audioObject) {
b@1478 231 // Called by the audioObject holding this element. Must be present
b@1478 232 var node = document.createElement('value');
b@1478 233 node.textContent = this.slider.value;
b@1478 234 return node;
b@1478 235 };
b@1478 236 this.getValue = function() {
b@1478 237 return this.slider.value;
b@1478 238 };
b@1478 239 }
b@1478 240
b@1478 241
b@1478 242 function buttonSubmitClick() // TODO: Only when all songs have been played!
b@1478 243 {
b@1478 244 var checks = testState.currentStateMap[testState.currentIndex].interfaces[0].options;
b@1478 245 var canContinue = true;
b@1478 246
b@1478 247 // Check that the anchor and reference objects are correctly placed
b@1478 248 if (interfaceContext.checkHiddenAnchor() == false) {return;}
b@1478 249 if (interfaceContext.checkHiddenReference() == false) {return;}
b@1478 250 /*
b@1478 251 for (var i=0; i<checks.length; i++) {
b@1478 252 if (checks[i].type == 'check')
b@1478 253 {
b@1478 254 switch(checks[i].check) {
b@1478 255 case 'fragmentPlayed':
b@1478 256 // Check if all fragments have been played
b@1478 257 var checkState = interfaceContext.checkAllPlayed();
b@1478 258 if (checkState == false) {canContinue = false;}
b@1478 259 break;
b@1478 260 case 'fragmentFullPlayback':
b@1478 261 // Check all fragments have been played to their full length
b@1478 262 var checkState = interfaceContext.checkAllPlayed();
b@1478 263 if (checkState == false) {canContinue = false;}
b@1478 264 console.log('NOTE: fragmentFullPlayback not currently implemented, performing check fragmentPlayed instead');
b@1478 265 break;
b@1478 266 case 'fragmentMoved':
b@1478 267 // Check all fragment sliders have been moved.
b@1478 268 var checkState = interfaceContext.checkAllMoved();
b@1478 269 if (checkState == false) {canContinue = false;}
b@1478 270 break;
b@1478 271 case 'fragmentComments':
b@1478 272 // Check all fragment sliders have been moved.
b@1478 273 var checkState = interfaceContext.checkAllCommented();
b@1478 274 if (checkState == false) {canContinue = false;}
b@1478 275 break;
b@1478 276 case 'scalerange':
b@1478 277 // Check the scale is used to its full width outlined by the node
b@1478 278 var checkState = interfaceContext.checkScaleRange();
b@1478 279 if (checkState == false) {canContinue = false;}
b@1478 280 break;
b@1478 281 }
b@1478 282
b@1478 283 }
b@1478 284 if (!canContinue) {break;}
b@1478 285 }
b@1478 286 */
b@1478 287 if (canContinue) {
b@1478 288 if (audioEngineContext.status == 1) {
b@1478 289 var playback = document.getElementById('playback-button');
b@1478 290 playback.click();
b@1478 291 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options
b@1478 292 } else
b@1478 293 {
b@1478 294 if (audioEngineContext.timer.testStarted == false)
b@1478 295 {
b@1478 296 alert('You have not started the test! Please press start to begin the test!');
b@1478 297 return;
b@1478 298 }
b@1478 299 }
b@1478 300 testState.advanceState();
b@1478 301 }
b@1478 302 }
b@1478 303
b@1478 304 function pageXMLSave(store, testXML)
b@1478 305 {
b@1478 306 // Saves a specific test page
b@1478 307 var xmlDoc = store;
b@1478 308 // Check if any session wide metrics are enabled
b@1478 309
b@1478 310 var commentShow = testXML.elementComments;
b@1478 311
b@1478 312 var metric = document.createElement('metric');
b@1478 313 if (audioEngineContext.metric.enableTestTimer)
b@1478 314 {
b@1478 315 var testTime = document.createElement('metricResult');
b@1478 316 testTime.id = 'testTime';
b@1478 317 testTime.textContent = audioEngineContext.timer.testDuration;
b@1478 318 metric.appendChild(testTime);
b@1478 319 }
b@1478 320 xmlDoc.appendChild(metric);
b@1478 321 var audioObjects = audioEngineContext.audioObjects;
b@1478 322 for (var i=0; i<audioObjects.length; i++)
b@1478 323 {
b@1478 324 var audioElement = audioEngineContext.audioObjects[i].exportXMLDOM();
b@1478 325 audioElement.setAttribute('presentedId',i);
b@1478 326 xmlDoc.appendChild(audioElement);
b@1478 327 }
b@1478 328
b@1478 329 $(interfaceContext.commentQuestions).each(function(index,element){
b@1478 330 var node = element.exportXMLDOM();
b@1478 331 xmlDoc.appendChild(node);
b@1478 332 });
b@1478 333 store = xmlDoc;
b@1478 334 }