Mercurial > hg > webaudioevaluationtool
comparison core.js @ 905:b64ddb277eb0
Merge from the default branch
author | Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk> |
---|---|
date | Thu, 04 Jun 2015 10:36:05 +0100 |
parents | |
children | 6d37dd0f1dc7 35682e8e1159 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 905:b64ddb277eb0 |
---|---|
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 var popup; // Hold the interfacePopup object | |
12 var testState; | |
13 var currentState; // Keep track of the current state (pre/post test, which test, final test? first test?) | |
14 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order | |
15 var audioEngineContext; // The custome AudioEngine object | |
16 var projectReturn; // Hold the URL for the return | |
17 | |
18 | |
19 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it | |
20 AudioBufferSourceNode.prototype.owner = undefined; | |
21 | |
22 window.onload = function() { | |
23 // Function called once the browser has loaded all files. | |
24 // This should perform any initial commands such as structure / loading documents | |
25 | |
26 // Create a web audio API context | |
27 // Fixed for cross-browser support | |
28 var AudioContext = window.AudioContext || window.webkitAudioContext; | |
29 audioContext = new AudioContext; | |
30 | |
31 // Create test state | |
32 testState = new stateMachine(); | |
33 | |
34 // Create the audio engine object | |
35 audioEngineContext = new AudioEngine(); | |
36 | |
37 // Create the popup interface object | |
38 popup = new interfacePopup(); | |
39 }; | |
40 | |
41 function interfacePopup() { | |
42 // Creates an object to manage the popup | |
43 this.popup = null; | |
44 this.popupContent = null; | |
45 this.popupButton = null; | |
46 this.popupOptions = null; | |
47 this.currentIndex = null; | |
48 this.responses = null; | |
49 this.createPopup = function(){ | |
50 // Create popup window interface | |
51 var insertPoint = document.getElementById("topLevelBody"); | |
52 var blank = document.createElement('div'); | |
53 blank.className = 'testHalt'; | |
54 | |
55 this.popup = document.createElement('div'); | |
56 this.popup.id = 'popupHolder'; | |
57 this.popup.className = 'popupHolder'; | |
58 this.popup.style.position = 'absolute'; | |
59 this.popup.style.left = (window.innerWidth/2)-250 + 'px'; | |
60 this.popup.style.top = (window.innerHeight/2)-125 + 'px'; | |
61 | |
62 this.popupContent = document.createElement('div'); | |
63 this.popupContent.id = 'popupContent'; | |
64 this.popupContent.style.marginTop = '25px'; | |
65 this.popupContent.align = 'center'; | |
66 this.popup.appendChild(this.popupContent); | |
67 | |
68 this.popupButton = document.createElement('button'); | |
69 this.popupButton.className = 'popupButton'; | |
70 this.popupButton.innerHTML = 'Next'; | |
71 this.popupButton.onclick = function(){popup.buttonClicked();}; | |
72 insertPoint.appendChild(this.popup); | |
73 insertPoint.appendChild(blank); | |
74 }; | |
75 | |
76 this.showPopup = function(){ | |
77 if (this.popup == null || this.popup == undefined) { | |
78 this.createPopup(); | |
79 } | |
80 this.popup.style.zIndex = 3; | |
81 this.popup.style.visibility = 'visible'; | |
82 var blank = document.getElementsByClassName('testHalt')[0]; | |
83 blank.style.zIndex = 2; | |
84 blank.style.visibility = 'visible'; | |
85 }; | |
86 | |
87 this.hidePopup = function(){ | |
88 this.popup.style.zIndex = -1; | |
89 this.popup.style.visibility = 'hidden'; | |
90 var blank = document.getElementsByClassName('testHalt')[0]; | |
91 blank.style.zIndex = -2; | |
92 blank.style.visibility = 'hidden'; | |
93 }; | |
94 | |
95 this.postNode = function() { | |
96 // This will take the node from the popupOptions and display it | |
97 var node = this.popupOptions[this.currentIndex]; | |
98 this.popupContent.innerHTML = null; | |
99 if (node.nodeName == 'statement') { | |
100 var span = document.createElement('span'); | |
101 span.textContent = node.textContent; | |
102 this.popupContent.appendChild(span); | |
103 } else if (node.nodeName == 'question') { | |
104 var span = document.createElement('span'); | |
105 span.textContent = node.textContent; | |
106 var textArea = document.createElement('textarea'); | |
107 var br = document.createElement('br'); | |
108 this.popupContent.appendChild(span); | |
109 this.popupContent.appendChild(br); | |
110 this.popupContent.appendChild(textArea); | |
111 this.popupContent.childNodes[2].focus(); | |
112 } | |
113 this.popupContent.appendChild(this.popupButton); | |
114 }; | |
115 | |
116 this.initState = function(node) { | |
117 //Call this with your preTest and postTest nodes when needed to | |
118 // initialise the popup procedure. | |
119 this.popupOptions = $(node).children(); | |
120 if (this.popupOptions.length > 0) { | |
121 if (node.nodeName == 'preTest' || node.nodeName == 'PreTest') { | |
122 this.responses = document.createElement('PreTest'); | |
123 } else if (node.nodeName == 'postTest' || node.nodeName == 'PostTest') { | |
124 this.responses = document.createElement('PostTest'); | |
125 } else { | |
126 console.log ('WARNING - popup node neither pre or post!'); | |
127 this.responses = document.createElement('responses'); | |
128 } | |
129 this.currentIndex = 0; | |
130 this.showPopup(); | |
131 this.postNode(); | |
132 } | |
133 }; | |
134 | |
135 this.buttonClicked = function() { | |
136 // Each time the popup button is clicked! | |
137 var node = this.popupOptions[this.currentIndex]; | |
138 if (node.nodeName == 'question') { | |
139 // Must extract the question data | |
140 var mandatory = node.attributes['mandatory']; | |
141 if (mandatory == undefined) { | |
142 mandatory = false; | |
143 } else { | |
144 if (mandatory.value == 'true'){mandatory = true;} | |
145 else {mandatory = false;} | |
146 } | |
147 var textArea = $(popup.popupContent).find('textarea')[0]; | |
148 if (mandatory == true && textArea.value.length == 0) { | |
149 alert('This question is mandatory'); | |
150 return; | |
151 } else { | |
152 // Save the text content | |
153 var hold = document.createElement('comment'); | |
154 hold.id = node.attributes['id'].value; | |
155 hold.innerHTML = textArea.value; | |
156 console.log("Question: "+ node.textContent); | |
157 console.log("Question Response: "+ textArea.value); | |
158 this.responses.appendChild(hold); | |
159 } | |
160 } | |
161 this.currentIndex++; | |
162 if (this.currentIndex < this.popupOptions.length) { | |
163 this.postNode(); | |
164 } else { | |
165 // Reached the end of the popupOptions | |
166 this.hidePopup(); | |
167 if (this.responses.nodeName == testState.stateResults[testState.stateIndex].nodeName) { | |
168 testState.stateResults[testState.stateIndex] = this.responses; | |
169 } else { | |
170 testState.stateResults[testState.stateIndex].appendChild(this.responses); | |
171 } | |
172 advanceState(); | |
173 } | |
174 }; | |
175 } | |
176 | |
177 function advanceState() | |
178 { | |
179 // Just for complete clarity | |
180 testState.advanceState(); | |
181 } | |
182 | |
183 function stateMachine() | |
184 { | |
185 // Object prototype for tracking and managing the test state | |
186 this.stateMap = []; | |
187 this.stateIndex = null; | |
188 this.currentStateMap = []; | |
189 this.currentIndex = null; | |
190 this.currentTestId = 0; | |
191 this.stateResults = []; | |
192 this.timerCallBackHolders = null; | |
193 this.initialise = function(){ | |
194 if (this.stateMap.length > 0) { | |
195 if(this.stateIndex != null) { | |
196 console.log('NOTE - State already initialise'); | |
197 } | |
198 this.stateIndex = -1; | |
199 var that = this; | |
200 for (var id=0; id<this.stateMap.length; id++){ | |
201 var name = this.stateMap[id].nodeName; | |
202 var obj = document.createElement(name); | |
203 if (name == "audioHolder") { | |
204 obj.id = this.stateMap[id].id; | |
205 } | |
206 this.stateResults.push(obj); | |
207 } | |
208 } else { | |
209 conolse.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP'); | |
210 } | |
211 }; | |
212 this.advanceState = function(){ | |
213 if (this.stateIndex == null) { | |
214 this.initialise(); | |
215 } | |
216 if (this.stateIndex == -1) { | |
217 console.log('Starting test...'); | |
218 } | |
219 if (this.currentIndex == null){ | |
220 if (this.currentStateMap.nodeName == "audioHolder") { | |
221 // Save current page | |
222 this.testPageCompleted(this.stateResults[this.stateIndex],this.currentStateMap,this.currentTestId); | |
223 this.currentTestId++; | |
224 } | |
225 this.stateIndex++; | |
226 if (this.stateIndex >= this.stateMap.length) { | |
227 console.log('Test Completed'); | |
228 createProjectSave(projectReturn); | |
229 } else { | |
230 this.currentStateMap = this.stateMap[this.stateIndex]; | |
231 if (this.currentStateMap.nodeName == "audioHolder") { | |
232 console.log('Loading test page'); | |
233 loadTest(this.currentStateMap); | |
234 this.initialiseInnerState(this.currentStateMap); | |
235 } else if (this.currentStateMap.nodeName == "PreTest" || this.currentStateMap.nodeName == "PostTest") { | |
236 if (this.currentStateMap.childElementCount >= 1) { | |
237 popup.initState(this.currentStateMap); | |
238 } else { | |
239 this.advanceState(); | |
240 } | |
241 } else { | |
242 this.advanceState(); | |
243 } | |
244 } | |
245 } else { | |
246 this.advanceInnerState(); | |
247 } | |
248 }; | |
249 | |
250 this.testPageCompleted = function(store, testXML, testId) { | |
251 // Function called each time a test page has been completed | |
252 // Can be used to over-rule default behaviour | |
253 | |
254 pageXMLSave(store, testXML, testId); | |
255 } | |
256 | |
257 this.initialiseInnerState = function(testXML) { | |
258 // Parses the received testXML for pre and post test options | |
259 this.currentStateMap = []; | |
260 var preTest = $(testXML).find('PreTest')[0]; | |
261 var postTest = $(testXML).find('PostTest')[0]; | |
262 if (preTest == undefined) {preTest = document.createElement("preTest");} | |
263 if (postTest == undefined){postTest= document.createElement("postTest");} | |
264 this.currentStateMap.push(preTest); | |
265 this.currentStateMap.push(testXML); | |
266 this.currentStateMap.push(postTest); | |
267 this.currentIndex = -1; | |
268 this.advanceInnerState(); | |
269 } | |
270 | |
271 this.advanceInnerState = function() { | |
272 this.currentIndex++; | |
273 if (this.currentIndex >= this.currentStateMap.length) { | |
274 this.currentIndex = null; | |
275 this.currentStateMap = this.stateMap[this.stateIndex]; | |
276 this.advanceState(); | |
277 } else { | |
278 if (this.currentStateMap[this.currentIndex].nodeName == "audioHolder") { | |
279 console.log("Loading test page"+this.currentTestId); | |
280 } else if (this.currentStateMap[this.currentIndex].nodeName == "PreTest") { | |
281 popup.initState(this.currentStateMap[this.currentIndex]); | |
282 } else if (this.currentStateMap[this.currentIndex].nodeName == "PostTest") { | |
283 popup.initState(this.currentStateMap[this.currentIndex]); | |
284 } else { | |
285 this.advanceInnerState(); | |
286 } | |
287 } | |
288 } | |
289 | |
290 this.previousState = function(){}; | |
291 } | |
292 | |
293 function testEnded(testId) | |
294 { | |
295 pageXMLSave(testId); | |
296 if (testXMLSetups.length-1 > testId) | |
297 { | |
298 // Yes we have another test to perform | |
299 testId = (Number(testId)+1); | |
300 currentState = 'testRun-'+testId; | |
301 loadTest(testId); | |
302 } else { | |
303 console.log('Testing Completed!'); | |
304 currentState = 'postTest'; | |
305 // Check for any post tests | |
306 var xmlSetup = projectXML.find('setup'); | |
307 var postTest = xmlSetup.find('PostTest')[0]; | |
308 popup.initState(postTest); | |
309 } | |
310 } | |
311 | |
312 function loadProjectSpec(url) { | |
313 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data | |
314 // If url is null, request client to upload project XML document | |
315 var r = new XMLHttpRequest(); | |
316 r.open('GET',url,true); | |
317 r.onload = function() { | |
318 loadProjectSpecCallback(r.response); | |
319 }; | |
320 r.send(); | |
321 }; | |
322 | |
323 function loadProjectSpecCallback(response) { | |
324 // Function called after asynchronous download of XML project specification | |
325 var decode = $.parseXML(response); | |
326 projectXML = $(decode); | |
327 | |
328 // Now extract the setup tag | |
329 var xmlSetup = projectXML.find('setup'); | |
330 // Detect the interface to use and load the relevant javascripts. | |
331 var interfaceType = xmlSetup[0].attributes['interface']; | |
332 var interfaceJS = document.createElement('script'); | |
333 interfaceJS.setAttribute("type","text/javascript"); | |
334 if (interfaceType.value == 'APE') { | |
335 interfaceJS.setAttribute("src","ape.js"); | |
336 | |
337 // APE comes with a css file | |
338 var css = document.createElement('link'); | |
339 css.rel = 'stylesheet'; | |
340 css.type = 'text/css'; | |
341 css.href = 'ape.css'; | |
342 | |
343 document.getElementsByTagName("head")[0].appendChild(css); | |
344 } | |
345 document.getElementsByTagName("head")[0].appendChild(interfaceJS); | |
346 | |
347 // Define window callbacks for interface | |
348 window.onresize = function(event){resizeWindow(event);}; | |
349 } | |
350 | |
351 function createProjectSave(destURL) { | |
352 // Save the data from interface into XML and send to destURL | |
353 // If destURL is null then download XML in client | |
354 // Now time to render file locally | |
355 var xmlDoc = interfaceXMLSave(); | |
356 var parent = document.createElement("div"); | |
357 parent.appendChild(xmlDoc); | |
358 var file = [parent.innerHTML]; | |
359 if (destURL == "null" || destURL == undefined) { | |
360 var bb = new Blob(file,{type : 'application/xml'}); | |
361 var dnlk = window.URL.createObjectURL(bb); | |
362 var a = document.createElement("a"); | |
363 a.hidden = ''; | |
364 a.href = dnlk; | |
365 a.download = "save.xml"; | |
366 a.textContent = "Save File"; | |
367 | |
368 var submitDiv = document.getElementById('download-point'); | |
369 submitDiv.appendChild(a); | |
370 popup.showPopup(); | |
371 popup.popupContent.innerHTML = null; | |
372 popup.popupContent.appendChild(submitDiv) | |
373 } else { | |
374 var xmlhttp = new XMLHttpRequest; | |
375 xmlhttp.open("POST",destURL,true); | |
376 xmlhttp.setRequestHeader('Content-Type', 'text/xml'); | |
377 xmlhttp.onerror = function(){ | |
378 console.log('Error saving file to server! Presenting download locally'); | |
379 createProjectSave(null); | |
380 }; | |
381 xmlhttp.onreadystatechange = function() { | |
382 console.log(xmlhttp.status); | |
383 if (xmlhttp.status != 200 && xmlhttp.readyState == 4) { | |
384 createProjectSave(null); | |
385 } | |
386 }; | |
387 xmlhttp.send(file); | |
388 } | |
389 return submitDiv; | |
390 } | |
391 | |
392 // Only other global function which must be defined in the interface class. Determines how to create the XML document. | |
393 function interfaceXMLSave(){ | |
394 // Create the XML string to be exported with results | |
395 var xmlDoc = document.createElement("BrowserEvaluationResult"); | |
396 xmlDoc.appendChild(returnDateNode()); | |
397 for (var i=0; i<testState.stateResults.length; i++) | |
398 { | |
399 xmlDoc.appendChild(testState.stateResults[i]); | |
400 } | |
401 | |
402 return xmlDoc; | |
403 } | |
404 | |
405 function AudioEngine() { | |
406 | |
407 // Create two output paths, the main outputGain and fooGain. | |
408 // Output gain is default to 1 and any items for playback route here | |
409 // Foo gain is used for analysis to ensure paths get processed, but are not heard | |
410 // because web audio will optimise and any route which does not go to the destination gets ignored. | |
411 this.outputGain = audioContext.createGain(); | |
412 this.fooGain = audioContext.createGain(); | |
413 this.fooGain.gain = 0; | |
414 | |
415 // Use this to detect playback state: 0 - stopped, 1 - playing | |
416 this.status = 0; | |
417 this.audioObjectsReady = false; | |
418 | |
419 // Connect both gains to output | |
420 this.outputGain.connect(audioContext.destination); | |
421 this.fooGain.connect(audioContext.destination); | |
422 | |
423 // Create the timer Object | |
424 this.timer = new timer(); | |
425 // Create session metrics | |
426 this.metric = new sessionMetrics(this); | |
427 | |
428 this.loopPlayback = false; | |
429 | |
430 // Create store for new audioObjects | |
431 this.audioObjects = []; | |
432 | |
433 this.play = function() { | |
434 // Start the timer and set the audioEngine state to playing (1) | |
435 if (this.status == 0) { | |
436 // Check if all audioObjects are ready | |
437 if (this.audioObjectsReady == false) { | |
438 this.audioObjectsReady = this.checkAllReady(); | |
439 } | |
440 if (this.audioObjectsReady == true) { | |
441 this.timer.startTest(); | |
442 if (this.loopPlayback) { | |
443 for(var i=0; i<this.audioObjects.length; i++) { | |
444 this.audioObjects[i].play(this.timer.getTestTime()+1); | |
445 } | |
446 } | |
447 this.status = 1; | |
448 } | |
449 } | |
450 }; | |
451 | |
452 this.stop = function() { | |
453 // Send stop and reset command to all playback buffers and set audioEngine state to stopped (1) | |
454 if (this.status == 1) { | |
455 for (var i=0; i<this.audioObjects.length; i++) | |
456 { | |
457 this.audioObjects[i].stop(); | |
458 } | |
459 this.status = 0; | |
460 } | |
461 }; | |
462 | |
463 | |
464 this.newTrack = function(url) { | |
465 // Pull data from given URL into new audio buffer | |
466 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin' | |
467 | |
468 // Create the audioObject with ID of the new track length; | |
469 audioObjectId = this.audioObjects.length; | |
470 this.audioObjects[audioObjectId] = new audioObject(audioObjectId); | |
471 | |
472 // AudioObject will get track itself. | |
473 this.audioObjects[audioObjectId].constructTrack(url); | |
474 }; | |
475 | |
476 this.newTestPage = function() { | |
477 this.state = 0; | |
478 this.audioObjectsReady = false; | |
479 this.metric.reset(); | |
480 this.audioObjects = []; | |
481 }; | |
482 | |
483 this.checkAllPlayed = function() { | |
484 arr = []; | |
485 for (var id=0; id<this.audioObjects.length; id++) { | |
486 if (this.audioObjects[id].metric.wasListenedTo == false) { | |
487 arr.push(this.audioObjects[id].id); | |
488 } | |
489 } | |
490 return arr; | |
491 }; | |
492 | |
493 this.checkAllReady = function() { | |
494 var ready = true; | |
495 for (var i=0; i<this.audioObjects.length; i++) { | |
496 if (this.audioObjects[i].state == 0) { | |
497 // Track not ready | |
498 console.log('WAIT -- audioObject '+i+' not ready yet!'); | |
499 ready = false; | |
500 }; | |
501 } | |
502 return ready; | |
503 }; | |
504 | |
505 } | |
506 | |
507 function audioObject(id) { | |
508 // The main buffer object with common control nodes to the AudioEngine | |
509 | |
510 this.id = id; | |
511 this.state = 0; // 0 - no data, 1 - ready | |
512 this.url = null; // Hold the URL given for the output back to the results. | |
513 this.metric = new metricTracker(this); | |
514 | |
515 // Create a buffer and external gain control to allow internal patching of effects and volume leveling. | |
516 this.bufferNode = undefined; | |
517 this.outputGain = audioContext.createGain(); | |
518 | |
519 // Default output gain to be zero | |
520 this.outputGain.gain.value = 0.0; | |
521 | |
522 // Connect buffer to the audio graph | |
523 this.outputGain.connect(audioEngineContext.outputGain); | |
524 | |
525 // the audiobuffer is not designed for multi-start playback | |
526 // When stopeed, the buffer node is deleted and recreated with the stored buffer. | |
527 this.buffer; | |
528 | |
529 this.loopStart = function() { | |
530 this.outputGain.gain.value = 1.0; | |
531 this.metric.startListening(audioEngineContext.timer.getTestTime()); | |
532 } | |
533 | |
534 this.loopStop = function() { | |
535 if (this.outputGain.gain.value != 0.0) { | |
536 this.outputGain.gain.value = 0.0; | |
537 this.metric.stopListening(audioEngineContext.timer.getTestTime()); | |
538 } | |
539 } | |
540 | |
541 this.play = function(startTime) { | |
542 this.bufferNode = audioContext.createBufferSource(); | |
543 this.bufferNode.owner = this; | |
544 this.bufferNode.connect(this.outputGain); | |
545 this.bufferNode.buffer = this.buffer; | |
546 this.bufferNode.loop = audioEngineContext.loopPlayback; | |
547 this.bufferNode.onended = function() { | |
548 // Safari does not like using 'this' to reference the calling object! | |
549 event.srcElement.owner.metric.stopListening(audioEngineContext.timer.getTestTime()); | |
550 }; | |
551 if (this.bufferNode.loop == false) { | |
552 this.metric.startListening(audioEngineContext.timer.getTestTime()); | |
553 } | |
554 this.bufferNode.start(startTime); | |
555 }; | |
556 | |
557 this.stop = function() { | |
558 if (this.bufferNode != undefined) | |
559 { | |
560 this.bufferNode.stop(0); | |
561 this.bufferNode = undefined; | |
562 this.metric.stopListening(audioEngineContext.timer.getTestTime()); | |
563 } | |
564 }; | |
565 | |
566 this.getCurrentPosition = function() { | |
567 var time = audioEngineContext.timer.getTestTime(); | |
568 if (this.bufferNode != undefined) { | |
569 if (this.bufferNode.loop == true) { | |
570 if (audioEngineContext.status == 1) { | |
571 return time%this.buffer.duration; | |
572 } else { | |
573 return 0; | |
574 } | |
575 } else { | |
576 if (this.metric.listenHold) { | |
577 return time - this.metric.listenStart; | |
578 } else { | |
579 return 0; | |
580 } | |
581 } | |
582 } else { | |
583 return 0; | |
584 } | |
585 }; | |
586 | |
587 this.constructTrack = function(url) { | |
588 var request = new XMLHttpRequest(); | |
589 this.url = url; | |
590 request.open('GET',url,true); | |
591 request.responseType = 'arraybuffer'; | |
592 | |
593 var audioObj = this; | |
594 | |
595 // Create callback to decode the data asynchronously | |
596 request.onloadend = function() { | |
597 audioContext.decodeAudioData(request.response, function(decodedData) { | |
598 audioObj.buffer = decodedData; | |
599 audioObj.state = 1; | |
600 }, function(){ | |
601 // Should only be called if there was an error, but sometimes gets called continuously | |
602 // Check here if the error is genuine | |
603 if (audioObj.state == 0 || audioObj.buffer == undefined) { | |
604 // Genuine error | |
605 console.log('FATAL - Error loading buffer on '+audioObj.id); | |
606 } | |
607 }); | |
608 }; | |
609 request.send(); | |
610 }; | |
611 | |
612 } | |
613 | |
614 function timer() | |
615 { | |
616 /* Timer object used in audioEngine to keep track of session timings | |
617 * Uses the timer of the web audio API, so sample resolution | |
618 */ | |
619 this.testStarted = false; | |
620 this.testStartTime = 0; | |
621 this.testDuration = 0; | |
622 this.minimumTestTime = 0; // No minimum test time | |
623 this.startTest = function() | |
624 { | |
625 if (this.testStarted == false) | |
626 { | |
627 this.testStartTime = audioContext.currentTime; | |
628 this.testStarted = true; | |
629 this.updateTestTime(); | |
630 audioEngineContext.metric.initialiseTest(); | |
631 } | |
632 }; | |
633 this.stopTest = function() | |
634 { | |
635 if (this.testStarted) | |
636 { | |
637 this.testDuration = this.getTestTime(); | |
638 this.testStarted = false; | |
639 } else { | |
640 console.log('ERR: Test tried to end before beginning'); | |
641 } | |
642 }; | |
643 this.updateTestTime = function() | |
644 { | |
645 if (this.testStarted) | |
646 { | |
647 this.testDuration = audioContext.currentTime - this.testStartTime; | |
648 } | |
649 }; | |
650 this.getTestTime = function() | |
651 { | |
652 this.updateTestTime(); | |
653 return this.testDuration; | |
654 }; | |
655 } | |
656 | |
657 function sessionMetrics(engine) | |
658 { | |
659 /* Used by audioEngine to link to audioObjects to minimise the timer call timers; | |
660 */ | |
661 this.engine = engine; | |
662 this.lastClicked = -1; | |
663 this.data = -1; | |
664 this.reset = function() { | |
665 this.lastClicked = -1; | |
666 this.data = -1; | |
667 }; | |
668 this.initialiseTest = function(){}; | |
669 } | |
670 | |
671 function metricTracker(caller) | |
672 { | |
673 /* Custom object to track and collect metric data | |
674 * Used only inside the audioObjects object. | |
675 */ | |
676 | |
677 this.listenedTimer = 0; | |
678 this.listenStart = 0; | |
679 this.listenHold = false; | |
680 this.initialPosition = -1; | |
681 this.movementTracker = []; | |
682 this.listenTracker =[]; | |
683 this.wasListenedTo = false; | |
684 this.wasMoved = false; | |
685 this.hasComments = false; | |
686 this.parent = caller; | |
687 | |
688 this.initialised = function(position) | |
689 { | |
690 if (this.initialPosition == -1) { | |
691 this.initialPosition = position; | |
692 } | |
693 }; | |
694 | |
695 this.moved = function(time,position) | |
696 { | |
697 this.wasMoved = true; | |
698 this.movementTracker[this.movementTracker.length] = [time, position]; | |
699 }; | |
700 | |
701 this.startListening = function(time) | |
702 { | |
703 if (this.listenHold == false) | |
704 { | |
705 this.wasListenedTo = true; | |
706 this.listenStart = time; | |
707 this.listenHold = true; | |
708 | |
709 var evnt = document.createElement('event'); | |
710 var testTime = document.createElement('testTime'); | |
711 testTime.setAttribute('start',time); | |
712 var bufferTime = document.createElement('bufferTime'); | |
713 bufferTime.setAttribute('start',this.parent.getCurrentPosition()); | |
714 evnt.appendChild(testTime); | |
715 evnt.appendChild(bufferTime); | |
716 this.listenTracker.push(evnt); | |
717 | |
718 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id | |
719 } | |
720 }; | |
721 | |
722 this.stopListening = function(time) | |
723 { | |
724 if (this.listenHold == true) | |
725 { | |
726 var diff = time - this.listenStart; | |
727 this.listenedTimer += (diff); | |
728 this.listenStart = 0; | |
729 this.listenHold = false; | |
730 | |
731 var evnt = this.listenTracker[this.listenTracker.length-1]; | |
732 var testTime = evnt.getElementsByTagName('testTime')[0]; | |
733 var bufferTime = evnt.getElementsByTagName('bufferTime')[0]; | |
734 testTime.setAttribute('stop',time); | |
735 bufferTime.setAttribute('stop',this.parent.getCurrentPosition()); | |
736 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id | |
737 } | |
738 }; | |
739 } | |
740 | |
741 function randomiseOrder(input) | |
742 { | |
743 // This takes an array of information and randomises the order | |
744 var N = input.length; | |
745 var K = N; | |
746 var holdArr = []; | |
747 for (var n=0; n<N; n++) | |
748 { | |
749 // First pick a random number | |
750 var r = Math.random(); | |
751 // Multiply and floor by the number of elements left | |
752 r = Math.floor(r*input.length); | |
753 // Pick out that element and delete from the array | |
754 holdArr.push(input.splice(r,1)[0]); | |
755 } | |
756 return holdArr; | |
757 } | |
758 | |
759 function returnDateNode() | |
760 { | |
761 // Create an XML Node for the Date and Time a test was conducted | |
762 // Structure is | |
763 // <datetime> | |
764 // <date year="##" month="##" day="##">DD/MM/YY</date> | |
765 // <time hour="##" minute="##" sec="##">HH:MM:SS</time> | |
766 // </datetime> | |
767 var dateTime = new Date(); | |
768 var year = document.createAttribute('year'); | |
769 var month = document.createAttribute('month'); | |
770 var day = document.createAttribute('day'); | |
771 var hour = document.createAttribute('hour'); | |
772 var minute = document.createAttribute('minute'); | |
773 var secs = document.createAttribute('secs'); | |
774 | |
775 year.nodeValue = dateTime.getFullYear(); | |
776 month.nodeValue = dateTime.getMonth()+1; | |
777 day.nodeValue = dateTime.getDate(); | |
778 hour.nodeValue = dateTime.getHours(); | |
779 minute.nodeValue = dateTime.getMinutes(); | |
780 secs.nodeValue = dateTime.getSeconds(); | |
781 | |
782 var hold = document.createElement("datetime"); | |
783 var date = document.createElement("date"); | |
784 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue; | |
785 var time = document.createElement("time"); | |
786 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue; | |
787 | |
788 date.setAttributeNode(year); | |
789 date.setAttributeNode(month); | |
790 date.setAttributeNode(day); | |
791 time.setAttributeNode(hour); | |
792 time.setAttributeNode(minute); | |
793 time.setAttributeNode(secs); | |
794 | |
795 hold.appendChild(date); | |
796 hold.appendChild(time); | |
797 return hold | |
798 | |
799 } | |
800 | |
801 function testWaitIndicator() { | |
802 if (audioEngineContext.checkAllReady() == false) { | |
803 var hold = document.createElement("div"); | |
804 hold.id = "testWaitIndicator"; | |
805 hold.className = "indicator-box"; | |
806 var span = document.createElement("span"); | |
807 span.textContent = "Please wait! Elements still loading"; | |
808 hold.appendChild(span); | |
809 var body = document.getElementsByTagName('body')[0]; | |
810 body.appendChild(hold); | |
811 testWaitTimerIntervalHolder = setInterval(function(){ | |
812 var ready = audioEngineContext.checkAllReady(); | |
813 if (ready) { | |
814 var elem = document.getElementById('testWaitIndicator'); | |
815 var body = document.getElementsByTagName('body')[0]; | |
816 body.removeChild(elem); | |
817 clearInterval(testWaitTimerIntervalHolder); | |
818 } | |
819 },500,false); | |
820 } | |
821 } | |
822 | |
823 var testWaitTimerIntervalHolder = null; |