Mercurial > hg > webaudioevaluationtool
comparison core.js @ 1604:d5d7dfdbf335
Hacky test wait indicator!!
author | Nicholas Jillings <nickjillings@users.noreply.github.com> |
---|---|
date | Thu, 28 May 2015 18:58:45 +0100 |
parents | |
children | dea30ed2b549 |
comparison
equal
deleted
inserted
replaced
-1:000000000000 | 1604:d5d7dfdbf335 |
---|---|
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 testXMLSetups = []; // Hold the parsed test instances | |
15 //var testResultsHolders =[]; // Hold the results from each test for publishing to XML | |
16 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order | |
17 //var currentTestHolder; // Hold any intermediate results during test - metrics | |
18 var audioEngineContext; // The custome AudioEngine object | |
19 var projectReturn; // Hold the URL for the return | |
20 //var preTestQuestions = document.createElement('PreTest'); // Store any pre-test question response | |
21 //var postTestQuestions = document.createElement('PostTest'); // Store any post-test question response | |
22 | |
23 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it | |
24 AudioBufferSourceNode.prototype.owner = undefined; | |
25 | |
26 window.onload = function() { | |
27 // Function called once the browser has loaded all files. | |
28 // This should perform any initial commands such as structure / loading documents | |
29 | |
30 // Create a web audio API context | |
31 // Fixed for cross-browser support | |
32 var AudioContext = window.AudioContext || window.webkitAudioContext; | |
33 audioContext = new AudioContext; | |
34 | |
35 // Create test state | |
36 testState = new stateMachine(); | |
37 | |
38 // Create the audio engine object | |
39 audioEngineContext = new AudioEngine(); | |
40 | |
41 // Create the popup interface object | |
42 popup = new interfacePopup(); | |
43 }; | |
44 | |
45 function interfacePopup() { | |
46 // Creates an object to manage the popup | |
47 this.popup = null; | |
48 this.popupContent = null; | |
49 this.popupButton = null; | |
50 this.popupOptions = null; | |
51 this.currentIndex = null; | |
52 this.responses = null; | |
53 this.createPopup = function(){ | |
54 // Create popup window interface | |
55 var insertPoint = document.getElementById("topLevelBody"); | |
56 var blank = document.createElement('div'); | |
57 blank.className = 'testHalt'; | |
58 | |
59 this.popup = document.createElement('div'); | |
60 this.popup.id = 'popupHolder'; | |
61 this.popup.className = 'popupHolder'; | |
62 this.popup.style.position = 'absolute'; | |
63 this.popup.style.left = (window.innerWidth/2)-250 + 'px'; | |
64 this.popup.style.top = (window.innerHeight/2)-125 + 'px'; | |
65 | |
66 this.popupContent = document.createElement('div'); | |
67 this.popupContent.id = 'popupContent'; | |
68 this.popupContent.style.marginTop = '25px'; | |
69 this.popupContent.align = 'center'; | |
70 this.popup.appendChild(this.popupContent); | |
71 | |
72 this.popupButton = document.createElement('button'); | |
73 this.popupButton.className = 'popupButton'; | |
74 this.popupButton.innerHTML = 'Next'; | |
75 this.popupButton.onclick = function(){popup.buttonClicked();}; | |
76 insertPoint.appendChild(this.popup); | |
77 insertPoint.appendChild(blank); | |
78 }; | |
79 | |
80 this.showPopup = function(){ | |
81 if (this.popup == null || this.popup == undefined) { | |
82 this.createPopup(); | |
83 } | |
84 this.popup.style.zIndex = 3; | |
85 this.popup.style.visibility = 'visible'; | |
86 var blank = document.getElementsByClassName('testHalt')[0]; | |
87 blank.style.zIndex = 2; | |
88 blank.style.visibility = 'visible'; | |
89 }; | |
90 | |
91 this.hidePopup = function(){ | |
92 this.popup.style.zIndex = -1; | |
93 this.popup.style.visibility = 'hidden'; | |
94 var blank = document.getElementsByClassName('testHalt')[0]; | |
95 blank.style.zIndex = -2; | |
96 blank.style.visibility = 'hidden'; | |
97 }; | |
98 | |
99 this.postNode = function() { | |
100 // This will take the node from the popupOptions and display it | |
101 var node = this.popupOptions[this.currentIndex]; | |
102 this.popupContent.innerHTML = null; | |
103 if (node.nodeName == 'statement') { | |
104 var span = document.createElement('span'); | |
105 span.textContent = node.textContent; | |
106 this.popupContent.appendChild(span); | |
107 } else if (node.nodeName == 'question') { | |
108 var span = document.createElement('span'); | |
109 span.textContent = node.textContent; | |
110 var textArea = document.createElement('textarea'); | |
111 var br = document.createElement('br'); | |
112 this.popupContent.appendChild(span); | |
113 this.popupContent.appendChild(br); | |
114 this.popupContent.appendChild(textArea); | |
115 } | |
116 this.popupContent.appendChild(this.popupButton); | |
117 } | |
118 | |
119 this.initState = function(node) { | |
120 //Call this with your preTest and postTest nodes when needed to | |
121 // initialise the popup procedure. | |
122 this.popupOptions = $(node).children(); | |
123 if (this.popupOptions.length > 0) { | |
124 if (node.nodeName == 'preTest' || node.nodeName == 'PreTest') { | |
125 this.responses = document.createElement('PreTest'); | |
126 } else if (node.nodeName == 'postTest' || node.nodeName == 'PostTest') { | |
127 this.responses = document.createElement('PostTest'); | |
128 } else { | |
129 console.log ('WARNING - popup node neither pre or post!'); | |
130 this.responses = document.createElement('responses'); | |
131 } | |
132 this.currentIndex = 0; | |
133 this.showPopup(); | |
134 this.postNode(); | |
135 } | |
136 } | |
137 | |
138 this.buttonClicked = function() { | |
139 // Each time the popup button is clicked! | |
140 var node = this.popupOptions[this.currentIndex]; | |
141 if (node.nodeName == 'question') { | |
142 // Must extract the question data | |
143 var mandatory = node.attributes['mandatory']; | |
144 if (mandatory == undefined) { | |
145 mandatory = false; | |
146 } else { | |
147 if (mandatory.value == 'true'){mandatory = true;} | |
148 else {mandatory = false;} | |
149 } | |
150 var textArea = $(popup.popupContent).find('textarea')[0]; | |
151 if (mandatory == true && textArea.value.length == 0) { | |
152 alert('This question is mandatory'); | |
153 return; | |
154 } else { | |
155 // Save the text content | |
156 var hold = document.createElement('comment'); | |
157 hold.id = node.attributes['id'].value; | |
158 hold.innerHTML = textArea.value; | |
159 console.log("Question: "+ node.textContent); | |
160 console.log("Question Response: "+ textArea.value); | |
161 this.responses.appendChild(hold); | |
162 } | |
163 } | |
164 this.currentIndex++; | |
165 if (this.currentIndex < this.popupOptions.length) { | |
166 this.postNode(); | |
167 } else { | |
168 // Reached the end of the popupOptions | |
169 this.hidePopup(); | |
170 if (this.responses.nodeName == testState.stateResults[testState.stateIndex].nodeName) { | |
171 testState.stateResults[testState.stateIndex] = this.responses; | |
172 } else { | |
173 testState.stateResults[testState.stateIndex].appendChild(this.responses); | |
174 } | |
175 advanceState(); | |
176 } | |
177 } | |
178 } | |
179 | |
180 function advanceState() | |
181 { | |
182 // Just for complete clarity | |
183 testState.advanceState(); | |
184 } | |
185 | |
186 function stateMachine() | |
187 { | |
188 // Object prototype for tracking and managing the test state | |
189 this.stateMap = []; | |
190 this.stateIndex = null; | |
191 this.currentStateMap = []; | |
192 this.currentIndex = null; | |
193 this.currentTestId = 0; | |
194 this.stateResults = []; | |
195 this.timerCallBackHolders = null; | |
196 this.initialise = function(){ | |
197 if (this.stateMap.length > 0) { | |
198 if(this.stateIndex != null) { | |
199 console.log('NOTE - State already initialise'); | |
200 } | |
201 this.stateIndex = -1; | |
202 var that = this; | |
203 for (var id=0; id<this.stateMap.length; id++){ | |
204 var name = this.stateMap[id].nodeName; | |
205 var obj = document.createElement(name); | |
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.send(file); | |
382 } | |
383 return submitDiv; | |
384 } | |
385 | |
386 // Only other global function which must be defined in the interface class. Determines how to create the XML document. | |
387 function interfaceXMLSave(){ | |
388 // Create the XML string to be exported with results | |
389 var xmlDoc = document.createElement("BrowserEvaluationResult"); | |
390 xmlDoc.appendChild(returnDateNode()); | |
391 for (var i=0; i<testState.stateResults.length; i++) | |
392 { | |
393 xmlDoc.appendChild(testState.stateResults[i]); | |
394 } | |
395 | |
396 return xmlDoc; | |
397 } | |
398 | |
399 function AudioEngine() { | |
400 | |
401 // Create two output paths, the main outputGain and fooGain. | |
402 // Output gain is default to 1 and any items for playback route here | |
403 // Foo gain is used for analysis to ensure paths get processed, but are not heard | |
404 // because web audio will optimise and any route which does not go to the destination gets ignored. | |
405 this.outputGain = audioContext.createGain(); | |
406 this.fooGain = audioContext.createGain(); | |
407 this.fooGain.gain = 0; | |
408 | |
409 // Use this to detect playback state: 0 - stopped, 1 - playing | |
410 this.status = 0; | |
411 this.audioObjectsReady = false; | |
412 | |
413 // Connect both gains to output | |
414 this.outputGain.connect(audioContext.destination); | |
415 this.fooGain.connect(audioContext.destination); | |
416 | |
417 // Create the timer Object | |
418 this.timer = new timer(); | |
419 // Create session metrics | |
420 this.metric = new sessionMetrics(this); | |
421 | |
422 this.loopPlayback = false; | |
423 | |
424 // Create store for new audioObjects | |
425 this.audioObjects = []; | |
426 | |
427 this.play = function() { | |
428 // Start the timer and set the audioEngine state to playing (1) | |
429 if (this.status == 0) { | |
430 // Check if all audioObjects are ready | |
431 if (this.audioObjectsReady == false) { | |
432 this.audioObjectsReady = this.checkAllReady(); | |
433 } | |
434 if (this.audioObjectsReady == true) { | |
435 this.timer.startTest(); | |
436 if (this.loopPlayback) { | |
437 for(var i=0; i<this.audioObjects.length; i++) { | |
438 this.audioObjects[i].play(this.timer.getTestTime()+1); | |
439 } | |
440 } | |
441 this.status = 1; | |
442 } | |
443 } | |
444 }; | |
445 | |
446 this.stop = function() { | |
447 // Send stop and reset command to all playback buffers and set audioEngine state to stopped (1) | |
448 if (this.status == 1) { | |
449 for (var i=0; i<this.audioObjects.length; i++) | |
450 { | |
451 this.audioObjects[i].stop(); | |
452 } | |
453 this.status = 0; | |
454 } | |
455 }; | |
456 | |
457 | |
458 this.newTrack = function(url) { | |
459 // Pull data from given URL into new audio buffer | |
460 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin' | |
461 | |
462 // Create the audioObject with ID of the new track length; | |
463 audioObjectId = this.audioObjects.length; | |
464 this.audioObjects[audioObjectId] = new audioObject(audioObjectId); | |
465 | |
466 // AudioObject will get track itself. | |
467 this.audioObjects[audioObjectId].constructTrack(url); | |
468 }; | |
469 | |
470 this.newTestPage = function() { | |
471 this.state = 0; | |
472 this.audioObjectsReady = false; | |
473 this.metric.reset(); | |
474 this.audioObjects = []; | |
475 }; | |
476 | |
477 this.checkAllPlayed = function() { | |
478 arr = []; | |
479 for (var id=0; id<this.audioObjects.length; id++) { | |
480 if (this.audioObjects[id].played == false) { | |
481 arr.push(this.audioObjects[id].id); | |
482 } | |
483 } | |
484 return arr; | |
485 }; | |
486 | |
487 this.checkAllReady = function() { | |
488 var ready = true; | |
489 for (var i=0; i<this.audioObjects.length; i++) { | |
490 if (this.audioObjects[i].state == 0) { | |
491 // Track not ready | |
492 console.log('WAIT -- audioObject '+i+' not ready yet!'); | |
493 ready = false; | |
494 }; | |
495 } | |
496 if (ready == false) { | |
497 var holder = document.getElementById('testWaitIndicator'); | |
498 holder.style.visibility = "visible"; | |
499 setInterval(function() { | |
500 document.getElementById('testWaitIndicator').style.visibility = "hidden"; | |
501 }, 10000); | |
502 } | |
503 return ready; | |
504 }; | |
505 | |
506 } | |
507 | |
508 function audioObject(id) { | |
509 // The main buffer object with common control nodes to the AudioEngine | |
510 | |
511 this.id = id; | |
512 this.state = 0; // 0 - no data, 1 - ready | |
513 this.url = null; // Hold the URL given for the output back to the results. | |
514 this.metric = new metricTracker(); | |
515 | |
516 this.played = false; | |
517 | |
518 // Create a buffer and external gain control to allow internal patching of effects and volume leveling. | |
519 this.bufferNode = undefined; | |
520 this.outputGain = audioContext.createGain(); | |
521 | |
522 // Default output gain to be zero | |
523 this.outputGain.gain.value = 0.0; | |
524 | |
525 // Connect buffer to the audio graph | |
526 this.outputGain.connect(audioEngineContext.outputGain); | |
527 | |
528 // the audiobuffer is not designed for multi-start playback | |
529 // When stopeed, the buffer node is deleted and recreated with the stored buffer. | |
530 this.buffer; | |
531 | |
532 this.loopStart = function() { | |
533 this.outputGain.gain.value = 1.0; | |
534 this.metric.startListening(audioEngineContext.timer.getTestTime()); | |
535 } | |
536 | |
537 this.loopStop = function() { | |
538 if (this.outputGain.gain.value != 0.0) { | |
539 this.outputGain.gain.value = 0.0; | |
540 this.metric.stopListening(audioEngineContext.timer.getTestTime()); | |
541 } | |
542 } | |
543 | |
544 this.play = function(startTime) { | |
545 this.bufferNode = audioContext.createBufferSource(); | |
546 this.bufferNode.owner = this; | |
547 this.bufferNode.connect(this.outputGain); | |
548 this.bufferNode.buffer = this.buffer; | |
549 this.bufferNode.loop = audioEngineContext.loopPlayback; | |
550 this.bufferNode.onended = function() { | |
551 this.owner.metric.stopListening(audioEngineContext.timer.getTestTime()); | |
552 }; | |
553 if (this.bufferNode.loop == false) { | |
554 this.metric.startListening(audioEngineContext.timer.getTestTime()); | |
555 } | |
556 this.bufferNode.start(startTime); | |
557 this.played = true; | |
558 }; | |
559 | |
560 this.stop = function() { | |
561 if (this.bufferNode != undefined) | |
562 { | |
563 this.bufferNode.stop(0); | |
564 this.bufferNode = undefined; | |
565 this.metric.stopListening(audioEngineContext.timer.getTestTime()); | |
566 } | |
567 }; | |
568 | |
569 this.constructTrack = function(url) { | |
570 var request = new XMLHttpRequest(); | |
571 this.url = url; | |
572 request.open('GET',url,true); | |
573 request.responseType = 'arraybuffer'; | |
574 | |
575 var audioObj = this; | |
576 | |
577 // Create callback to decode the data asynchronously | |
578 request.onloadend = function() { | |
579 audioContext.decodeAudioData(request.response, function(decodedData) { | |
580 audioObj.buffer = decodedData; | |
581 audioObj.state = 1; | |
582 }, function(){ | |
583 // Should only be called if there was an error, but sometimes gets called continuously | |
584 // Check here if the error is genuine | |
585 if (audioObj.state == 0 || audioObj.buffer == undefined) { | |
586 // Genuine error | |
587 console.log('FATAL - Error loading buffer on '+audioObj.id); | |
588 } | |
589 }); | |
590 }; | |
591 request.send(); | |
592 }; | |
593 | |
594 } | |
595 | |
596 function timer() | |
597 { | |
598 /* Timer object used in audioEngine to keep track of session timings | |
599 * Uses the timer of the web audio API, so sample resolution | |
600 */ | |
601 this.testStarted = false; | |
602 this.testStartTime = 0; | |
603 this.testDuration = 0; | |
604 this.minimumTestTime = 0; // No minimum test time | |
605 this.startTest = function() | |
606 { | |
607 if (this.testStarted == false) | |
608 { | |
609 this.testStartTime = audioContext.currentTime; | |
610 this.testStarted = true; | |
611 this.updateTestTime(); | |
612 audioEngineContext.metric.initialiseTest(); | |
613 } | |
614 }; | |
615 this.stopTest = function() | |
616 { | |
617 if (this.testStarted) | |
618 { | |
619 this.testDuration = this.getTestTime(); | |
620 this.testStarted = false; | |
621 } else { | |
622 console.log('ERR: Test tried to end before beginning'); | |
623 } | |
624 }; | |
625 this.updateTestTime = function() | |
626 { | |
627 if (this.testStarted) | |
628 { | |
629 this.testDuration = audioContext.currentTime - this.testStartTime; | |
630 } | |
631 }; | |
632 this.getTestTime = function() | |
633 { | |
634 this.updateTestTime(); | |
635 return this.testDuration; | |
636 }; | |
637 } | |
638 | |
639 function sessionMetrics(engine) | |
640 { | |
641 /* Used by audioEngine to link to audioObjects to minimise the timer call timers; | |
642 */ | |
643 this.engine = engine; | |
644 this.lastClicked = -1; | |
645 this.data = -1; | |
646 this.reset = function() { | |
647 this.lastClicked = -1; | |
648 this.data = -1; | |
649 }; | |
650 this.initialiseTest = function(){}; | |
651 } | |
652 | |
653 function metricTracker() | |
654 { | |
655 /* Custom object to track and collect metric data | |
656 * Used only inside the audioObjects object. | |
657 */ | |
658 | |
659 this.listenedTimer = 0; | |
660 this.listenStart = 0; | |
661 this.listenHold = false; | |
662 this.initialPosition = -1; | |
663 this.movementTracker = []; | |
664 this.wasListenedTo = false; | |
665 this.wasMoved = false; | |
666 this.hasComments = false; | |
667 | |
668 this.initialised = function(position) | |
669 { | |
670 if (this.initialPosition == -1) { | |
671 this.initialPosition = position; | |
672 } | |
673 }; | |
674 | |
675 this.moved = function(time,position) | |
676 { | |
677 this.wasMoved = true; | |
678 this.movementTracker[this.movementTracker.length] = [time, position]; | |
679 }; | |
680 | |
681 this.startListening = function(time) | |
682 { | |
683 if (this.listenHold == false) | |
684 { | |
685 this.wasListenedTo = true; | |
686 this.listenStart = time; | |
687 this.listenHold = true; | |
688 } | |
689 } | |
690 | |
691 this.stopListening = function(time) | |
692 { | |
693 if (this.listenHold == true) | |
694 { | |
695 this.listenedTimer += (time - this.listenStart); | |
696 this.listenStart = 0; | |
697 this.listenHold = false; | |
698 } | |
699 }; | |
700 } | |
701 | |
702 function randomiseOrder(input) | |
703 { | |
704 // This takes an array of information and randomises the order | |
705 var N = input.length; | |
706 var K = N; | |
707 var holdArr = []; | |
708 for (var n=0; n<N; n++) | |
709 { | |
710 // First pick a random number | |
711 var r = Math.random(); | |
712 // Multiply and floor by the number of elements left | |
713 r = Math.floor(r*input.length); | |
714 // Pick out that element and delete from the array | |
715 holdArr.push(input.splice(r,1)[0]); | |
716 } | |
717 return holdArr; | |
718 } | |
719 | |
720 function returnDateNode() | |
721 { | |
722 // Create an XML Node for the Date and Time a test was conducted | |
723 // Structure is | |
724 // <datetime> | |
725 // <date year="##" month="##" day="##">DD/MM/YY</date> | |
726 // <time hour="##" minute="##" sec="##">HH:MM:SS</time> | |
727 // </datetime> | |
728 var dateTime = new Date(); | |
729 var year = document.createAttribute('year'); | |
730 var month = document.createAttribute('month'); | |
731 var day = document.createAttribute('day'); | |
732 var hour = document.createAttribute('hour'); | |
733 var minute = document.createAttribute('minute'); | |
734 var secs = document.createAttribute('secs'); | |
735 | |
736 year.nodeValue = dateTime.getFullYear(); | |
737 month.nodeValue = dateTime.getMonth()+1; | |
738 day.nodeValue = dateTime.getDate(); | |
739 hour.nodeValue = dateTime.getHours(); | |
740 minute.nodeValue = dateTime.getMinutes(); | |
741 secs.nodeValue = dateTime.getSeconds(); | |
742 | |
743 var hold = document.createElement("datetime"); | |
744 var date = document.createElement("date"); | |
745 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue; | |
746 var time = document.createElement("time"); | |
747 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue; | |
748 | |
749 date.setAttributeNode(year); | |
750 date.setAttributeNode(month); | |
751 date.setAttributeNode(day); | |
752 time.setAttributeNode(hour); | |
753 time.setAttributeNode(minute); | |
754 time.setAttributeNode(secs); | |
755 | |
756 hold.appendChild(date); | |
757 hold.appendChild(time); | |
758 return hold | |
759 | |
760 } | |
761 | |
762 function testWaitIndicator() { | |
763 var hold = document.createElement("div"); | |
764 hold.id = "testWaitIndicator"; | |
765 hold.style.position = "absolute"; | |
766 hold.style.left = "100px"; | |
767 hold.style.top = "10px"; | |
768 hold.style.width = "500px"; | |
769 hold.style.height = "100px"; | |
770 hold.style.padding = "20px"; | |
771 hold.style.backgroundColor = "rgb(100,200,200)"; | |
772 hold.style.visibility = "hidden"; | |
773 var span = document.createElement("span"); | |
774 span.textContent = "Please wait! Elements still loading"; | |
775 hold.appendChild(span); | |
776 var body = document.getElementsByTagName('body')[0]; | |
777 body.appendChild(hold); | |
778 } | |
779 | |
780 var hidetestwait = null; |