Mercurial > hg > webaudioevaluationtool
comparison core.js @ 1608:a21deccefe7d
Currently playing sample red.
author | Brecht De Man <b.deman@qmul.ac.uk> |
---|---|
date | Sat, 16 May 2015 17:52:51 +0100 |
parents | |
children | 759c2ace3c65 f378fb8286ae |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 1608:a21deccefe7d |
---|---|
1 /** | |
2 * core.js | |
3 * | |
4 * Main script to run, calls all other core functions and manages loading/store to backend. | |
5 * Also contains all global variables. | |
6 */ | |
7 | |
8 /* create the web audio API context and store in audioContext*/ | |
9 var audioContext; // Hold the browser web audio API | |
10 var projectXML; // Hold the parsed setup XML | |
11 | |
12 var testXMLSetups = []; // Hold the parsed test instances | |
13 var testResultsHolders =[]; // Hold the results from each test for publishing to XML | |
14 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order | |
15 var currentTestHolder; // Hold any intermediate results during test - metrics | |
16 var audioEngineContext; // The custome AudioEngine object | |
17 var projectReturn; // Hold the URL for the return | |
18 var preTestQuestions = document.createElement('PreTest'); // Store any pre-test question response | |
19 var postTestQuestions = document.createElement('PostTest'); // Store any post-test question response | |
20 | |
21 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it | |
22 AudioBufferSourceNode.prototype.owner = undefined; | |
23 | |
24 window.onload = function() { | |
25 // Function called once the browser has loaded all files. | |
26 // This should perform any initial commands such as structure / loading documents | |
27 | |
28 // Create a web audio API context | |
29 // Fixed for cross-browser support | |
30 var AudioContext = window.AudioContext || window.webkitAudioContext; | |
31 audioContext = new AudioContext; | |
32 | |
33 // Create the audio engine object | |
34 audioEngineContext = new AudioEngine(); | |
35 }; | |
36 | |
37 function loadProjectSpec(url) { | |
38 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data | |
39 // If url is null, request client to upload project XML document | |
40 var r = new XMLHttpRequest(); | |
41 r.open('GET',url,true); | |
42 r.onload = function() { | |
43 loadProjectSpecCallback(r.response); | |
44 }; | |
45 r.send(); | |
46 }; | |
47 | |
48 function loadProjectSpecCallback(response) { | |
49 // Function called after asynchronous download of XML project specification | |
50 var decode = $.parseXML(response); | |
51 projectXML = $(decode); | |
52 | |
53 // Now extract the setup tag | |
54 var xmlSetup = projectXML.find('setup'); | |
55 // Detect the interface to use and load the relevant javascripts. | |
56 var interfaceType = xmlSetup[0].attributes['interface']; | |
57 var interfaceJS = document.createElement('script'); | |
58 interfaceJS.setAttribute("type","text/javascript"); | |
59 if (interfaceType.value == 'APE') { | |
60 interfaceJS.setAttribute("src","ape.js"); | |
61 | |
62 // APE comes with a css file | |
63 var css = document.createElement('link'); | |
64 css.rel = 'stylesheet'; | |
65 css.type = 'text/css'; | |
66 css.href = 'ape.css'; | |
67 | |
68 document.getElementsByTagName("head")[0].appendChild(css); | |
69 } | |
70 document.getElementsByTagName("head")[0].appendChild(interfaceJS); | |
71 } | |
72 | |
73 function createProjectSave(destURL) { | |
74 // Save the data from interface into XML and send to destURL | |
75 // If destURL is null then download XML in client | |
76 // Now time to render file locally | |
77 var xmlDoc = interfaceXMLSave(); | |
78 if (destURL == "null" || destURL == undefined) { | |
79 var parent = document.createElement("div"); | |
80 parent.appendChild(xmlDoc); | |
81 var file = [parent.innerHTML]; | |
82 var bb = new Blob(file,{type : 'application/xml'}); | |
83 var dnlk = window.URL.createObjectURL(bb); | |
84 var a = document.createElement("a"); | |
85 a.hidden = ''; | |
86 a.href = dnlk; | |
87 a.download = "save.xml"; | |
88 a.textContent = "Save File"; | |
89 | |
90 var submitDiv = document.getElementById('download-point'); | |
91 submitDiv.appendChild(a); | |
92 } | |
93 return submitDiv; | |
94 } | |
95 | |
96 function AudioEngine() { | |
97 | |
98 // Create two output paths, the main outputGain and fooGain. | |
99 // Output gain is default to 1 and any items for playback route here | |
100 // Foo gain is used for analysis to ensure paths get processed, but are not heard | |
101 // because web audio will optimise and any route which does not go to the destination gets ignored. | |
102 this.outputGain = audioContext.createGain(); | |
103 this.fooGain = audioContext.createGain(); | |
104 this.fooGain.gain = 0; | |
105 | |
106 // Use this to detect playback state: 0 - stopped, 1 - playing | |
107 this.status = 0; | |
108 | |
109 // Connect both gains to output | |
110 this.outputGain.connect(audioContext.destination); | |
111 this.fooGain.connect(audioContext.destination); | |
112 | |
113 // Create the timer Object | |
114 this.timer = new timer(); | |
115 // Create session metrics | |
116 this.metric = new sessionMetrics(this); | |
117 | |
118 this.loopPlayback = false; | |
119 | |
120 // Create store for new audioObjects | |
121 this.audioObjects = []; | |
122 | |
123 this.play = function(){}; | |
124 | |
125 this.stop = function(){}; | |
126 | |
127 | |
128 this.newTrack = function(url) { | |
129 // Pull data from given URL into new audio buffer | |
130 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin' | |
131 | |
132 // Create the audioObject with ID of the new track length; | |
133 audioObjectId = this.audioObjects.length; | |
134 this.audioObjects[audioObjectId] = new audioObject(audioObjectId); | |
135 | |
136 // AudioObject will get track itself. | |
137 this.audioObjects[audioObjectId].constructTrack(url); | |
138 }; | |
139 | |
140 } | |
141 | |
142 function audioObject(id) { | |
143 // The main buffer object with common control nodes to the AudioEngine | |
144 | |
145 this.id = id; | |
146 this.state = 0; // 0 - no data, 1 - ready | |
147 this.url = null; // Hold the URL given for the output back to the results. | |
148 this.metric = new metricTracker(); | |
149 | |
150 // Create a buffer and external gain control to allow internal patching of effects and volume leveling. | |
151 this.bufferNode = undefined; | |
152 this.outputGain = audioContext.createGain(); | |
153 | |
154 // Default output gain to be zero | |
155 this.outputGain.gain.value = 0.0; | |
156 | |
157 // Connect buffer to the audio graph | |
158 this.outputGain.connect(audioEngineContext.outputGain); | |
159 | |
160 // the audiobuffer is not designed for multi-start playback | |
161 // When stopeed, the buffer node is deleted and recreated with the stored buffer. | |
162 this.buffer; | |
163 | |
164 this.play = function(startTime) { | |
165 this.bufferNode = audioContext.createBufferSource(); | |
166 this.bufferNode.connect(this.outputGain); | |
167 this.bufferNode.buffer = this.buffer; | |
168 this.bufferNode.loop = audioEngineContext.loopPlayback; | |
169 this.bufferNode.start(startTime); | |
170 }; | |
171 | |
172 this.stop = function() { | |
173 this.bufferNode.stop(0); | |
174 this.bufferNode = undefined; | |
175 }; | |
176 | |
177 this.constructTrack = function(url) { | |
178 var request = new XMLHttpRequest(); | |
179 this.url = url; | |
180 request.open('GET',url,true); | |
181 request.responseType = 'arraybuffer'; | |
182 | |
183 var audioObj = this; | |
184 | |
185 // Create callback to decode the data asynchronously | |
186 request.onloadend = function() { | |
187 audioContext.decodeAudioData(request.response, function(decodedData) { | |
188 audioObj.buffer = decodedData; | |
189 audioObj.state = 1; | |
190 }, function(){ | |
191 // Should only be called if there was an error, but sometimes gets called continuously | |
192 // Check here if the error is genuine | |
193 if (audioObj.state == 0 || audioObj.buffer == undefined) { | |
194 // Genuine error | |
195 console.log('FATAL - Error loading buffer on '+audioObj.id); | |
196 } | |
197 }); | |
198 }; | |
199 request.send(); | |
200 }; | |
201 | |
202 } | |
203 | |
204 function timer() | |
205 { | |
206 /* Timer object used in audioEngine to keep track of session timings | |
207 * Uses the timer of the web audio API, so sample resolution | |
208 */ | |
209 this.testStarted = false; | |
210 this.testStartTime = 0; | |
211 this.testDuration = 0; | |
212 this.minimumTestTime = 0; // No minimum test time | |
213 this.startTest = function() | |
214 { | |
215 if (this.testStarted == false) | |
216 { | |
217 this.testStartTime = audioContext.currentTime; | |
218 this.testStarted = true; | |
219 this.updateTestTime(); | |
220 audioEngineContext.metric.initialiseTest(); | |
221 } | |
222 }; | |
223 this.stopTest = function() | |
224 { | |
225 if (this.testStarted) | |
226 { | |
227 this.testDuration = this.getTestTime(); | |
228 this.testStarted = false; | |
229 } else { | |
230 console.log('ERR: Test tried to end before beginning'); | |
231 } | |
232 }; | |
233 this.updateTestTime = function() | |
234 { | |
235 if (this.testStarted) | |
236 { | |
237 this.testDuration = audioContext.currentTime - this.testStartTime; | |
238 } | |
239 }; | |
240 this.getTestTime = function() | |
241 { | |
242 this.updateTestTime(); | |
243 return this.testDuration; | |
244 }; | |
245 } | |
246 | |
247 function sessionMetrics(engine) | |
248 { | |
249 /* Used by audioEngine to link to audioObjects to minimise the timer call timers; | |
250 */ | |
251 this.engine = engine; | |
252 this.lastClicked = -1; | |
253 this.data = -1; | |
254 this.initialiseTest = function(){}; | |
255 } | |
256 | |
257 function metricTracker() | |
258 { | |
259 /* Custom object to track and collect metric data | |
260 * Used only inside the audioObjects object. | |
261 */ | |
262 | |
263 this.listenedTimer = 0; | |
264 this.listenStart = 0; | |
265 this.initialPosition = -1; | |
266 this.movementTracker = []; | |
267 this.wasListenedTo = false; | |
268 this.wasMoved = false; | |
269 this.hasComments = false; | |
270 | |
271 this.initialised = function(position) | |
272 { | |
273 if (this.initialPosition == -1) { | |
274 this.initialPosition = position; | |
275 } | |
276 }; | |
277 | |
278 this.moved = function(time,position) | |
279 { | |
280 this.wasMoved = true; | |
281 this.movementTracker[this.movementTracker.length] = [time, position]; | |
282 }; | |
283 | |
284 this.listening = function(time) | |
285 { | |
286 if (this.listenStart == 0) | |
287 { | |
288 this.wasListenedTo = true; | |
289 this.listenStart = time; | |
290 } else { | |
291 this.listenedTimer += (time - this.listenStart); | |
292 this.listenStart = 0; | |
293 } | |
294 }; | |
295 } | |
296 | |
297 function randomiseOrder(input) | |
298 { | |
299 // This takes an array of information and randomises the order | |
300 var N = input.length; | |
301 var K = N; | |
302 var holdArr = []; | |
303 for (var n=0; n<N; n++) | |
304 { | |
305 // First pick a random number | |
306 var r = Math.random(); | |
307 // Multiply and floor by the number of elements left | |
308 r = Math.floor(r*input.length); | |
309 // Pick out that element and delete from the array | |
310 holdArr.push(input.splice(r,1)[0]); | |
311 } | |
312 return holdArr; | |
313 } |