annotate mushra.js @ 1079:24eeac8994bf

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