annotate mushra.js @ 1483:d99c580e2683

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