Mercurial > hg > webaudioevaluationtool
comparison ape.js @ 1563:4730207bcae0
Merge from the default branch
author | Nicholas Jillings <nickjillings@users.noreply.github.com> |
---|---|
date | Wed, 10 Jun 2015 14:39:15 +0100 |
parents | |
children | 1af8a548cab2 4e9ab4f92f20 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 1563:4730207bcae0 |
---|---|
1 /** | |
2 * ape.js | |
3 * Create the APE interface | |
4 */ | |
5 | |
6 // preTest - In preTest state | |
7 // testRun-ID - In test running, test Id number at the end 'testRun-2' | |
8 // testRunPost-ID - Post test of test ID | |
9 // testRunPre-ID - Pre-test of test ID | |
10 // postTest - End of test, final submission! | |
11 | |
12 | |
13 // Once this is loaded and parsed, begin execution | |
14 loadInterface(); | |
15 | |
16 function loadInterface() { | |
17 | |
18 // Get the dimensions of the screen available to the page | |
19 var width = window.innerWidth; | |
20 var height = window.innerHeight; | |
21 | |
22 // The injection point into the HTML page | |
23 interfaceContext.insertPoint = document.getElementById("topLevelBody"); | |
24 var testContent = document.createElement('div'); | |
25 | |
26 testContent.id = 'testContent'; | |
27 | |
28 | |
29 // Create APE specific metric functions | |
30 audioEngineContext.metric.initialiseTest = function() | |
31 { | |
32 }; | |
33 | |
34 audioEngineContext.metric.sliderMoved = function() | |
35 { | |
36 | |
37 var id = this.data; | |
38 this.data = -1; | |
39 var position = convSliderPosToRate(id); | |
40 console.log('slider ' + id + ': '+ position + ' (' + time + ')'); // DEBUG/SAFETY: show position and slider id | |
41 if (audioEngineContext.timer.testStarted) | |
42 { | |
43 audioEngineContext.audioObjects[id].metric.moved(time,position); | |
44 } | |
45 }; | |
46 | |
47 audioEngineContext.metric.sliderPlayed = function(id) | |
48 { | |
49 var time = audioEngineContext.timer.getTestTime(); | |
50 if (audioEngineContext.timer.testStarted) | |
51 { | |
52 if (this.lastClicked >= 0) | |
53 { | |
54 audioEngineContext.audioObjects[this.lastClicked].metric.listening(time); | |
55 } | |
56 this.lastClicked = id; | |
57 audioEngineContext.audioObjects[id].metric.listening(time); | |
58 } | |
59 console.log('slider ' + id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id | |
60 }; | |
61 | |
62 // Bindings for audioObjects | |
63 | |
64 // Create the top div for the Title element | |
65 var titleAttr = specification.title; | |
66 var title = document.createElement('div'); | |
67 title.className = "title"; | |
68 title.align = "center"; | |
69 var titleSpan = document.createElement('span'); | |
70 | |
71 // Set title to that defined in XML, else set to default | |
72 if (titleAttr != undefined) { | |
73 titleSpan.textContent = titleAttr; | |
74 } else { | |
75 titleSpan.textContent = 'Listening test'; | |
76 } | |
77 // Insert the titleSpan element into the title div element. | |
78 title.appendChild(titleSpan); | |
79 | |
80 var pagetitle = document.createElement('div'); | |
81 pagetitle.className = "pageTitle"; | |
82 pagetitle.align = "center"; | |
83 var titleSpan = document.createElement('span'); | |
84 titleSpan.id = "pageTitle"; | |
85 pagetitle.appendChild(titleSpan); | |
86 | |
87 // Create Interface buttons! | |
88 var interfaceButtons = document.createElement('div'); | |
89 interfaceButtons.id = 'interface-buttons'; | |
90 | |
91 // Create playback start/stop points | |
92 var playback = document.createElement("button"); | |
93 playback.innerHTML = 'Stop'; | |
94 playback.id = 'playback-button'; | |
95 // onclick function. Check if it is playing or not, call the correct function in the | |
96 // audioEngine, change the button text to reflect the next state. | |
97 playback.onclick = function() { | |
98 if (audioEngineContext.status == 1) { | |
99 audioEngineContext.stop(); | |
100 this.innerHTML = 'Stop'; | |
101 var time = audioEngineContext.timer.getTestTime(); | |
102 console.log('Stopped at ' + time); // DEBUG/SAFETY | |
103 } | |
104 }; | |
105 // Create Submit (save) button | |
106 var submit = document.createElement("button"); | |
107 submit.innerHTML = 'Submit'; | |
108 submit.onclick = buttonSubmitClick; | |
109 submit.id = 'submit-button'; | |
110 // Append the interface buttons into the interfaceButtons object. | |
111 interfaceButtons.appendChild(playback); | |
112 interfaceButtons.appendChild(submit); | |
113 | |
114 // Now create the slider and HTML5 canvas boxes | |
115 | |
116 // Create the div box to center align | |
117 var sliderBox = document.createElement('div'); | |
118 sliderBox.className = 'sliderCanvasDiv'; | |
119 sliderBox.id = 'sliderCanvasHolder'; | |
120 | |
121 // Create the slider box to hold the slider elements | |
122 var canvas = document.createElement('div'); | |
123 canvas.id = 'slider'; | |
124 canvas.align = "left"; | |
125 canvas.addEventListener('dragover',function(event){ | |
126 event.preventDefault(); | |
127 return false; | |
128 },false); | |
129 var sliderMargin = document.createAttribute('marginsize'); | |
130 sliderMargin.nodeValue = 42; // Set default margins to 42px either side | |
131 // Must have a known EXACT width, as this is used later to determine the ratings | |
132 var w = (Number(sliderMargin.nodeValue)+8)*2; | |
133 canvas.style.width = width - w +"px"; | |
134 canvas.style.marginLeft = sliderMargin.nodeValue +'px'; | |
135 canvas.setAttributeNode(sliderMargin); | |
136 sliderBox.appendChild(canvas); | |
137 | |
138 // Create the div to hold any scale objects | |
139 var scale = document.createElement('div'); | |
140 scale.className = 'sliderScale'; | |
141 scale.id = 'sliderScaleHolder'; | |
142 scale.align = 'left'; | |
143 sliderBox.appendChild(scale); | |
144 | |
145 // Global parent for the comment boxes on the page | |
146 var feedbackHolder = document.createElement('div'); | |
147 feedbackHolder.id = 'feedbackHolder'; | |
148 | |
149 testContent.style.zIndex = 1; | |
150 interfaceContext.insertPoint.innerHTML = null; // Clear the current schema | |
151 | |
152 // Inject into HTML | |
153 testContent.appendChild(title); // Insert the title | |
154 testContent.appendChild(pagetitle); | |
155 testContent.appendChild(interfaceButtons); | |
156 testContent.appendChild(sliderBox); | |
157 testContent.appendChild(feedbackHolder); | |
158 interfaceContext.insertPoint.appendChild(testContent); | |
159 | |
160 // Load the full interface | |
161 testState.initialise(); | |
162 testState.advanceState(); | |
163 | |
164 } | |
165 | |
166 function loadTest(audioHolderObject) | |
167 { | |
168 | |
169 // Reset audioEngineContext.Metric globals for new test | |
170 audioEngineContext.newTestPage(); | |
171 | |
172 var id = audioHolderObject.id; | |
173 | |
174 var feedbackHolder = document.getElementById('feedbackHolder'); | |
175 var canvas = document.getElementById('slider'); | |
176 feedbackHolder.innerHTML = null; | |
177 canvas.innerHTML = null; | |
178 | |
179 // Setup question title | |
180 var interfaceObj = audioHolderObject.interfaces; | |
181 var commentBoxPrefix = "Comment on track"; | |
182 if (interfaceObj.length != 0) { | |
183 interfaceObj = interfaceObj[0]; | |
184 var titleNode = interfaceObj.title; | |
185 if (titleNode != undefined) | |
186 { | |
187 document.getElementById('pageTitle').textContent = titleNode; | |
188 } | |
189 var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2); | |
190 var offset = Number(document.getElementById('slider').attributes['marginsize'].value); | |
191 var scale = document.getElementById('sliderScaleHolder'); | |
192 scale.innerHTML = null; | |
193 $(interfaceObj.scale).each(function(index,scaleObj){ | |
194 var value = document.createAttribute('value'); | |
195 var position = Number(scaleObj[0])*0.01; | |
196 value.nodeValue = position; | |
197 var pixelPosition = (position*positionScale)+offset; | |
198 var scaleDOM = document.createElement('span'); | |
199 scaleDOM.textContent = scaleObj[1]; | |
200 scale.appendChild(scaleDOM); | |
201 scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px'; | |
202 scaleDOM.setAttributeNode(value); | |
203 }); | |
204 | |
205 if (interfaceObj.commentBoxPrefix != undefined) { | |
206 commentBoxPrefix = interfaceObj.commentBoxPrefix; | |
207 } | |
208 } | |
209 | |
210 /// CHECK FOR SAMPLE RATE COMPATIBILITY | |
211 if (audioHolderObject.sampleRate != undefined) { | |
212 if (Number(audioHolderObject.sampleRate) != audioContext.sampleRate) { | |
213 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.'; | |
214 alert(errStr); | |
215 return; | |
216 } | |
217 } | |
218 | |
219 var commentShow = audioHolderObject.elementComments; | |
220 | |
221 var loopPlayback = audioHolderObject.loop; | |
222 | |
223 audioEngineContext.loopPlayback = loopPlayback; | |
224 // Create AudioEngine bindings for playback | |
225 if (loopPlayback) { | |
226 audioEngineContext.selectedTrack = function(id) { | |
227 for (var i=0; i<this.audioObjects.length; i++) | |
228 { | |
229 if (id == i) { | |
230 this.audioObjects[i].loopStart(); | |
231 } else { | |
232 this.audioObjects[i].loopStop(); | |
233 } | |
234 } | |
235 }; | |
236 } else { | |
237 audioEngineContext.selectedTrack = function(id) { | |
238 for (var i=0; i<this.audioObjects.length; i++) | |
239 { | |
240 this.audioObjects[i].outputGain.gain.value = 0.0; | |
241 this.audioObjects[i].stop(); | |
242 } | |
243 if (this.status == 1) { | |
244 this.audioObjects[id].outputGain.gain.value = 1.0; | |
245 this.audioObjects[id].play(audioContext.currentTime+0.01); | |
246 } | |
247 }; | |
248 } | |
249 | |
250 currentTestHolder = document.createElement('audioHolder'); | |
251 currentTestHolder.id = audioHolderObject.id; | |
252 currentTestHolder.repeatCount = audioHolderObject.repeatCount; | |
253 | |
254 var randomise = audioHolderObject.randomiseOrder; | |
255 | |
256 var audioElements = audioHolderObject.audioElements; | |
257 currentTrackOrder = []; | |
258 if (randomise) { | |
259 audioHolderObject.audioElements = randomiseOrder(audioHolderObject.audioElements); | |
260 } | |
261 | |
262 // Delete any previous audioObjects associated with the audioEngine | |
263 audioEngineContext.audioObjects = []; | |
264 | |
265 // Find all the audioElements from the audioHolder | |
266 $(audioHolderObject.audioElements).each(function(index,element){ | |
267 // Find URL of track | |
268 // In this jQuery loop, variable 'this' holds the current audioElement. | |
269 | |
270 // Now load each audio sample. First create the new track by passing the full URL | |
271 var trackURL = audioHolderObject.hostURL + element.url; | |
272 var audioObject = audioEngineContext.newTrack(element); | |
273 | |
274 if (commentShow) { | |
275 var node = interfaceContext.createCommentBox(audioObject); | |
276 } | |
277 | |
278 // Create a slider per track | |
279 audioObject.interfaceDOM = new sliderObject(audioObject); | |
280 | |
281 // Distribute it randomnly | |
282 var w = window.innerWidth - (offset+8)*2; | |
283 w = Math.random()*w; | |
284 w = Math.floor(w+(offset+8)); | |
285 audioObject.interfaceDOM.trackSliderObj.style.left = w+'px'; | |
286 | |
287 canvas.appendChild(audioObject.interfaceDOM.trackSliderObj); | |
288 audioObject.metric.initialised(convSliderPosToRate(audioObject.interfaceDOM.trackSliderObj)); | |
289 | |
290 }); | |
291 if (commentShow) { | |
292 interfaceContext.showCommentBoxes(feedbackHolder,true); | |
293 } | |
294 | |
295 $(audioHolderObject.commentQuestions).each(function(index,element) { | |
296 var node = interfaceContext.createCommentQuestion(element); | |
297 feedbackHolder.appendChild(node.holder); | |
298 }); | |
299 | |
300 | |
301 testWaitIndicator(); | |
302 } | |
303 | |
304 function sliderObject(audioObject) { | |
305 // Create a new slider object; | |
306 this.parent = audioObject; | |
307 this.trackSliderObj = document.createElement('div'); | |
308 this.trackSliderObj.className = 'track-slider'; | |
309 this.trackSliderObj.id = 'track-slider-'+audioObject.id; | |
310 | |
311 this.trackSliderObj.setAttribute('trackIndex',audioObject.id); | |
312 this.trackSliderObj.innerHTML = '<span>'+audioObject.id+'</span>'; | |
313 this.trackSliderObj.draggable = true; | |
314 this.trackSliderObj.ondragend = dragEnd; | |
315 | |
316 // Onclick, switch playback to that track | |
317 this.trackSliderObj.onclick = function() { | |
318 // Start the test on first click, that way timings are more accurate. | |
319 audioEngineContext.play(); | |
320 if (audioEngineContext.audioObjectsReady) { | |
321 // Cannot continue to issue play command until audioObjects reported as ready! | |
322 // Get the track ID from the object ID | |
323 var id = Number(event.srcElement.attributes['trackIndex'].value); | |
324 //audioEngineContext.metric.sliderPlayed(id); | |
325 audioEngineContext.selectedTrack(id); | |
326 // Currently playing track red, rest green | |
327 | |
328 //document.getElementById('track-slider-'+index).style.backgroundColor = "#FF0000"; | |
329 $('.track-slider').removeClass('track-slider-playing'); | |
330 $(event.srcElement).addClass('track-slider-playing'); | |
331 $('.comment-div').removeClass('comment-box-playing'); | |
332 $('#comment-div-'+id).addClass('comment-box-playing'); | |
333 } | |
334 }; | |
335 | |
336 this.exportXMLDOM = function() { | |
337 // Called by the audioObject holding this element. Must be present | |
338 var node = document.createElement('value'); | |
339 node.textContent = convSliderPosToRate(this.trackSliderObj); | |
340 return node; | |
341 }; | |
342 } | |
343 | |
344 function dragEnd(ev) { | |
345 // Function call when a div has been dropped | |
346 var slider = document.getElementById('slider'); | |
347 var marginSize = Number(slider.attributes['marginsize'].value); | |
348 var w = slider.style.width; | |
349 w = Number(w.substr(0,w.length-2)); | |
350 var x = ev.x; | |
351 if (x >= marginSize && x < w+marginSize) { | |
352 this.style.left = (x)+'px'; | |
353 } else { | |
354 if (x<marginSize) { | |
355 this.style.left = marginSize+'px'; | |
356 } else { | |
357 this.style.left = (w+marginSize) + 'px'; | |
358 } | |
359 } | |
360 var time = audioEngineContext.timer.getTestTime(); | |
361 var id = Number(ev.srcElement.getAttribute('trackindex')); | |
362 audioEngineContext.audioObjects[id].metric.moved(time,convSliderPosToRate(ev.srcElement)); | |
363 } | |
364 | |
365 function buttonSubmitClick() // TODO: Only when all songs have been played! | |
366 { | |
367 hasBeenPlayed = audioEngineContext.checkAllPlayed(); | |
368 if (hasBeenPlayed.length == 0) { | |
369 if (audioEngineContext.status == 1) { | |
370 var playback = document.getElementById('playback-button'); | |
371 playback.click(); | |
372 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options | |
373 } else | |
374 { | |
375 if (audioEngineContext.timer.testStarted == false) | |
376 { | |
377 alert('You have not started the test! Please press start to begin the test!'); | |
378 return; | |
379 } | |
380 } | |
381 testState.advanceState(); | |
382 } else // if a fragment has not been played yet | |
383 { | |
384 str = ""; | |
385 if (hasBeenPlayed.length > 1) { | |
386 for (var i=0; i<hasBeenPlayed.length; i++) { | |
387 str = str + hasBeenPlayed[i]; | |
388 if (i < hasBeenPlayed.length-2){ | |
389 str += ", "; | |
390 } else if (i == hasBeenPlayed.length-2) { | |
391 str += " or "; | |
392 } | |
393 } | |
394 alert('You have not played fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.'); | |
395 } else { | |
396 alert('You have not played fragment ' + hasBeenPlayed[0] + ' yet. Please listen, rate and comment all samples before submitting.'); | |
397 } | |
398 return; | |
399 } | |
400 } | |
401 | |
402 function convSliderPosToRate(slider) | |
403 { | |
404 var w = document.getElementById('slider').style.width; | |
405 var marginsize = Number(document.getElementById('slider').attributes['marginsize'].value); | |
406 var maxPix = w.substr(0,w.length-2); | |
407 var pix = slider.style.left; | |
408 pix = pix.substr(0,pix.length-2); | |
409 var rate = (pix-marginsize)/maxPix; | |
410 return rate; | |
411 } | |
412 | |
413 function resizeWindow(event){ | |
414 // Function called when the window has been resized. | |
415 // MANDATORY FUNCTION | |
416 | |
417 // Store the slider marker values | |
418 var holdValues = []; | |
419 $(".track-slider").each(function(index,sliderObj){ | |
420 holdValues.push(convSliderPosToRate(index)); | |
421 }); | |
422 | |
423 var width = event.target.innerWidth; | |
424 var canvas = document.getElementById('sliderCanvasHolder'); | |
425 var sliderDiv = canvas.children[0]; | |
426 var sliderScaleDiv = canvas.children[1]; | |
427 var marginsize = Number(sliderDiv.attributes['marginsize'].value); | |
428 var w = (marginsize+8)*2; | |
429 sliderDiv.style.width = width - w + 'px'; | |
430 var width = width - w; | |
431 // Move sliders into new position | |
432 $(".track-slider").each(function(index,sliderObj){ | |
433 var pos = holdValues[index]; | |
434 var pix = pos * width; | |
435 sliderObj.style.left = pix+marginsize+'px'; | |
436 }); | |
437 | |
438 // Move scale labels | |
439 $(sliderScaleDiv.children).each(function(index,scaleObj){ | |
440 var position = Number(scaleObj.attributes['value'].value); | |
441 var pixelPosition = (position*width)+marginsize; | |
442 scaleObj.style.left = Math.floor((pixelPosition-($(scaleObj).width()/2)))+'px'; | |
443 }); | |
444 } | |
445 | |
446 function pageXMLSave(store, testXML) | |
447 { | |
448 // Saves a specific test page | |
449 var xmlDoc = store; | |
450 // Check if any session wide metrics are enabled | |
451 | |
452 var commentShow = testXML.elementComments; | |
453 | |
454 var metric = document.createElement('metric'); | |
455 if (audioEngineContext.metric.enableTestTimer) | |
456 { | |
457 var testTime = document.createElement('metricResult'); | |
458 testTime.id = 'testTime'; | |
459 testTime.textContent = audioEngineContext.timer.testDuration; | |
460 metric.appendChild(testTime); | |
461 } | |
462 xmlDoc.appendChild(metric); | |
463 var audioObjects = audioEngineContext.audioObjects; | |
464 for (var i=0; i<audioObjects.length; i++) | |
465 { | |
466 var audioElement = audioEngineContext.audioObjects[i].exportXMLDOM(); | |
467 xmlDoc.appendChild(audioElement); | |
468 } | |
469 | |
470 $(interfaceContext.commentQuestions).each(function(index,element){ | |
471 var node = element.exportXMLDOM(); | |
472 xmlDoc.appendChild(node); | |
473 }); | |
474 store = xmlDoc; | |
475 } |