Mercurial > hg > webaudioevaluationtool
comparison ape.js @ 1556:c6decd1db0da
Added common interface to specification to handle global interface-specific functions such as checks for playback.
author | Nicholas Jillings <nickjillings@users.noreply.github.com> |
---|---|
date | Tue, 16 Jun 2015 15:44:02 +0100 |
parents | |
children | 13e79bef8b01 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 1556:c6decd1db0da |
---|---|
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 var playbackHolder = document.createElement('div'); | |
180 playbackHolder.style.width = "100%"; | |
181 playbackHolder.align = 'center'; | |
182 playbackHolder.appendChild(interfaceContext.playhead.object); | |
183 feedbackHolder.appendChild(playbackHolder); | |
184 // Setup question title | |
185 var interfaceObj = audioHolderObject.interfaces; | |
186 var commentBoxPrefix = "Comment on track"; | |
187 if (interfaceObj.length != 0) { | |
188 interfaceObj = interfaceObj[0]; | |
189 var titleNode = interfaceObj.title; | |
190 if (titleNode != undefined) | |
191 { | |
192 document.getElementById('pageTitle').textContent = titleNode; | |
193 } | |
194 var positionScale = canvas.style.width.substr(0,canvas.style.width.length-2); | |
195 var offset = Number(document.getElementById('slider').attributes['marginsize'].value); | |
196 var scale = document.getElementById('sliderScaleHolder'); | |
197 scale.innerHTML = null; | |
198 $(interfaceObj.scale).each(function(index,scaleObj){ | |
199 var value = document.createAttribute('value'); | |
200 var position = Number(scaleObj[0])*0.01; | |
201 value.nodeValue = position; | |
202 var pixelPosition = (position*positionScale)+offset; | |
203 var scaleDOM = document.createElement('span'); | |
204 scaleDOM.textContent = scaleObj[1]; | |
205 scale.appendChild(scaleDOM); | |
206 scaleDOM.style.left = Math.floor((pixelPosition-($(scaleDOM).width()/2)))+'px'; | |
207 scaleDOM.setAttributeNode(value); | |
208 }); | |
209 | |
210 if (interfaceObj.commentBoxPrefix != undefined) { | |
211 commentBoxPrefix = interfaceObj.commentBoxPrefix; | |
212 } | |
213 } | |
214 | |
215 /// CHECK FOR SAMPLE RATE COMPATIBILITY | |
216 if (audioHolderObject.sampleRate != undefined) { | |
217 if (Number(audioHolderObject.sampleRate) != audioContext.sampleRate) { | |
218 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.'; | |
219 alert(errStr); | |
220 return; | |
221 } | |
222 } | |
223 | |
224 var commentShow = audioHolderObject.elementComments; | |
225 | |
226 var loopPlayback = audioHolderObject.loop; | |
227 | |
228 audioEngineContext.loopPlayback = loopPlayback; | |
229 // Create AudioEngine bindings for playback | |
230 audioEngineContext.selectedTrack = function(id) { | |
231 console.log('Deprecated'); | |
232 }; | |
233 | |
234 currentTestHolder = document.createElement('audioHolder'); | |
235 currentTestHolder.id = audioHolderObject.id; | |
236 currentTestHolder.repeatCount = audioHolderObject.repeatCount; | |
237 | |
238 var randomise = audioHolderObject.randomiseOrder; | |
239 | |
240 var audioElements = audioHolderObject.audioElements; | |
241 currentTrackOrder = []; | |
242 if (randomise) { | |
243 audioHolderObject.audioElements = randomiseOrder(audioHolderObject.audioElements); | |
244 } | |
245 | |
246 // Delete any previous audioObjects associated with the audioEngine | |
247 audioEngineContext.audioObjects = []; | |
248 interfaceContext.deleteCommentBoxes(); | |
249 | |
250 // Find all the audioElements from the audioHolder | |
251 $(audioHolderObject.audioElements).each(function(index,element){ | |
252 // Find URL of track | |
253 // In this jQuery loop, variable 'this' holds the current audioElement. | |
254 | |
255 // Now load each audio sample. First create the new track by passing the full URL | |
256 var trackURL = audioHolderObject.hostURL + element.url; | |
257 var audioObject = audioEngineContext.newTrack(element); | |
258 | |
259 var node = interfaceContext.createCommentBox(audioObject); | |
260 | |
261 // Create a slider per track | |
262 audioObject.interfaceDOM = new sliderObject(audioObject); | |
263 | |
264 // Distribute it randomnly | |
265 var w = window.innerWidth - (offset+8)*2; | |
266 w = Math.random()*w; | |
267 w = Math.floor(w+(offset+8)); | |
268 audioObject.interfaceDOM.trackSliderObj.style.left = w+'px'; | |
269 | |
270 canvas.appendChild(audioObject.interfaceDOM.trackSliderObj); | |
271 audioObject.metric.initialised(convSliderPosToRate(audioObject.interfaceDOM.trackSliderObj)); | |
272 | |
273 }); | |
274 if (commentShow) { | |
275 interfaceContext.showCommentBoxes(feedbackHolder,true); | |
276 } | |
277 | |
278 $(audioHolderObject.commentQuestions).each(function(index,element) { | |
279 var node = interfaceContext.createCommentQuestion(element); | |
280 feedbackHolder.appendChild(node.holder); | |
281 }); | |
282 | |
283 | |
284 testWaitIndicator(); | |
285 } | |
286 | |
287 function sliderObject(audioObject) { | |
288 // Create a new slider object; | |
289 this.parent = audioObject; | |
290 this.trackSliderObj = document.createElement('div'); | |
291 this.trackSliderObj.className = 'track-slider'; | |
292 this.trackSliderObj.id = 'track-slider-'+audioObject.id; | |
293 | |
294 this.trackSliderObj.setAttribute('trackIndex',audioObject.id); | |
295 this.trackSliderObj.innerHTML = '<span>'+audioObject.id+'</span>'; | |
296 this.trackSliderObj.draggable = true; | |
297 this.trackSliderObj.ondragend = dragEnd; | |
298 | |
299 // Onclick, switch playback to that track | |
300 this.trackSliderObj.onclick = function() { | |
301 // Start the test on first click, that way timings are more accurate. | |
302 audioEngineContext.play(); | |
303 if (audioEngineContext.audioObjectsReady) { | |
304 // Cannot continue to issue play command until audioObjects reported as ready! | |
305 // Get the track ID from the object ID | |
306 var element; | |
307 if (event.srcElement.nodeName == "SPAN") { | |
308 element = event.srcElement.parentNode; | |
309 } else { | |
310 element = event.srcElement; | |
311 } | |
312 var id = Number(element.attributes['trackIndex'].value); | |
313 //audioEngineContext.metric.sliderPlayed(id); | |
314 audioEngineContext.play(id); | |
315 // Currently playing track red, rest green | |
316 | |
317 //document.getElementById('track-slider-'+index).style.backgroundColor = "#FF0000"; | |
318 $('.track-slider').removeClass('track-slider-playing'); | |
319 $(element).addClass('track-slider-playing'); | |
320 $('.comment-div').removeClass('comment-box-playing'); | |
321 $('#comment-div-'+id).addClass('comment-box-playing'); | |
322 } | |
323 }; | |
324 | |
325 this.exportXMLDOM = function() { | |
326 // Called by the audioObject holding this element. Must be present | |
327 var node = document.createElement('value'); | |
328 node.textContent = convSliderPosToRate(this.trackSliderObj); | |
329 return node; | |
330 }; | |
331 } | |
332 | |
333 function dragEnd(ev) { | |
334 // Function call when a div has been dropped | |
335 var slider = document.getElementById('slider'); | |
336 var marginSize = Number(slider.attributes['marginsize'].value); | |
337 var w = slider.style.width; | |
338 w = Number(w.substr(0,w.length-2)); | |
339 var x = ev.x; | |
340 if (x >= marginSize && x < w+marginSize) { | |
341 this.style.left = (x)+'px'; | |
342 } else { | |
343 if (x<marginSize) { | |
344 this.style.left = marginSize+'px'; | |
345 } else { | |
346 this.style.left = (w+marginSize) + 'px'; | |
347 } | |
348 } | |
349 var time = audioEngineContext.timer.getTestTime(); | |
350 var id = Number(ev.srcElement.getAttribute('trackindex')); | |
351 audioEngineContext.audioObjects[id].metric.moved(time,convSliderPosToRate(ev.srcElement)); | |
352 } | |
353 | |
354 function buttonSubmitClick() // TODO: Only when all songs have been played! | |
355 { | |
356 hasBeenPlayed = audioEngineContext.checkAllPlayed(); | |
357 if (hasBeenPlayed.length == 0) { | |
358 if (audioEngineContext.status == 1) { | |
359 var playback = document.getElementById('playback-button'); | |
360 playback.click(); | |
361 // This function is called when the submit button is clicked. Will check for any further tests to perform, or any post-test options | |
362 } else | |
363 { | |
364 if (audioEngineContext.timer.testStarted == false) | |
365 { | |
366 alert('You have not started the test! Please press start to begin the test!'); | |
367 return; | |
368 } | |
369 } | |
370 testState.advanceState(); | |
371 } else // if a fragment has not been played yet | |
372 { | |
373 str = ""; | |
374 if (hasBeenPlayed.length > 1) { | |
375 for (var i=0; i<hasBeenPlayed.length; i++) { | |
376 str = str + hasBeenPlayed[i]; | |
377 if (i < hasBeenPlayed.length-2){ | |
378 str += ", "; | |
379 } else if (i == hasBeenPlayed.length-2) { | |
380 str += " or "; | |
381 } | |
382 } | |
383 alert('You have not played fragments ' + str + ' yet. Please listen, rate and comment all samples before submitting.'); | |
384 } else { | |
385 alert('You have not played fragment ' + hasBeenPlayed[0] + ' yet. Please listen, rate and comment all samples before submitting.'); | |
386 } | |
387 return; | |
388 } | |
389 } | |
390 | |
391 function convSliderPosToRate(slider) | |
392 { | |
393 var w = document.getElementById('slider').style.width; | |
394 var marginsize = Number(document.getElementById('slider').attributes['marginsize'].value); | |
395 var maxPix = w.substr(0,w.length-2); | |
396 var pix = slider.style.left; | |
397 pix = pix.substr(0,pix.length-2); | |
398 var rate = (pix-marginsize)/maxPix; | |
399 return rate; | |
400 } | |
401 | |
402 function resizeWindow(event){ | |
403 // Function called when the window has been resized. | |
404 // MANDATORY FUNCTION | |
405 | |
406 // Store the slider marker values | |
407 var holdValues = []; | |
408 $(".track-slider").each(function(index,sliderObj){ | |
409 holdValues.push(convSliderPosToRate(index)); | |
410 }); | |
411 | |
412 var width = event.target.innerWidth; | |
413 var canvas = document.getElementById('sliderCanvasHolder'); | |
414 var sliderDiv = canvas.children[0]; | |
415 var sliderScaleDiv = canvas.children[1]; | |
416 var marginsize = Number(sliderDiv.attributes['marginsize'].value); | |
417 var w = (marginsize+8)*2; | |
418 sliderDiv.style.width = width - w + 'px'; | |
419 var width = width - w; | |
420 // Move sliders into new position | |
421 $(".track-slider").each(function(index,sliderObj){ | |
422 var pos = holdValues[index]; | |
423 var pix = pos * width; | |
424 sliderObj.style.left = pix+marginsize+'px'; | |
425 }); | |
426 | |
427 // Move scale labels | |
428 $(sliderScaleDiv.children).each(function(index,scaleObj){ | |
429 var position = Number(scaleObj.attributes['value'].value); | |
430 var pixelPosition = (position*width)+marginsize; | |
431 scaleObj.style.left = Math.floor((pixelPosition-($(scaleObj).width()/2)))+'px'; | |
432 }); | |
433 } | |
434 | |
435 function pageXMLSave(store, testXML) | |
436 { | |
437 // Saves a specific test page | |
438 var xmlDoc = store; | |
439 // Check if any session wide metrics are enabled | |
440 | |
441 var commentShow = testXML.elementComments; | |
442 | |
443 var metric = document.createElement('metric'); | |
444 if (audioEngineContext.metric.enableTestTimer) | |
445 { | |
446 var testTime = document.createElement('metricResult'); | |
447 testTime.id = 'testTime'; | |
448 testTime.textContent = audioEngineContext.timer.testDuration; | |
449 metric.appendChild(testTime); | |
450 } | |
451 xmlDoc.appendChild(metric); | |
452 var audioObjects = audioEngineContext.audioObjects; | |
453 for (var i=0; i<audioObjects.length; i++) | |
454 { | |
455 var audioElement = audioEngineContext.audioObjects[i].exportXMLDOM(); | |
456 xmlDoc.appendChild(audioElement); | |
457 } | |
458 | |
459 $(interfaceContext.commentQuestions).each(function(index,element){ | |
460 var node = element.exportXMLDOM(); | |
461 xmlDoc.appendChild(node); | |
462 }); | |
463 store = xmlDoc; | |
464 } |