annotate mushra.js @ 1460:1b81ab727352

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