nicholas@2224
|
1 /**
|
nicholas@2224
|
2 * core.js
|
nicholas@2224
|
3 *
|
nicholas@2224
|
4 * Main script to run, calls all other core functions and manages loading/store to backend.
|
nicholas@2224
|
5 * Also contains all global variables.
|
nicholas@2224
|
6 */
|
nicholas@2224
|
7
|
nicholas@2224
|
8 /* create the web audio API context and store in audioContext*/
|
nicholas@2224
|
9 var audioContext; // Hold the browser web audio API
|
nicholas@2224
|
10 var projectXML; // Hold the parsed setup XML
|
nicholas@2224
|
11 var schemaXSD; // Hold the parsed schema XSD
|
nicholas@2224
|
12 var specification;
|
nicholas@2224
|
13 var interfaceContext;
|
nicholas@2224
|
14 var storage;
|
nicholas@2224
|
15 var popup; // Hold the interfacePopup object
|
nicholas@2224
|
16 var testState;
|
nicholas@2224
|
17 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
|
nicholas@2224
|
18 var audioEngineContext; // The custome AudioEngine object
|
nicholas@2224
|
19 var projectReturn; // Hold the URL for the return
|
nicholas@2224
|
20
|
nicholas@2224
|
21
|
nicholas@2224
|
22 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
|
nicholas@2224
|
23 AudioBufferSourceNode.prototype.owner = undefined;
|
nicholas@2224
|
24 // Add a prototype to the bufferSourceNode to hold when the object was given a play command
|
nicholas@2224
|
25 AudioBufferSourceNode.prototype.playbackStartTime = undefined;
|
nicholas@2224
|
26 // Add a prototype to the bufferNode to hold the desired LINEAR gain
|
nicholas@2224
|
27 AudioBuffer.prototype.playbackGain = undefined;
|
nicholas@2224
|
28 // Add a prototype to the bufferNode to hold the computed LUFS loudness
|
nicholas@2224
|
29 AudioBuffer.prototype.lufs = undefined;
|
nicholas@2224
|
30
|
nicholas@2224
|
31 // Convert relative URLs into absolutes
|
nicholas@2224
|
32 function escapeHTML(s) {
|
nicholas@2224
|
33 return s.split('&').join('&').split('<').join('<').split('"').join('"');
|
nicholas@2224
|
34 }
|
nicholas@2224
|
35 function qualifyURL(url) {
|
nicholas@2224
|
36 var el= document.createElement('div');
|
nicholas@2224
|
37 el.innerHTML= '<a href="'+escapeHTML(url)+'">x</a>';
|
nicholas@2224
|
38 return el.firstChild.href;
|
nicholas@2224
|
39 }
|
nicholas@2224
|
40
|
nicholas@2224
|
41 // Firefox does not have an XMLDocument.prototype.getElementsByName
|
nicholas@2224
|
42 // and there is no searchAll style command, this custom function will
|
nicholas@2224
|
43 // search all children recusrively for the name. Used for XSD where all
|
nicholas@2224
|
44 // element nodes must have a name and therefore can pull the schema node
|
nicholas@2224
|
45 XMLDocument.prototype.getAllElementsByName = function(name)
|
nicholas@2224
|
46 {
|
nicholas@2224
|
47 name = String(name);
|
nicholas@2224
|
48 var selected = this.documentElement.getAllElementsByName(name);
|
nicholas@2224
|
49 return selected;
|
nicholas@2224
|
50 }
|
nicholas@2224
|
51
|
nicholas@2224
|
52 Element.prototype.getAllElementsByName = function(name)
|
nicholas@2224
|
53 {
|
nicholas@2224
|
54 name = String(name);
|
nicholas@2224
|
55 var selected = [];
|
nicholas@2224
|
56 var node = this.firstElementChild;
|
nicholas@2224
|
57 while(node != null)
|
nicholas@2224
|
58 {
|
nicholas@2224
|
59 if (node.getAttribute('name') == name)
|
nicholas@2224
|
60 {
|
nicholas@2224
|
61 selected.push(node);
|
nicholas@2224
|
62 }
|
nicholas@2224
|
63 if (node.childElementCount > 0)
|
nicholas@2224
|
64 {
|
nicholas@2224
|
65 selected = selected.concat(node.getAllElementsByName(name));
|
nicholas@2224
|
66 }
|
nicholas@2224
|
67 node = node.nextElementSibling;
|
nicholas@2224
|
68 }
|
nicholas@2224
|
69 return selected;
|
nicholas@2224
|
70 }
|
nicholas@2224
|
71
|
nicholas@2224
|
72 XMLDocument.prototype.getAllElementsByTagName = function(name)
|
nicholas@2224
|
73 {
|
nicholas@2224
|
74 name = String(name);
|
nicholas@2224
|
75 var selected = this.documentElement.getAllElementsByTagName(name);
|
nicholas@2224
|
76 return selected;
|
nicholas@2224
|
77 }
|
nicholas@2224
|
78
|
nicholas@2224
|
79 Element.prototype.getAllElementsByTagName = function(name)
|
nicholas@2224
|
80 {
|
nicholas@2224
|
81 name = String(name);
|
nicholas@2224
|
82 var selected = [];
|
nicholas@2224
|
83 var node = this.firstElementChild;
|
nicholas@2224
|
84 while(node != null)
|
nicholas@2224
|
85 {
|
nicholas@2224
|
86 if (node.nodeName == name)
|
nicholas@2224
|
87 {
|
nicholas@2224
|
88 selected.push(node);
|
nicholas@2224
|
89 }
|
nicholas@2224
|
90 if (node.childElementCount > 0)
|
nicholas@2224
|
91 {
|
nicholas@2224
|
92 selected = selected.concat(node.getAllElementsByTagName(name));
|
nicholas@2224
|
93 }
|
nicholas@2224
|
94 node = node.nextElementSibling;
|
nicholas@2224
|
95 }
|
nicholas@2224
|
96 return selected;
|
nicholas@2224
|
97 }
|
nicholas@2224
|
98
|
nicholas@2224
|
99 // Firefox does not have an XMLDocument.prototype.getElementsByName
|
nicholas@2224
|
100 if (typeof XMLDocument.prototype.getElementsByName != "function") {
|
nicholas@2224
|
101 XMLDocument.prototype.getElementsByName = function(name)
|
nicholas@2224
|
102 {
|
nicholas@2224
|
103 name = String(name);
|
nicholas@2224
|
104 var node = this.documentElement.firstElementChild;
|
nicholas@2224
|
105 var selected = [];
|
nicholas@2224
|
106 while(node != null)
|
nicholas@2224
|
107 {
|
nicholas@2224
|
108 if (node.getAttribute('name') == name)
|
nicholas@2224
|
109 {
|
nicholas@2224
|
110 selected.push(node);
|
nicholas@2224
|
111 }
|
nicholas@2224
|
112 node = node.nextElementSibling;
|
nicholas@2224
|
113 }
|
nicholas@2224
|
114 return selected;
|
nicholas@2224
|
115 }
|
nicholas@2224
|
116 }
|
nicholas@2224
|
117
|
nicholas@2224
|
118 window.onload = function() {
|
nicholas@2224
|
119 // Function called once the browser has loaded all files.
|
nicholas@2224
|
120 // This should perform any initial commands such as structure / loading documents
|
nicholas@2224
|
121
|
nicholas@2224
|
122 // Create a web audio API context
|
nicholas@2224
|
123 // Fixed for cross-browser support
|
nicholas@2224
|
124 var AudioContext = window.AudioContext || window.webkitAudioContext;
|
nicholas@2224
|
125 audioContext = new AudioContext;
|
nicholas@2224
|
126
|
nicholas@2224
|
127 // Create test state
|
nicholas@2224
|
128 testState = new stateMachine();
|
nicholas@2224
|
129
|
nicholas@2224
|
130 // Create the popup interface object
|
nicholas@2224
|
131 popup = new interfacePopup();
|
nicholas@2224
|
132
|
nicholas@2224
|
133 // Create the specification object
|
nicholas@2224
|
134 specification = new Specification();
|
nicholas@2224
|
135
|
nicholas@2224
|
136 // Create the interface object
|
nicholas@2224
|
137 interfaceContext = new Interface(specification);
|
nicholas@2224
|
138
|
nicholas@2224
|
139 // Create the storage object
|
nicholas@2224
|
140 storage = new Storage();
|
nicholas@2224
|
141 // Define window callbacks for interface
|
nicholas@2224
|
142 window.onresize = function(event){interfaceContext.resizeWindow(event);};
|
nicholas@2224
|
143 };
|
nicholas@2224
|
144
|
nicholas@2224
|
145 function loadProjectSpec(url) {
|
nicholas@2224
|
146 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
|
nicholas@2224
|
147 // If url is null, request client to upload project XML document
|
nicholas@2224
|
148 var xmlhttp = new XMLHttpRequest();
|
nicholas@2224
|
149 xmlhttp.open("GET",'xml/test-schema.xsd',true);
|
nicholas@2224
|
150 xmlhttp.onload = function()
|
nicholas@2224
|
151 {
|
nicholas@2224
|
152 schemaXSD = xmlhttp.response;
|
nicholas@2224
|
153 var parse = new DOMParser();
|
nicholas@2224
|
154 specification.schema = parse.parseFromString(xmlhttp.response,'text/xml');
|
nicholas@2224
|
155 var r = new XMLHttpRequest();
|
nicholas@2224
|
156 r.open('GET',url,true);
|
nicholas@2224
|
157 r.onload = function() {
|
nicholas@2224
|
158 loadProjectSpecCallback(r.response);
|
nicholas@2224
|
159 };
|
nicholas@2224
|
160 r.onerror = function() {
|
nicholas@2224
|
161 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2224
|
162 var msg = document.createElement("h3");
|
nicholas@2224
|
163 msg.textContent = "FATAL ERROR";
|
nicholas@2224
|
164 var span = document.createElement("p");
|
nicholas@2224
|
165 span.textContent = "There was an error when loading your XML file. Please check your path in the URL. After the path to this page, there should be '?url=path/to/your/file.xml'. Check the spelling of your filename as well. If you are still having issues, check the log of the python server or your webserver distribution for 404 codes for your file.";
|
nicholas@2224
|
166 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2224
|
167 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
168 }
|
nicholas@2224
|
169 r.send();
|
nicholas@2224
|
170 };
|
nicholas@2224
|
171 xmlhttp.send();
|
nicholas@2224
|
172 };
|
nicholas@2224
|
173
|
nicholas@2224
|
174 function loadProjectSpecCallback(response) {
|
nicholas@2224
|
175 // Function called after asynchronous download of XML project specification
|
nicholas@2224
|
176 //var decode = $.parseXML(response);
|
nicholas@2224
|
177 //projectXML = $(decode);
|
nicholas@2224
|
178
|
nicholas@2224
|
179 // Check if XML is new or a resumption
|
nicholas@2224
|
180 var parse = new DOMParser();
|
nicholas@2224
|
181 var responseDocument = parse.parseFromString(response,'text/xml');
|
nicholas@2224
|
182 var errorNode = responseDocument.getElementsByTagName('parsererror');
|
nicholas@2224
|
183 if (errorNode.length >= 1)
|
nicholas@2224
|
184 {
|
nicholas@2224
|
185 var msg = document.createElement("h3");
|
nicholas@2224
|
186 msg.textContent = "FATAL ERROR";
|
nicholas@2224
|
187 var span = document.createElement("span");
|
nicholas@2224
|
188 span.textContent = "The XML parser returned the following errors when decoding your XML file";
|
nicholas@2224
|
189 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2224
|
190 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2224
|
191 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
192 document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
|
nicholas@2224
|
193 return;
|
nicholas@2224
|
194 }
|
nicholas@2247
|
195 if (responseDocument == undefined || responseDocument.firstChild == undefined) {
|
nicholas@2224
|
196 var msg = document.createElement("h3");
|
nicholas@2224
|
197 msg.textContent = "FATAL ERROR";
|
nicholas@2224
|
198 var span = document.createElement("span");
|
nicholas@2224
|
199 span.textContent = "The project XML was not decoded properly, try refreshing your browser and clearing caches. If the problem persists, contact the test creator.";
|
nicholas@2224
|
200 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2224
|
201 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2224
|
202 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
203 return;
|
nicholas@2224
|
204 }
|
nicholas@2247
|
205 if (responseDocument.firstChild.nodeName == "waet") {
|
nicholas@2224
|
206 // document is a specification
|
nicholas@2224
|
207
|
nicholas@2224
|
208 // Perform XML schema validation
|
nicholas@2224
|
209 var Module = {
|
nicholas@2224
|
210 xml: response,
|
nicholas@2224
|
211 schema: schemaXSD,
|
nicholas@2224
|
212 arguments:["--noout", "--schema", 'test-schema.xsd','document.xml']
|
nicholas@2224
|
213 };
|
nicholas@2224
|
214 projectXML = responseDocument;
|
nicholas@2224
|
215 var xmllint = validateXML(Module);
|
nicholas@2224
|
216 console.log(xmllint);
|
nicholas@2224
|
217 if(xmllint != 'document.xml validates\n')
|
nicholas@2224
|
218 {
|
nicholas@2224
|
219 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2224
|
220 var msg = document.createElement("h3");
|
nicholas@2224
|
221 msg.textContent = "FATAL ERROR";
|
nicholas@2224
|
222 var span = document.createElement("h4");
|
nicholas@2224
|
223 span.textContent = "The XML validator returned the following errors when decoding your XML file";
|
nicholas@2224
|
224 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2224
|
225 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
226 xmllint = xmllint.split('\n');
|
nicholas@2224
|
227 for (var i in xmllint)
|
nicholas@2224
|
228 {
|
nicholas@2224
|
229 document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
|
nicholas@2224
|
230 var span = document.createElement("span");
|
nicholas@2224
|
231 span.textContent = xmllint[i];
|
nicholas@2224
|
232 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
233 }
|
nicholas@2224
|
234 return;
|
nicholas@2224
|
235 }
|
nicholas@2224
|
236 // Build the specification
|
nicholas@2224
|
237 specification.decode(projectXML);
|
nicholas@2224
|
238 // Generate the session-key
|
nicholas@2224
|
239 storage.initialise();
|
nicholas@2224
|
240
|
nicholas@2247
|
241 } else if (responseDocument.firstChild.nodeName == "waetresult") {
|
nicholas@2224
|
242 // document is a result
|
nicholas@2224
|
243 projectXML = document.implementation.createDocument(null,"waet");
|
nicholas@2294
|
244 projectXML.firstChild.appendChild(responseDocument.getElementsByTagName('waet')[0].getElementsByTagName("setup")[0].cloneNode(true));
|
nicholas@2294
|
245 var child = responseDocument.firstChild.firstChild;
|
nicholas@2224
|
246 while (child != null) {
|
nicholas@2224
|
247 if (child.nodeName == "survey") {
|
nicholas@2224
|
248 // One of the global survey elements
|
nicholas@2224
|
249 if (child.getAttribute("state") == "complete") {
|
nicholas@2224
|
250 // We need to remove this survey from <setup>
|
nicholas@2224
|
251 var location = child.getAttribute("location");
|
nicholas@2224
|
252 var globalSurveys = projectXML.getElementsByTagName("setup")[0].getElementsByTagName("survey")[0];
|
nicholas@2224
|
253 while(globalSurveys != null) {
|
nicholas@2224
|
254 if (location == "pre" || location == "before") {
|
nicholas@2224
|
255 if (globalSurveys.getAttribute("location") == "pre" || globalSurveys.getAttribute("location") == "before") {
|
nicholas@2224
|
256 projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
|
nicholas@2224
|
257 break;
|
nicholas@2224
|
258 }
|
nicholas@2224
|
259 } else {
|
nicholas@2224
|
260 if (globalSurveys.getAttribute("location") == "post" || globalSurveys.getAttribute("location") == "after") {
|
nicholas@2224
|
261 projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
|
nicholas@2224
|
262 break;
|
nicholas@2224
|
263 }
|
nicholas@2224
|
264 }
|
nicholas@2224
|
265 globalSurveys = globalSurveys.nextElementSibling;
|
nicholas@2224
|
266 }
|
nicholas@2224
|
267 } else {
|
nicholas@2224
|
268 // We need to complete this, so it must be regenerated by store
|
nicholas@2224
|
269 var copy = child;
|
nicholas@2224
|
270 child = child.previousElementSibling;
|
nicholas@2294
|
271 responseDocument.firstChild.removeChild(copy);
|
nicholas@2224
|
272 }
|
nicholas@2224
|
273 } else if (child.nodeName == "page") {
|
nicholas@2224
|
274 if (child.getAttribute("state") == "empty") {
|
nicholas@2224
|
275 // We need to complete this page
|
nicholas@2294
|
276 projectXML.firstChild.appendChild(responseDocument.getElementById(child.getAttribute("ref")).cloneNode(true));
|
nicholas@2224
|
277 var copy = child;
|
nicholas@2224
|
278 child = child.previousElementSibling;
|
nicholas@2294
|
279 responseDocument.firstChild.removeChild(copy);
|
nicholas@2224
|
280 }
|
nicholas@2224
|
281 }
|
nicholas@2224
|
282 child = child.nextElementSibling;
|
nicholas@2224
|
283 }
|
nicholas@2224
|
284 // Build the specification
|
nicholas@2224
|
285 specification.decode(projectXML);
|
nicholas@2224
|
286 // Use the original
|
nicholas@2224
|
287 storage.initialise(responseDocument);
|
nicholas@2224
|
288 }
|
nicholas@2224
|
289 /// CHECK FOR SAMPLE RATE COMPATIBILITY
|
nicholas@2224
|
290 if (specification.sampleRate != undefined) {
|
nicholas@2224
|
291 if (Number(specification.sampleRate) != audioContext.sampleRate) {
|
nicholas@2224
|
292 var errStr = 'Sample rates do not match! Requested '+Number(specification.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
|
nicholas@2224
|
293 alert(errStr);
|
nicholas@2224
|
294 return;
|
nicholas@2224
|
295 }
|
nicholas@2224
|
296 }
|
nicholas@2224
|
297
|
nicholas@2224
|
298 // Detect the interface to use and load the relevant javascripts.
|
nicholas@2224
|
299 var interfaceJS = document.createElement('script');
|
nicholas@2224
|
300 interfaceJS.setAttribute("type","text/javascript");
|
nicholas@2224
|
301 switch(specification.interface)
|
nicholas@2224
|
302 {
|
nicholas@2224
|
303 case "APE":
|
nicholas@2224
|
304 interfaceJS.setAttribute("src","interfaces/ape.js");
|
nicholas@2224
|
305
|
nicholas@2224
|
306 // APE comes with a css file
|
nicholas@2224
|
307 var css = document.createElement('link');
|
nicholas@2224
|
308 css.rel = 'stylesheet';
|
nicholas@2224
|
309 css.type = 'text/css';
|
nicholas@2224
|
310 css.href = 'interfaces/ape.css';
|
nicholas@2224
|
311
|
nicholas@2224
|
312 document.getElementsByTagName("head")[0].appendChild(css);
|
nicholas@2224
|
313 break;
|
nicholas@2224
|
314
|
nicholas@2224
|
315 case "MUSHRA":
|
nicholas@2224
|
316 interfaceJS.setAttribute("src","interfaces/mushra.js");
|
nicholas@2224
|
317
|
nicholas@2224
|
318 // MUSHRA comes with a css file
|
nicholas@2224
|
319 var css = document.createElement('link');
|
nicholas@2224
|
320 css.rel = 'stylesheet';
|
nicholas@2224
|
321 css.type = 'text/css';
|
nicholas@2224
|
322 css.href = 'interfaces/mushra.css';
|
nicholas@2224
|
323
|
nicholas@2224
|
324 document.getElementsByTagName("head")[0].appendChild(css);
|
nicholas@2224
|
325 break;
|
nicholas@2224
|
326
|
nicholas@2224
|
327 case "AB":
|
nicholas@2224
|
328 interfaceJS.setAttribute("src","interfaces/AB.js");
|
nicholas@2224
|
329
|
nicholas@2224
|
330 // AB comes with a css file
|
nicholas@2224
|
331 var css = document.createElement('link');
|
nicholas@2224
|
332 css.rel = 'stylesheet';
|
nicholas@2224
|
333 css.type = 'text/css';
|
nicholas@2224
|
334 css.href = 'interfaces/AB.css';
|
nicholas@2224
|
335
|
nicholas@2224
|
336 document.getElementsByTagName("head")[0].appendChild(css);
|
nicholas@2224
|
337 break;
|
nicholas@2224
|
338
|
nicholas@2224
|
339 case "ABX":
|
nicholas@2224
|
340 interfaceJS.setAttribute("src","interfaces/ABX.js");
|
nicholas@2224
|
341
|
nicholas@2224
|
342 // AB comes with a css file
|
nicholas@2224
|
343 var css = document.createElement('link');
|
nicholas@2224
|
344 css.rel = 'stylesheet';
|
nicholas@2224
|
345 css.type = 'text/css';
|
nicholas@2224
|
346 css.href = 'interfaces/ABX.css';
|
nicholas@2224
|
347
|
nicholas@2224
|
348 document.getElementsByTagName("head")[0].appendChild(css);
|
nicholas@2224
|
349 break;
|
nicholas@2224
|
350
|
nicholas@2224
|
351 case "Bipolar":
|
nicholas@2224
|
352 case "ACR":
|
nicholas@2224
|
353 case "DCR":
|
nicholas@2224
|
354 case "CCR":
|
nicholas@2224
|
355 case "ABC":
|
nicholas@2224
|
356 // Above enumerate to horizontal sliders
|
nicholas@2224
|
357 interfaceJS.setAttribute("src","interfaces/horizontal-sliders.js");
|
nicholas@2224
|
358
|
nicholas@2224
|
359 // horizontal-sliders comes with a css file
|
nicholas@2224
|
360 var css = document.createElement('link');
|
nicholas@2224
|
361 css.rel = 'stylesheet';
|
nicholas@2224
|
362 css.type = 'text/css';
|
nicholas@2224
|
363 css.href = 'interfaces/horizontal-sliders.css';
|
nicholas@2224
|
364
|
nicholas@2224
|
365 document.getElementsByTagName("head")[0].appendChild(css);
|
nicholas@2224
|
366 break;
|
nicholas@2224
|
367 case "discrete":
|
nicholas@2224
|
368 case "likert":
|
nicholas@2224
|
369 // Above enumerate to horizontal discrete radios
|
nicholas@2224
|
370 interfaceJS.setAttribute("src","interfaces/discrete.js");
|
nicholas@2224
|
371
|
nicholas@2224
|
372 // horizontal-sliders comes with a css file
|
nicholas@2224
|
373 var css = document.createElement('link');
|
nicholas@2224
|
374 css.rel = 'stylesheet';
|
nicholas@2224
|
375 css.type = 'text/css';
|
nicholas@2224
|
376 css.href = 'interfaces/discrete.css';
|
nicholas@2224
|
377
|
nicholas@2224
|
378 document.getElementsByTagName("head")[0].appendChild(css);
|
nicholas@2224
|
379 break;
|
nicholas@2224
|
380 }
|
nicholas@2224
|
381 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
|
nicholas@2224
|
382
|
nicholas@2224
|
383 // Create the audio engine object
|
nicholas@2224
|
384 audioEngineContext = new AudioEngine(specification);
|
nicholas@2224
|
385 }
|
nicholas@2224
|
386
|
nicholas@2224
|
387 function createProjectSave(destURL) {
|
nicholas@2224
|
388 // Clear the window.onbeforeunload
|
nicholas@2224
|
389 window.onbeforeunload = null;
|
nicholas@2224
|
390 // Save the data from interface into XML and send to destURL
|
nicholas@2224
|
391 // If destURL is null then download XML in client
|
nicholas@2224
|
392 // Now time to render file locally
|
nicholas@2224
|
393 var xmlDoc = interfaceXMLSave();
|
nicholas@2224
|
394 var parent = document.createElement("div");
|
nicholas@2224
|
395 parent.appendChild(xmlDoc);
|
nicholas@2224
|
396 var file = [parent.innerHTML];
|
nicholas@2224
|
397 if (destURL == "local") {
|
nicholas@2224
|
398 var bb = new Blob(file,{type : 'application/xml'});
|
nicholas@2224
|
399 var dnlk = window.URL.createObjectURL(bb);
|
nicholas@2224
|
400 var a = document.createElement("a");
|
nicholas@2224
|
401 a.hidden = '';
|
nicholas@2224
|
402 a.href = dnlk;
|
nicholas@2224
|
403 a.download = "save.xml";
|
nicholas@2224
|
404 a.textContent = "Save File";
|
nicholas@2224
|
405
|
nicholas@2224
|
406 popup.showPopup();
|
giuliomoro@2299
|
407 popup.popupContent.innerHTML = "<span>Please save the file below to give to your test supervisor</span><br>";
|
nicholas@2224
|
408 popup.popupContent.appendChild(a);
|
nicholas@2224
|
409 } else {
|
nicholas@2224
|
410 var xmlhttp = new XMLHttpRequest;
|
nicholas@2302
|
411 var returnURL = "";
|
nicholas@2302
|
412 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
413 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
414 returnURL = specification.projectReturn;
|
nicholas@2302
|
415 }
|
nicholas@2302
|
416 }
|
nicholas@2302
|
417 xmlhttp.open("POST",returnURL+"php/save.php?key="+storage.SessionKey.key,true);
|
nicholas@2224
|
418 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2224
|
419 xmlhttp.onerror = function(){
|
nicholas@2224
|
420 console.log('Error saving file to server! Presenting download locally');
|
nicholas@2224
|
421 createProjectSave("local");
|
nicholas@2224
|
422 };
|
nicholas@2224
|
423 xmlhttp.onload = function() {
|
nicholas@2224
|
424 console.log(xmlhttp);
|
nicholas@2224
|
425 if (this.status >= 300) {
|
nicholas@2224
|
426 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
427 createProjectSave("local");
|
nicholas@2224
|
428 } else {
|
nicholas@2224
|
429 var parser = new DOMParser();
|
nicholas@2224
|
430 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
431 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
432 if (response.getAttribute("state") == "OK") {
|
nicholas@2303
|
433 window.onbeforeunload = undefined;
|
nicholas@2224
|
434 var file = response.getElementsByTagName("file")[0];
|
nicholas@2224
|
435 console.log("Save: OK, written "+file.getAttribute("bytes")+"B");
|
nicholas@2303
|
436 if (typeof specification.returnURL == "string") {
|
nicholas@2303
|
437 window.location = returnURL;
|
nicholas@2303
|
438 } else {
|
nicholas@2303
|
439 popup.popupContent.textContent = specification.exitText;
|
nicholas@2303
|
440 }
|
nicholas@2224
|
441 } else {
|
nicholas@2224
|
442 var message = response.getElementsByTagName("message");
|
nicholas@2224
|
443 console.log("Save: Error! "+message.textContent);
|
nicholas@2224
|
444 createProjectSave("local");
|
nicholas@2224
|
445 }
|
nicholas@2224
|
446 }
|
nicholas@2224
|
447 };
|
nicholas@2224
|
448 xmlhttp.send(file);
|
nicholas@2224
|
449 popup.showPopup();
|
nicholas@2224
|
450 popup.popupContent.innerHTML = null;
|
nicholas@2224
|
451 popup.popupContent.textContent = "Submitting. Please Wait";
|
nicholas@2224
|
452 popup.hideNextButton();
|
nicholas@2224
|
453 popup.hidePreviousButton();
|
nicholas@2224
|
454 }
|
nicholas@2224
|
455 }
|
nicholas@2224
|
456
|
nicholas@2224
|
457 function errorSessionDump(msg){
|
nicholas@2224
|
458 // Create the partial interface XML save
|
nicholas@2224
|
459 // Include error node with message on why the dump occured
|
nicholas@2224
|
460 popup.showPopup();
|
nicholas@2224
|
461 popup.popupContent.innerHTML = null;
|
nicholas@2224
|
462 var err = document.createElement('error');
|
nicholas@2224
|
463 var parent = document.createElement("div");
|
nicholas@2224
|
464 if (typeof msg === "object")
|
nicholas@2224
|
465 {
|
nicholas@2224
|
466 err.appendChild(msg);
|
nicholas@2224
|
467 popup.popupContent.appendChild(msg);
|
nicholas@2224
|
468
|
nicholas@2224
|
469 } else {
|
nicholas@2224
|
470 err.textContent = msg;
|
nicholas@2224
|
471 popup.popupContent.innerHTML = "ERROR : "+msg;
|
nicholas@2224
|
472 }
|
nicholas@2224
|
473 var xmlDoc = interfaceXMLSave();
|
nicholas@2224
|
474 xmlDoc.appendChild(err);
|
nicholas@2224
|
475 parent.appendChild(xmlDoc);
|
nicholas@2224
|
476 var file = [parent.innerHTML];
|
nicholas@2224
|
477 var bb = new Blob(file,{type : 'application/xml'});
|
nicholas@2224
|
478 var dnlk = window.URL.createObjectURL(bb);
|
nicholas@2224
|
479 var a = document.createElement("a");
|
nicholas@2224
|
480 a.hidden = '';
|
nicholas@2224
|
481 a.href = dnlk;
|
nicholas@2224
|
482 a.download = "save.xml";
|
nicholas@2224
|
483 a.textContent = "Save File";
|
nicholas@2224
|
484
|
nicholas@2224
|
485
|
nicholas@2224
|
486
|
nicholas@2224
|
487 popup.popupContent.appendChild(a);
|
nicholas@2224
|
488 }
|
nicholas@2224
|
489
|
nicholas@2224
|
490 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
|
nicholas@2224
|
491 function interfaceXMLSave(){
|
nicholas@2224
|
492 // Create the XML string to be exported with results
|
nicholas@2224
|
493 return storage.finish();
|
nicholas@2224
|
494 }
|
nicholas@2224
|
495
|
nicholas@2224
|
496 function linearToDecibel(gain)
|
nicholas@2224
|
497 {
|
nicholas@2224
|
498 return 20.0*Math.log10(gain);
|
nicholas@2224
|
499 }
|
nicholas@2224
|
500
|
nicholas@2224
|
501 function decibelToLinear(gain)
|
nicholas@2224
|
502 {
|
nicholas@2224
|
503 return Math.pow(10,gain/20.0);
|
nicholas@2224
|
504 }
|
nicholas@2224
|
505
|
nicholas@2224
|
506 function secondsToSamples(time,fs) {
|
nicholas@2224
|
507 return Math.round(time*fs);
|
nicholas@2224
|
508 }
|
nicholas@2224
|
509
|
nicholas@2224
|
510 function samplesToSeconds(samples,fs) {
|
nicholas@2224
|
511 return samples / fs;
|
nicholas@2224
|
512 }
|
nicholas@2224
|
513
|
nicholas@2224
|
514 function randomString(length) {
|
nicholas@2224
|
515 return Math.round((Math.pow(36, length + 1) - Math.random() * Math.pow(36, length))).toString(36).slice(1);
|
nicholas@2224
|
516 }
|
nicholas@2224
|
517
|
nicholas@2224
|
518 function randomiseOrder(input)
|
nicholas@2224
|
519 {
|
nicholas@2224
|
520 // This takes an array of information and randomises the order
|
nicholas@2224
|
521 var N = input.length;
|
nicholas@2224
|
522
|
nicholas@2224
|
523 var inputSequence = []; // For safety purposes: keep track of randomisation
|
nicholas@2224
|
524 for (var counter = 0; counter < N; ++counter)
|
nicholas@2224
|
525 inputSequence.push(counter) // Fill array
|
nicholas@2224
|
526 var inputSequenceClone = inputSequence.slice(0);
|
nicholas@2224
|
527
|
nicholas@2224
|
528 var holdArr = [];
|
nicholas@2224
|
529 var outputSequence = [];
|
nicholas@2224
|
530 for (var n=0; n<N; n++)
|
nicholas@2224
|
531 {
|
nicholas@2224
|
532 // First pick a random number
|
nicholas@2224
|
533 var r = Math.random();
|
nicholas@2224
|
534 // Multiply and floor by the number of elements left
|
nicholas@2224
|
535 r = Math.floor(r*input.length);
|
nicholas@2224
|
536 // Pick out that element and delete from the array
|
nicholas@2224
|
537 holdArr.push(input.splice(r,1)[0]);
|
nicholas@2224
|
538 // Do the same with sequence
|
nicholas@2224
|
539 outputSequence.push(inputSequence.splice(r,1)[0]);
|
nicholas@2224
|
540 }
|
nicholas@2224
|
541 console.log(inputSequenceClone.toString()); // print original array to console
|
nicholas@2224
|
542 console.log(outputSequence.toString()); // print randomised array to console
|
nicholas@2224
|
543 return holdArr;
|
nicholas@2224
|
544 }
|
nicholas@2224
|
545
|
nicholas@2224
|
546 function randomSubArray(array,num) {
|
nicholas@2224
|
547 if (num > array.length) {
|
nicholas@2224
|
548 num = array.length;
|
nicholas@2224
|
549 }
|
nicholas@2224
|
550 var ret = [];
|
nicholas@2224
|
551 while (num > 0) {
|
nicholas@2224
|
552 var index = Math.floor(Math.random() * array.length);
|
nicholas@2224
|
553 ret.push( array.splice(index,1)[0] );
|
nicholas@2224
|
554 num--;
|
nicholas@2224
|
555 }
|
nicholas@2224
|
556 return ret;
|
nicholas@2224
|
557 }
|
nicholas@2224
|
558
|
nicholas@2224
|
559 function interfacePopup() {
|
nicholas@2224
|
560 // Creates an object to manage the popup
|
nicholas@2224
|
561 this.popup = null;
|
nicholas@2224
|
562 this.popupContent = null;
|
nicholas@2224
|
563 this.popupTitle = null;
|
nicholas@2224
|
564 this.popupResponse = null;
|
nicholas@2224
|
565 this.buttonProceed = null;
|
nicholas@2224
|
566 this.buttonPrevious = null;
|
nicholas@2224
|
567 this.popupOptions = null;
|
nicholas@2224
|
568 this.currentIndex = null;
|
nicholas@2224
|
569 this.node = null;
|
nicholas@2224
|
570 this.store = null;
|
nicholas@2224
|
571 $(window).keypress(function(e){
|
nicholas@2224
|
572 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
|
nicholas@2224
|
573 {
|
nicholas@2224
|
574 console.log(e);
|
nicholas@2224
|
575 popup.buttonProceed.onclick();
|
nicholas@2224
|
576 e.preventDefault();
|
nicholas@2224
|
577 }
|
nicholas@2224
|
578 });
|
nicholas@2224
|
579
|
nicholas@2224
|
580 this.createPopup = function(){
|
nicholas@2224
|
581 // Create popup window interface
|
nicholas@2224
|
582 var insertPoint = document.getElementById("topLevelBody");
|
nicholas@2224
|
583
|
nicholas@2224
|
584 this.popup = document.getElementById('popupHolder');
|
nicholas@2224
|
585 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
|
nicholas@2224
|
586 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
|
nicholas@2224
|
587
|
nicholas@2224
|
588 this.popupContent = document.getElementById('popupContent');
|
nicholas@2224
|
589
|
nicholas@2224
|
590 this.popupTitle = document.getElementById('popupTitle');
|
nicholas@2224
|
591
|
nicholas@2224
|
592 this.popupResponse = document.getElementById('popupResponse');
|
nicholas@2224
|
593
|
nicholas@2224
|
594 this.buttonProceed = document.getElementById('popup-proceed');
|
nicholas@2224
|
595 this.buttonProceed.onclick = function(){popup.proceedClicked();};
|
nicholas@2224
|
596
|
nicholas@2224
|
597 this.buttonPrevious = document.getElementById('popup-previous');
|
nicholas@2224
|
598 this.buttonPrevious.onclick = function(){popup.previousClick();};
|
nicholas@2224
|
599
|
nicholas@2224
|
600 this.hidePopup();
|
nicholas@2224
|
601 this.popup.style.visibility = 'hidden';
|
nicholas@2224
|
602 };
|
nicholas@2224
|
603
|
nicholas@2224
|
604 this.showPopup = function(){
|
nicholas@2224
|
605 if (this.popup == null) {
|
nicholas@2224
|
606 this.createPopup();
|
nicholas@2224
|
607 }
|
nicholas@2224
|
608 this.popup.style.visibility = 'visible';
|
nicholas@2224
|
609 var blank = document.getElementsByClassName('testHalt')[0];
|
nicholas@2224
|
610 blank.style.visibility = 'visible';
|
nicholas@2224
|
611 this.popupResponse.style.left="0%";
|
nicholas@2224
|
612 };
|
nicholas@2224
|
613
|
nicholas@2224
|
614 this.hidePopup = function(){
|
nicholas@2224
|
615 if (this.popup) {
|
nicholas@2224
|
616 this.popup.style.visibility = 'hidden';
|
nicholas@2224
|
617 var blank = document.getElementsByClassName('testHalt')[0];
|
nicholas@2224
|
618 blank.style.visibility = 'hidden';
|
nicholas@2224
|
619 this.buttonPrevious.style.visibility = 'inherit';
|
nicholas@2224
|
620 }
|
nicholas@2224
|
621 };
|
nicholas@2224
|
622
|
nicholas@2224
|
623 this.postNode = function() {
|
nicholas@2224
|
624 // This will take the node from the popupOptions and display it
|
nicholas@2224
|
625 var node = this.popupOptions[this.currentIndex];
|
nicholas@2224
|
626 this.popupResponse.innerHTML = null;
|
nicholas@2224
|
627 this.popupTitle.textContent = node.specification.statement;
|
nicholas@2224
|
628 if (node.specification.type == 'question') {
|
nicholas@2224
|
629 var textArea = document.createElement('textarea');
|
nicholas@2224
|
630 switch (node.specification.boxsize) {
|
nicholas@2224
|
631 case 'small':
|
nicholas@2224
|
632 textArea.cols = "20";
|
nicholas@2224
|
633 textArea.rows = "1";
|
nicholas@2224
|
634 break;
|
nicholas@2224
|
635 case 'normal':
|
nicholas@2224
|
636 textArea.cols = "30";
|
nicholas@2224
|
637 textArea.rows = "2";
|
nicholas@2224
|
638 break;
|
nicholas@2224
|
639 case 'large':
|
nicholas@2224
|
640 textArea.cols = "40";
|
nicholas@2224
|
641 textArea.rows = "5";
|
nicholas@2224
|
642 break;
|
nicholas@2224
|
643 case 'huge':
|
nicholas@2224
|
644 textArea.cols = "50";
|
nicholas@2224
|
645 textArea.rows = "10";
|
nicholas@2224
|
646 break;
|
nicholas@2224
|
647 }
|
nicholas@2224
|
648 if (node.response == undefined) {
|
nicholas@2224
|
649 node.response = "";
|
nicholas@2224
|
650 } else {
|
nicholas@2224
|
651 textArea.value = node.response;
|
nicholas@2224
|
652 }
|
nicholas@2224
|
653 this.popupResponse.appendChild(textArea);
|
nicholas@2224
|
654 textArea.focus();
|
nicholas@2224
|
655 this.popupResponse.style.textAlign="center";
|
nicholas@2224
|
656 this.popupResponse.style.left="0%";
|
nicholas@2224
|
657 } else if (node.specification.type == 'checkbox') {
|
nicholas@2224
|
658 if (node.response == undefined) {
|
nicholas@2224
|
659 node.response = Array(node.specification.options.length);
|
nicholas@2224
|
660 }
|
nicholas@2224
|
661 var index = 0;
|
nicholas@2224
|
662 var max_w = 0;
|
nicholas@2224
|
663 for (var option of node.specification.options) {
|
nicholas@2224
|
664 var input = document.createElement('input');
|
nicholas@2224
|
665 input.id = option.name;
|
nicholas@2224
|
666 input.type = 'checkbox';
|
nicholas@2224
|
667 var span = document.createElement('span');
|
nicholas@2224
|
668 span.textContent = option.text;
|
nicholas@2224
|
669 var hold = document.createElement('div');
|
nicholas@2224
|
670 hold.setAttribute('name','option');
|
nicholas@2224
|
671 hold.className = "popup-option-checbox";
|
nicholas@2224
|
672 hold.appendChild(input);
|
nicholas@2224
|
673 hold.appendChild(span);
|
nicholas@2224
|
674 this.popupResponse.appendChild(hold);
|
nicholas@2224
|
675 if (node.response[index] != undefined){
|
nicholas@2224
|
676 if (node.response[index].checked == true) {
|
nicholas@2224
|
677 input.checked = "true";
|
nicholas@2224
|
678 }
|
nicholas@2224
|
679 }
|
nicholas@2224
|
680 var w = $(hold).width();
|
nicholas@2224
|
681 if (w > max_w)
|
nicholas@2224
|
682 max_w = w;
|
nicholas@2224
|
683 index++;
|
nicholas@2224
|
684 }
|
nicholas@2224
|
685 this.popupResponse.style.textAlign="";
|
nicholas@2224
|
686 var leftP = 50-(((max_w/$('#popupContent').width())/2)*100);
|
nicholas@2224
|
687 this.popupResponse.style.left=leftP+"%";
|
nicholas@2224
|
688 } else if (node.specification.type == 'radio') {
|
nicholas@2224
|
689 if (node.response == undefined) {
|
nicholas@2224
|
690 node.response = {name: "", text: ""};
|
nicholas@2224
|
691 }
|
nicholas@2224
|
692 var index = 0;
|
nicholas@2224
|
693 var max_w = 0;
|
nicholas@2224
|
694 for (var option of node.specification.options) {
|
nicholas@2224
|
695 var input = document.createElement('input');
|
nicholas@2224
|
696 input.id = option.name;
|
nicholas@2224
|
697 input.type = 'radio';
|
nicholas@2224
|
698 input.name = node.specification.id;
|
nicholas@2224
|
699 var span = document.createElement('span');
|
nicholas@2224
|
700 span.textContent = option.text;
|
nicholas@2224
|
701 var hold = document.createElement('div');
|
nicholas@2224
|
702 hold.setAttribute('name','option');
|
nicholas@2224
|
703 hold.className = "popup-option-checbox";
|
nicholas@2224
|
704 hold.appendChild(input);
|
nicholas@2224
|
705 hold.appendChild(span);
|
nicholas@2224
|
706 this.popupResponse.appendChild(hold);
|
nicholas@2224
|
707 if (input.id == node.response.name) {
|
nicholas@2224
|
708 input.checked = "true";
|
nicholas@2224
|
709 }
|
nicholas@2224
|
710 var w = $(hold).width();
|
nicholas@2224
|
711 if (w > max_w)
|
nicholas@2224
|
712 max_w = w;
|
nicholas@2224
|
713 }
|
nicholas@2224
|
714 this.popupResponse.style.textAlign="";
|
nicholas@2224
|
715 var leftP = 50-(((max_w/$('#popupContent').width())/2)*100);
|
nicholas@2224
|
716 this.popupResponse.style.left=leftP+"%";
|
nicholas@2224
|
717 } else if (node.specification.type == 'number') {
|
nicholas@2224
|
718 var input = document.createElement('input');
|
nicholas@2224
|
719 input.type = 'textarea';
|
nicholas@2224
|
720 if (node.min != null) {input.min = node.specification.min;}
|
nicholas@2224
|
721 if (node.max != null) {input.max = node.specification.max;}
|
nicholas@2224
|
722 if (node.step != null) {input.step = node.specification.step;}
|
nicholas@2224
|
723 if (node.response != undefined) {
|
nicholas@2224
|
724 input.value = node.response;
|
nicholas@2224
|
725 }
|
nicholas@2224
|
726 this.popupResponse.appendChild(input);
|
nicholas@2224
|
727 this.popupResponse.style.textAlign="center";
|
nicholas@2224
|
728 this.popupResponse.style.left="0%";
|
nicholas@2224
|
729 }
|
nicholas@2224
|
730 if(this.currentIndex+1 == this.popupOptions.length) {
|
nicholas@2224
|
731 if (this.node.location == "pre") {
|
nicholas@2224
|
732 this.buttonProceed.textContent = 'Start';
|
nicholas@2224
|
733 } else {
|
nicholas@2224
|
734 this.buttonProceed.textContent = 'Submit';
|
nicholas@2224
|
735 }
|
nicholas@2224
|
736 } else {
|
nicholas@2224
|
737 this.buttonProceed.textContent = 'Next';
|
nicholas@2224
|
738 }
|
nicholas@2224
|
739 if(this.currentIndex > 0)
|
nicholas@2224
|
740 this.buttonPrevious.style.visibility = 'visible';
|
nicholas@2224
|
741 else
|
nicholas@2224
|
742 this.buttonPrevious.style.visibility = 'hidden';
|
nicholas@2224
|
743 };
|
nicholas@2224
|
744
|
nicholas@2224
|
745 this.initState = function(node,store) {
|
nicholas@2224
|
746 //Call this with your preTest and postTest nodes when needed to
|
nicholas@2224
|
747 // initialise the popup procedure.
|
nicholas@2224
|
748 if (node.options.length > 0) {
|
nicholas@2224
|
749 this.popupOptions = [];
|
nicholas@2224
|
750 this.node = node;
|
nicholas@2224
|
751 this.store = store;
|
nicholas@2224
|
752 for (var opt of node.options)
|
nicholas@2224
|
753 {
|
nicholas@2224
|
754 this.popupOptions.push({
|
nicholas@2224
|
755 specification: opt,
|
nicholas@2224
|
756 response: null
|
nicholas@2224
|
757 });
|
nicholas@2224
|
758 }
|
nicholas@2224
|
759 this.currentIndex = 0;
|
nicholas@2224
|
760 this.showPopup();
|
nicholas@2224
|
761 this.postNode();
|
nicholas@2224
|
762 } else {
|
nicholas@2224
|
763 advanceState();
|
nicholas@2224
|
764 }
|
nicholas@2224
|
765 };
|
nicholas@2224
|
766
|
nicholas@2224
|
767 this.proceedClicked = function() {
|
nicholas@2224
|
768 // Each time the popup button is clicked!
|
nicholas@2224
|
769 if (testState.stateIndex == 0 && specification.calibration) {
|
nicholas@2224
|
770 interfaceContext.calibrationModuleObject.collect();
|
nicholas@2224
|
771 advanceState();
|
nicholas@2224
|
772 return;
|
nicholas@2224
|
773 }
|
nicholas@2224
|
774 var node = this.popupOptions[this.currentIndex];
|
nicholas@2224
|
775 if (node.specification.type == 'question') {
|
nicholas@2224
|
776 // Must extract the question data
|
nicholas@2224
|
777 var textArea = $(popup.popupContent).find('textarea')[0];
|
nicholas@2224
|
778 if (node.specification.mandatory == true && textArea.value.length == 0) {
|
nicholas@2224
|
779 alert('This question is mandatory');
|
nicholas@2224
|
780 return;
|
nicholas@2224
|
781 } else {
|
nicholas@2224
|
782 // Save the text content
|
nicholas@2224
|
783 console.log("Question: "+ node.specification.statement);
|
nicholas@2224
|
784 console.log("Question Response: "+ textArea.value);
|
nicholas@2224
|
785 node.response = textArea.value;
|
nicholas@2224
|
786 }
|
nicholas@2224
|
787 } else if (node.specification.type == 'checkbox') {
|
nicholas@2224
|
788 // Must extract checkbox data
|
nicholas@2224
|
789 console.log("Checkbox: "+ node.specification.statement);
|
nicholas@2224
|
790 var inputs = this.popupResponse.getElementsByTagName('input');
|
nicholas@2224
|
791 node.response = [];
|
nicholas@2224
|
792 for (var i=0; i<node.specification.options.length; i++) {
|
nicholas@2224
|
793 node.response.push({
|
nicholas@2224
|
794 name: node.specification.options[i].name,
|
nicholas@2224
|
795 text: node.specification.options[i].text,
|
nicholas@2224
|
796 checked: inputs[i].checked
|
nicholas@2224
|
797 });
|
nicholas@2224
|
798 console.log(node.specification.options[i].name+": "+ inputs[i].checked);
|
nicholas@2224
|
799 }
|
nicholas@2224
|
800 } else if (node.specification.type == "radio") {
|
nicholas@2224
|
801 var optHold = this.popupResponse;
|
nicholas@2224
|
802 console.log("Radio: "+ node.specification.statement);
|
nicholas@2224
|
803 node.response = null;
|
nicholas@2224
|
804 var i=0;
|
nicholas@2224
|
805 var inputs = optHold.getElementsByTagName('input');
|
nicholas@2224
|
806 while(node.response == null) {
|
nicholas@2224
|
807 if (i == inputs.length)
|
nicholas@2224
|
808 {
|
nicholas@2224
|
809 if (node.specification.mandatory == true)
|
nicholas@2224
|
810 {
|
nicholas@2224
|
811 alert("This radio is mandatory");
|
nicholas@2224
|
812 } else {
|
nicholas@2224
|
813 node.response = -1;
|
nicholas@2224
|
814 }
|
nicholas@2224
|
815 return;
|
nicholas@2224
|
816 }
|
nicholas@2224
|
817 if (inputs[i].checked == true) {
|
nicholas@2224
|
818 node.response = node.specification.options[i];
|
nicholas@2224
|
819 console.log("Selected: "+ node.specification.options[i].name);
|
nicholas@2224
|
820 }
|
nicholas@2224
|
821 i++;
|
nicholas@2224
|
822 }
|
nicholas@2224
|
823 } else if (node.specification.type == "number") {
|
nicholas@2224
|
824 var input = this.popupContent.getElementsByTagName('input')[0];
|
nicholas@2224
|
825 if (node.mandatory == true && input.value.length == 0) {
|
nicholas@2224
|
826 alert('This question is mandatory. Please enter a number');
|
nicholas@2224
|
827 return;
|
nicholas@2224
|
828 }
|
nicholas@2224
|
829 var enteredNumber = Number(input.value);
|
nicholas@2224
|
830 if (isNaN(enteredNumber)) {
|
nicholas@2224
|
831 alert('Please enter a valid number');
|
nicholas@2224
|
832 return;
|
nicholas@2224
|
833 }
|
nicholas@2224
|
834 if (enteredNumber < node.min && node.min != null) {
|
nicholas@2224
|
835 alert('Number is below the minimum value of '+node.min);
|
nicholas@2224
|
836 return;
|
nicholas@2224
|
837 }
|
nicholas@2224
|
838 if (enteredNumber > node.max && node.max != null) {
|
nicholas@2224
|
839 alert('Number is above the maximum value of '+node.max);
|
nicholas@2224
|
840 return;
|
nicholas@2224
|
841 }
|
nicholas@2224
|
842 node.response = input.value;
|
nicholas@2224
|
843 }
|
nicholas@2224
|
844 this.currentIndex++;
|
nicholas@2224
|
845 if (this.currentIndex < this.popupOptions.length) {
|
nicholas@2224
|
846 this.postNode();
|
nicholas@2224
|
847 } else {
|
nicholas@2224
|
848 // Reached the end of the popupOptions
|
nicholas@2224
|
849 this.hidePopup();
|
nicholas@2224
|
850 for (var node of this.popupOptions)
|
nicholas@2224
|
851 {
|
nicholas@2224
|
852 this.store.postResult(node);
|
nicholas@2224
|
853 }
|
nicholas@2224
|
854 this.store.complete();
|
nicholas@2224
|
855 advanceState();
|
nicholas@2224
|
856 }
|
nicholas@2224
|
857 };
|
nicholas@2224
|
858
|
nicholas@2224
|
859 this.previousClick = function() {
|
nicholas@2224
|
860 // Triggered when the 'Back' button is clicked in the survey
|
nicholas@2224
|
861 if (this.currentIndex > 0) {
|
nicholas@2224
|
862 this.currentIndex--;
|
nicholas@2224
|
863 this.postNode();
|
nicholas@2224
|
864 }
|
nicholas@2224
|
865 };
|
nicholas@2224
|
866
|
nicholas@2224
|
867 this.resize = function(event)
|
nicholas@2224
|
868 {
|
nicholas@2224
|
869 // Called on window resize;
|
nicholas@2224
|
870 if (this.popup != null) {
|
nicholas@2224
|
871 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
|
nicholas@2224
|
872 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
|
nicholas@2224
|
873 var blank = document.getElementsByClassName('testHalt')[0];
|
nicholas@2224
|
874 blank.style.width = window.innerWidth;
|
nicholas@2224
|
875 blank.style.height = window.innerHeight;
|
nicholas@2224
|
876 }
|
nicholas@2224
|
877 };
|
nicholas@2224
|
878 this.hideNextButton = function() {
|
nicholas@2224
|
879 this.buttonProceed.style.visibility = "hidden";
|
nicholas@2224
|
880 }
|
nicholas@2224
|
881 this.hidePreviousButton = function() {
|
nicholas@2224
|
882 this.buttonPrevious.style.visibility = "hidden";
|
nicholas@2224
|
883 }
|
nicholas@2224
|
884 this.showNextButton = function() {
|
nicholas@2224
|
885 this.buttonProceed.style.visibility = "visible";
|
nicholas@2224
|
886 }
|
nicholas@2224
|
887 this.showPreviousButton = function() {
|
nicholas@2224
|
888 this.buttonPrevious.style.visibility = "visible";
|
nicholas@2224
|
889 }
|
nicholas@2224
|
890 }
|
nicholas@2224
|
891
|
nicholas@2224
|
892 function advanceState()
|
nicholas@2224
|
893 {
|
nicholas@2224
|
894 // Just for complete clarity
|
nicholas@2224
|
895 testState.advanceState();
|
nicholas@2224
|
896 }
|
nicholas@2224
|
897
|
nicholas@2224
|
898 function stateMachine()
|
nicholas@2224
|
899 {
|
nicholas@2224
|
900 // Object prototype for tracking and managing the test state
|
nicholas@2224
|
901 this.stateMap = [];
|
nicholas@2224
|
902 this.preTestSurvey = null;
|
nicholas@2224
|
903 this.postTestSurvey = null;
|
nicholas@2224
|
904 this.stateIndex = null;
|
nicholas@2224
|
905 this.currentStateMap = null;
|
nicholas@2224
|
906 this.currentStatePosition = null;
|
nicholas@2224
|
907 this.currentStore = null;
|
nicholas@2224
|
908 this.initialise = function(){
|
nicholas@2224
|
909
|
nicholas@2224
|
910 // Get the data from Specification
|
nicholas@2224
|
911 var pagePool = [];
|
nicholas@2224
|
912 var pageInclude = [];
|
nicholas@2224
|
913 for (var page of specification.pages)
|
nicholas@2224
|
914 {
|
nicholas@2224
|
915 if (page.alwaysInclude) {
|
nicholas@2224
|
916 pageInclude.push(page);
|
nicholas@2224
|
917 } else {
|
nicholas@2224
|
918 pagePool.push(page);
|
nicholas@2224
|
919 }
|
nicholas@2224
|
920 }
|
nicholas@2224
|
921
|
nicholas@2224
|
922 // Find how many are left to get
|
nicholas@2224
|
923 var numPages = specification.poolSize;
|
nicholas@2224
|
924 if (numPages > pagePool.length) {
|
nicholas@2224
|
925 console.log("WARNING - You have specified more pages in <setup poolSize> than you have created!!");
|
nicholas@2224
|
926 numPages = specification.pages.length;
|
nicholas@2224
|
927 }
|
nicholas@2224
|
928 if (specification.poolSize == 0) {
|
nicholas@2224
|
929 numPages = specification.pages.length;
|
nicholas@2224
|
930 }
|
nicholas@2224
|
931 numPages -= pageInclude.length;
|
nicholas@2224
|
932
|
nicholas@2224
|
933 if (numPages > 0) {
|
nicholas@2224
|
934 // Go find the rest of the pages from the pool
|
nicholas@2224
|
935 var subarr = null;
|
nicholas@2224
|
936 if (specification.randomiseOrder) {
|
nicholas@2224
|
937 // Append a random sub-array
|
nicholas@2224
|
938 subarr = randomSubArray(pagePool,numPages);
|
nicholas@2224
|
939 } else {
|
nicholas@2224
|
940 // Append the matching number
|
nicholas@2224
|
941 subarr = pagePool.slice(0,numPages);
|
nicholas@2224
|
942 }
|
nicholas@2224
|
943 pageInclude = pageInclude.concat(subarr);
|
nicholas@2224
|
944 }
|
nicholas@2224
|
945
|
nicholas@2224
|
946 // We now have our selected pages in pageInclude array
|
nicholas@2224
|
947 if (specification.randomiseOrder)
|
nicholas@2224
|
948 {
|
nicholas@2224
|
949 pageInclude = randomiseOrder(pageInclude);
|
nicholas@2224
|
950 }
|
nicholas@2224
|
951 for (var i=0; i<pageInclude.length; i++)
|
nicholas@2224
|
952 {
|
nicholas@2224
|
953 pageInclude[i].presentedId = i;
|
nicholas@2224
|
954 this.stateMap.push(pageInclude[i]);
|
nicholas@2224
|
955 // For each selected page, we must get the sub pool
|
nicholas@2224
|
956 if (pageInclude[i].poolSize != 0 && pageInclude[i].poolSize != pageInclude[i].audioElements.length) {
|
nicholas@2224
|
957 var elemInclude = [];
|
nicholas@2224
|
958 var elemPool = [];
|
nicholas@2224
|
959 for (var elem of pageInclude[i].audioElements) {
|
nicholas@2224
|
960 if (elem.include || elem.type != "normal") {
|
nicholas@2224
|
961 elemInclude.push(elem);
|
nicholas@2224
|
962 } else {
|
nicholas@2224
|
963 elemPool.push(elem);
|
nicholas@2224
|
964 }
|
nicholas@2224
|
965 }
|
nicholas@2224
|
966 var numElems = pageInclude[i].poolSize - elemInclude.length;
|
nicholas@2224
|
967 pageInclude[i].audioElements = elemInclude.concat(randomSubArray(elemPool,numElems));
|
nicholas@2224
|
968 }
|
nicholas@2224
|
969 storage.createTestPageStore(pageInclude[i]);
|
nicholas@2224
|
970 audioEngineContext.loadPageData(pageInclude[i]);
|
nicholas@2224
|
971 }
|
nicholas@2224
|
972
|
nicholas@2224
|
973 if (specification.preTest != null) {this.preTestSurvey = specification.preTest;}
|
nicholas@2224
|
974 if (specification.postTest != null) {this.postTestSurvey = specification.postTest;}
|
nicholas@2224
|
975
|
nicholas@2224
|
976 if (this.stateMap.length > 0) {
|
nicholas@2224
|
977 if(this.stateIndex != null) {
|
nicholas@2224
|
978 console.log('NOTE - State already initialise');
|
nicholas@2224
|
979 }
|
nicholas@2224
|
980 this.stateIndex = -2;
|
nicholas@2224
|
981 console.log('Starting test...');
|
nicholas@2224
|
982 } else {
|
nicholas@2224
|
983 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
|
nicholas@2224
|
984 }
|
nicholas@2224
|
985 };
|
nicholas@2224
|
986 this.advanceState = function(){
|
nicholas@2224
|
987 if (this.stateIndex == null) {
|
nicholas@2224
|
988 this.initialise();
|
nicholas@2224
|
989 }
|
nicholas@2224
|
990 storage.update();
|
nicholas@2224
|
991 if (this.stateIndex == -2) {
|
nicholas@2224
|
992 this.stateIndex++;
|
nicholas@2224
|
993 if (this.preTestSurvey != null)
|
nicholas@2224
|
994 {
|
nicholas@2224
|
995 popup.initState(this.preTestSurvey,storage.globalPreTest);
|
nicholas@2224
|
996 } else {
|
nicholas@2224
|
997 this.advanceState();
|
nicholas@2224
|
998 }
|
nicholas@2224
|
999 } else if (this.stateIndex == -1) {
|
nicholas@2224
|
1000 this.stateIndex++;
|
nicholas@2224
|
1001 if (specification.calibration) {
|
nicholas@2224
|
1002 popup.showPopup();
|
nicholas@2224
|
1003 popup.popupTitle.textContent = "Calibration. Set the levels so all tones are of equal amplitude. Move your mouse over the sliders to hear the tones. The red slider is the reference tone";
|
nicholas@2224
|
1004 interfaceContext.calibrationModuleObject = new interfaceContext.calibrationModule();
|
nicholas@2224
|
1005 interfaceContext.calibrationModuleObject.build(popup.popupResponse);
|
nicholas@2224
|
1006 popup.hidePreviousButton();
|
nicholas@2224
|
1007 } else {
|
nicholas@2224
|
1008 this.advanceState();
|
nicholas@2224
|
1009 }
|
nicholas@2224
|
1010 }
|
nicholas@2224
|
1011 else if (this.stateIndex == this.stateMap.length)
|
nicholas@2224
|
1012 {
|
nicholas@2224
|
1013 // All test pages complete, post test
|
nicholas@2224
|
1014 console.log('Ending test ...');
|
nicholas@2224
|
1015 this.stateIndex++;
|
nicholas@2224
|
1016 if (this.postTestSurvey == null) {
|
nicholas@2224
|
1017 this.advanceState();
|
nicholas@2224
|
1018 } else {
|
nicholas@2224
|
1019 popup.initState(this.postTestSurvey,storage.globalPostTest);
|
nicholas@2224
|
1020 }
|
nicholas@2224
|
1021 } else if (this.stateIndex > this.stateMap.length)
|
nicholas@2224
|
1022 {
|
nicholas@2224
|
1023 createProjectSave(specification.projectReturn);
|
nicholas@2224
|
1024 }
|
nicholas@2224
|
1025 else
|
nicholas@2224
|
1026 {
|
nicholas@2224
|
1027 popup.hidePopup();
|
nicholas@2224
|
1028 if (this.currentStateMap == null)
|
nicholas@2224
|
1029 {
|
nicholas@2224
|
1030 this.currentStateMap = this.stateMap[this.stateIndex];
|
nicholas@2224
|
1031 if (this.currentStateMap.randomiseOrder)
|
nicholas@2224
|
1032 {
|
nicholas@2224
|
1033 this.currentStateMap.audioElements = randomiseOrder(this.currentStateMap.audioElements);
|
nicholas@2224
|
1034 }
|
nicholas@2224
|
1035 this.currentStore = storage.testPages[this.stateIndex];
|
nicholas@2224
|
1036 if (this.currentStateMap.preTest != null)
|
nicholas@2224
|
1037 {
|
nicholas@2224
|
1038 this.currentStatePosition = 'pre';
|
nicholas@2224
|
1039 popup.initState(this.currentStateMap.preTest,storage.testPages[this.stateIndex].preTest);
|
nicholas@2224
|
1040 } else {
|
nicholas@2224
|
1041 this.currentStatePosition = 'test';
|
nicholas@2224
|
1042 }
|
nicholas@2224
|
1043 interfaceContext.newPage(this.currentStateMap,storage.testPages[this.stateIndex]);
|
nicholas@2224
|
1044 return;
|
nicholas@2224
|
1045 }
|
nicholas@2224
|
1046 switch(this.currentStatePosition)
|
nicholas@2224
|
1047 {
|
nicholas@2224
|
1048 case 'pre':
|
nicholas@2224
|
1049 this.currentStatePosition = 'test';
|
nicholas@2224
|
1050 break;
|
nicholas@2224
|
1051 case 'test':
|
nicholas@2224
|
1052 this.currentStatePosition = 'post';
|
nicholas@2224
|
1053 // Save the data
|
nicholas@2224
|
1054 this.testPageCompleted();
|
nicholas@2224
|
1055 if (this.currentStateMap.postTest == null)
|
nicholas@2224
|
1056 {
|
nicholas@2224
|
1057 this.advanceState();
|
nicholas@2224
|
1058 return;
|
nicholas@2224
|
1059 } else {
|
nicholas@2224
|
1060 popup.initState(this.currentStateMap.postTest,storage.testPages[this.stateIndex].postTest);
|
nicholas@2224
|
1061 }
|
nicholas@2224
|
1062 break;
|
nicholas@2224
|
1063 case 'post':
|
nicholas@2224
|
1064 this.stateIndex++;
|
nicholas@2224
|
1065 this.currentStateMap = null;
|
nicholas@2224
|
1066 this.advanceState();
|
nicholas@2224
|
1067 break;
|
nicholas@2224
|
1068 };
|
nicholas@2224
|
1069 }
|
nicholas@2224
|
1070 };
|
nicholas@2224
|
1071
|
nicholas@2224
|
1072 this.testPageCompleted = function() {
|
nicholas@2224
|
1073 // Function called each time a test page has been completed
|
nicholas@2224
|
1074 var storePoint = storage.testPages[this.stateIndex];
|
nicholas@2224
|
1075 // First get the test metric
|
nicholas@2224
|
1076
|
nicholas@2224
|
1077 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
|
nicholas@2224
|
1078 if (audioEngineContext.metric.enableTestTimer)
|
nicholas@2224
|
1079 {
|
nicholas@2224
|
1080 var testTime = storePoint.parent.document.createElement('metricresult');
|
nicholas@2224
|
1081 testTime.id = 'testTime';
|
nicholas@2224
|
1082 testTime.textContent = audioEngineContext.timer.testDuration;
|
nicholas@2224
|
1083 metric.appendChild(testTime);
|
nicholas@2224
|
1084 }
|
nicholas@2224
|
1085
|
nicholas@2224
|
1086 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2224
|
1087 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
1088 {
|
nicholas@2224
|
1089 ao.exportXMLDOM();
|
nicholas@2224
|
1090 }
|
nicholas@2224
|
1091 for (var element of interfaceContext.commentQuestions)
|
nicholas@2224
|
1092 {
|
nicholas@2224
|
1093 element.exportXMLDOM(storePoint);
|
nicholas@2224
|
1094 }
|
nicholas@2224
|
1095 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
|
nicholas@2224
|
1096 storePoint.complete();
|
nicholas@2224
|
1097 };
|
nicholas@2310
|
1098
|
nicholas@2310
|
1099 this.getCurrentTestPage = function() {
|
nicholas@2310
|
1100 if (this.stateIndex >= 0 && this.stateIndex< this.stateMap.length) {
|
nicholas@2310
|
1101 return this.currentStateMap;
|
nicholas@2310
|
1102 } else {
|
nicholas@2310
|
1103 return null;
|
nicholas@2310
|
1104 }
|
nicholas@2310
|
1105 }
|
nicholas@2224
|
1106 }
|
nicholas@2224
|
1107
|
nicholas@2224
|
1108 function AudioEngine(specification) {
|
nicholas@2224
|
1109
|
nicholas@2224
|
1110 // Create two output paths, the main outputGain and fooGain.
|
nicholas@2224
|
1111 // Output gain is default to 1 and any items for playback route here
|
nicholas@2224
|
1112 // Foo gain is used for analysis to ensure paths get processed, but are not heard
|
nicholas@2224
|
1113 // because web audio will optimise and any route which does not go to the destination gets ignored.
|
nicholas@2224
|
1114 this.outputGain = audioContext.createGain();
|
nicholas@2224
|
1115 this.fooGain = audioContext.createGain();
|
nicholas@2224
|
1116 this.fooGain.gain = 0;
|
nicholas@2224
|
1117
|
nicholas@2224
|
1118 // Use this to detect playback state: 0 - stopped, 1 - playing
|
nicholas@2224
|
1119 this.status = 0;
|
nicholas@2224
|
1120
|
nicholas@2224
|
1121 // Connect both gains to output
|
nicholas@2224
|
1122 this.outputGain.connect(audioContext.destination);
|
nicholas@2224
|
1123 this.fooGain.connect(audioContext.destination);
|
nicholas@2224
|
1124
|
nicholas@2224
|
1125 // Create the timer Object
|
nicholas@2224
|
1126 this.timer = new timer();
|
nicholas@2224
|
1127 // Create session metrics
|
nicholas@2224
|
1128 this.metric = new sessionMetrics(this,specification);
|
nicholas@2224
|
1129
|
nicholas@2224
|
1130 this.loopPlayback = false;
|
nicholas@2224
|
1131
|
nicholas@2224
|
1132 this.pageStore = null;
|
nicholas@2224
|
1133
|
nicholas@2224
|
1134 // Create store for new audioObjects
|
nicholas@2224
|
1135 this.audioObjects = [];
|
nicholas@2224
|
1136
|
nicholas@2224
|
1137 this.buffers = [];
|
nicholas@2224
|
1138 this.bufferObj = function()
|
nicholas@2224
|
1139 {
|
nicholas@2224
|
1140 this.url = null;
|
nicholas@2224
|
1141 this.buffer = null;
|
nicholas@2224
|
1142 this.xmlRequest = new XMLHttpRequest();
|
nicholas@2224
|
1143 this.xmlRequest.parent = this;
|
nicholas@2224
|
1144 this.users = [];
|
nicholas@2224
|
1145 this.progress = 0;
|
nicholas@2224
|
1146 this.status = 0;
|
nicholas@2224
|
1147 this.ready = function()
|
nicholas@2224
|
1148 {
|
nicholas@2224
|
1149 if (this.status >= 2)
|
nicholas@2224
|
1150 {
|
nicholas@2224
|
1151 this.status = 3;
|
nicholas@2224
|
1152 }
|
nicholas@2224
|
1153 for (var i=0; i<this.users.length; i++)
|
nicholas@2224
|
1154 {
|
nicholas@2224
|
1155 this.users[i].state = 1;
|
nicholas@2224
|
1156 if (this.users[i].interfaceDOM != null)
|
nicholas@2224
|
1157 {
|
nicholas@2224
|
1158 this.users[i].bufferLoaded(this);
|
nicholas@2224
|
1159 }
|
nicholas@2224
|
1160 }
|
nicholas@2224
|
1161 };
|
nicholas@2224
|
1162 this.getMedia = function(url) {
|
nicholas@2224
|
1163 this.url = url;
|
nicholas@2224
|
1164 this.xmlRequest.open('GET',this.url,true);
|
nicholas@2224
|
1165 this.xmlRequest.responseType = 'arraybuffer';
|
nicholas@2224
|
1166
|
nicholas@2224
|
1167 var bufferObj = this;
|
nicholas@2224
|
1168
|
nicholas@2224
|
1169 // Create callback to decode the data asynchronously
|
nicholas@2224
|
1170 this.xmlRequest.onloadend = function() {
|
nicholas@2224
|
1171 // Use inbuilt WAVE decoder first
|
nicholas@2224
|
1172 if (this.status == -1) {return;}
|
nicholas@2224
|
1173 var waveObj = new WAVE();
|
nicholas@2224
|
1174 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
|
nicholas@2224
|
1175 {
|
nicholas@2224
|
1176 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
|
nicholas@2224
|
1177 for (var c=0; c<waveObj.num_channels; c++)
|
nicholas@2224
|
1178 {
|
nicholas@2224
|
1179 var buffer_ptr = bufferObj.buffer.getChannelData(c);
|
nicholas@2224
|
1180 for (var n=0; n<waveObj.num_samples; n++)
|
nicholas@2224
|
1181 {
|
nicholas@2224
|
1182 buffer_ptr[n] = waveObj.decoded_data[c][n];
|
nicholas@2224
|
1183 }
|
nicholas@2224
|
1184 }
|
nicholas@2224
|
1185
|
nicholas@2224
|
1186 delete waveObj;
|
nicholas@2224
|
1187 } else {
|
nicholas@2224
|
1188 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
|
nicholas@2224
|
1189 bufferObj.buffer = decodedData;
|
nicholas@2244
|
1190 bufferObj.status = 2;
|
nicholas@2244
|
1191 calculateLoudness(bufferObj,"I");
|
nicholas@2224
|
1192 }, function(e){
|
nicholas@2224
|
1193 // Should only be called if there was an error, but sometimes gets called continuously
|
nicholas@2224
|
1194 // Check here if the error is genuine
|
nicholas@2224
|
1195 if (bufferObj.xmlRequest.response == undefined) {
|
nicholas@2224
|
1196 // Genuine error
|
nicholas@2224
|
1197 console.log('FATAL - Error loading buffer on '+audioObj.id);
|
nicholas@2224
|
1198 if (request.status == 404)
|
nicholas@2224
|
1199 {
|
nicholas@2224
|
1200 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
|
nicholas@2224
|
1201 console.log('URL: '+audioObj.url);
|
nicholas@2224
|
1202 errorSessionDump('Fragment '+audioObj.id+' 404 error');
|
nicholas@2224
|
1203 }
|
nicholas@2224
|
1204 this.parent.status = -1;
|
nicholas@2224
|
1205 }
|
nicholas@2224
|
1206 });
|
nicholas@2224
|
1207 }
|
nicholas@2224
|
1208 if (bufferObj.buffer != undefined)
|
nicholas@2224
|
1209 {
|
nicholas@2224
|
1210 bufferObj.status = 2;
|
nicholas@2224
|
1211 calculateLoudness(bufferObj,"I");
|
nicholas@2224
|
1212 }
|
nicholas@2224
|
1213 };
|
nicholas@2224
|
1214
|
nicholas@2224
|
1215 // Create callback for any error in loading
|
nicholas@2224
|
1216 this.xmlRequest.onerror = function() {
|
nicholas@2224
|
1217 this.parent.status = -1;
|
nicholas@2224
|
1218 for (var i=0; i<this.parent.users.length; i++)
|
nicholas@2224
|
1219 {
|
nicholas@2224
|
1220 this.parent.users[i].state = -1;
|
nicholas@2224
|
1221 if (this.parent.users[i].interfaceDOM != null)
|
nicholas@2224
|
1222 {
|
nicholas@2224
|
1223 this.parent.users[i].bufferLoaded(this);
|
nicholas@2224
|
1224 }
|
nicholas@2224
|
1225 }
|
nicholas@2224
|
1226 }
|
nicholas@2224
|
1227
|
nicholas@2224
|
1228 this.progress = 0;
|
nicholas@2224
|
1229 this.progressCallback = function(event){
|
nicholas@2224
|
1230 if (event.lengthComputable)
|
nicholas@2224
|
1231 {
|
nicholas@2224
|
1232 this.parent.progress = event.loaded / event.total;
|
nicholas@2224
|
1233 for (var i=0; i<this.parent.users.length; i++)
|
nicholas@2224
|
1234 {
|
nicholas@2224
|
1235 if(this.parent.users[i].interfaceDOM != null)
|
nicholas@2224
|
1236 {
|
nicholas@2224
|
1237 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
|
nicholas@2224
|
1238 {
|
nicholas@2224
|
1239 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
|
nicholas@2224
|
1240 }
|
nicholas@2224
|
1241 }
|
nicholas@2224
|
1242 }
|
nicholas@2224
|
1243 }
|
nicholas@2224
|
1244 };
|
nicholas@2224
|
1245 this.xmlRequest.addEventListener("progress", this.progressCallback);
|
nicholas@2224
|
1246 this.status = 1;
|
nicholas@2224
|
1247 this.xmlRequest.send();
|
nicholas@2224
|
1248 };
|
nicholas@2224
|
1249
|
nicholas@2224
|
1250 this.registerAudioObject = function(audioObject)
|
nicholas@2224
|
1251 {
|
nicholas@2224
|
1252 // Called by an audioObject to register to the buffer for use
|
nicholas@2224
|
1253 // First check if already in the register pool
|
nicholas@2224
|
1254 for (var objects of this.users)
|
nicholas@2224
|
1255 {
|
nicholas@2224
|
1256 if (audioObject.id == objects.id){return 0;}
|
nicholas@2224
|
1257 }
|
nicholas@2224
|
1258 this.users.push(audioObject);
|
nicholas@2224
|
1259 if (this.status == 3 || this.status == -1)
|
nicholas@2224
|
1260 {
|
nicholas@2224
|
1261 // The buffer is already ready, trigger bufferLoaded
|
nicholas@2224
|
1262 audioObject.bufferLoaded(this);
|
nicholas@2224
|
1263 }
|
nicholas@2224
|
1264 };
|
nicholas@2224
|
1265
|
nicholas@2224
|
1266 this.copyBuffer = function(preSilenceTime,postSilenceTime) {
|
nicholas@2224
|
1267 // Copies the entire bufferObj.
|
nicholas@2224
|
1268 if (preSilenceTime == undefined) {preSilenceTime = 0;}
|
nicholas@2224
|
1269 if (postSilenceTime == undefined) {postSilenceTime = 0;}
|
nicholas@2224
|
1270 var copy = new this.constructor();
|
nicholas@2224
|
1271 copy.url = this.url;
|
nicholas@2224
|
1272 var preSilenceSamples = secondsToSamples(preSilenceTime,this.buffer.sampleRate);
|
nicholas@2224
|
1273 var postSilenceSamples = secondsToSamples(postSilenceTime,this.buffer.sampleRate);
|
nicholas@2224
|
1274 var newLength = this.buffer.length+preSilenceSamples+postSilenceSamples;
|
nicholas@2224
|
1275 copy.buffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
|
nicholas@2224
|
1276 // Now we can use some efficient background copy schemes if we are just padding the end
|
nicholas@2224
|
1277 if (preSilenceSamples == 0 && typeof copy.buffer.copyToChannel == "function") {
|
nicholas@2224
|
1278 for (var c=0; c<this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1279 copy.buffer.copyToChannel(this.buffer.getChannelData(c),c);
|
nicholas@2224
|
1280 }
|
nicholas@2224
|
1281 } else {
|
nicholas@2224
|
1282 for (var c=0; c<this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1283 var src = this.buffer.getChannelData(c);
|
nicholas@2224
|
1284 var dst = copy.buffer.getChannelData(c);
|
nicholas@2224
|
1285 for (var n=0; n<src.length; n++)
|
nicholas@2224
|
1286 dst[n+preSilenceSamples] = src[n];
|
nicholas@2224
|
1287 }
|
nicholas@2224
|
1288 }
|
nicholas@2224
|
1289 // Copy in the rest of the buffer information
|
nicholas@2224
|
1290 copy.buffer.lufs = this.buffer.lufs;
|
nicholas@2224
|
1291 copy.buffer.playbackGain = this.buffer.playbackGain;
|
nicholas@2224
|
1292 return copy;
|
nicholas@2224
|
1293 }
|
nicholas@2224
|
1294 };
|
nicholas@2224
|
1295
|
nicholas@2224
|
1296 this.loadPageData = function(page) {
|
nicholas@2224
|
1297 // Load the URL from pages
|
nicholas@2224
|
1298 for (var element of page.audioElements) {
|
nicholas@2224
|
1299 var URL = page.hostURL + element.url;
|
nicholas@2224
|
1300 var buffer = null;
|
nicholas@2224
|
1301 for (var buffObj of this.buffers) {
|
nicholas@2224
|
1302 if (URL == buffObj.url) {
|
nicholas@2224
|
1303 buffer = buffObj;
|
nicholas@2224
|
1304 break;
|
nicholas@2224
|
1305 }
|
nicholas@2224
|
1306 }
|
nicholas@2224
|
1307 if (buffer == null) {
|
nicholas@2224
|
1308 buffer = new this.bufferObj();
|
nicholas@2224
|
1309 buffer.getMedia(URL);
|
nicholas@2224
|
1310 this.buffers.push(buffer);
|
nicholas@2224
|
1311 }
|
nicholas@2224
|
1312 }
|
nicholas@2224
|
1313 };
|
nicholas@2224
|
1314
|
nicholas@2224
|
1315 this.play = function(id) {
|
nicholas@2224
|
1316 // Start the timer and set the audioEngine state to playing (1)
|
nicholas@2224
|
1317 if (this.status == 0 && this.loopPlayback) {
|
nicholas@2224
|
1318 // Check if all audioObjects are ready
|
nicholas@2224
|
1319 if(this.checkAllReady())
|
nicholas@2224
|
1320 {
|
nicholas@2224
|
1321 this.status = 1;
|
nicholas@2224
|
1322 this.setSynchronousLoop();
|
nicholas@2224
|
1323 }
|
nicholas@2224
|
1324 }
|
nicholas@2224
|
1325 else
|
nicholas@2224
|
1326 {
|
nicholas@2224
|
1327 this.status = 1;
|
nicholas@2224
|
1328 }
|
nicholas@2224
|
1329 if (this.status== 1) {
|
nicholas@2224
|
1330 this.timer.startTest();
|
nicholas@2224
|
1331 if (id == undefined) {
|
nicholas@2224
|
1332 id = -1;
|
nicholas@2224
|
1333 console.error('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
|
nicholas@2224
|
1334 return;
|
nicholas@2224
|
1335 } else {
|
nicholas@2224
|
1336 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
|
nicholas@2224
|
1337 }
|
nicholas@2224
|
1338 if (this.loopPlayback) {
|
nicholas@2224
|
1339 var setTime = audioContext.currentTime+specification.crossFade;
|
nicholas@2224
|
1340 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1341 {
|
nicholas@2224
|
1342 this.audioObjects[i].play(audioContext.currentTime);
|
nicholas@2224
|
1343 if (id == i) {
|
nicholas@2224
|
1344 this.audioObjects[i].loopStart(setTime);
|
nicholas@2224
|
1345 } else {
|
nicholas@2224
|
1346 this.audioObjects[i].loopStop(setTime);
|
nicholas@2224
|
1347 }
|
nicholas@2224
|
1348 }
|
nicholas@2224
|
1349 } else {
|
nicholas@2224
|
1350 var setTime = audioContext.currentTime+specification.crossFade;
|
nicholas@2224
|
1351 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1352 {
|
nicholas@2224
|
1353 if (i != id) {
|
nicholas@2224
|
1354 this.audioObjects[i].stop(setTime);
|
nicholas@2224
|
1355 } else if (i == id) {
|
nicholas@2224
|
1356 this.audioObjects[id].play(setTime);
|
nicholas@2224
|
1357 }
|
nicholas@2224
|
1358 }
|
nicholas@2224
|
1359 }
|
nicholas@2224
|
1360 interfaceContext.playhead.start();
|
nicholas@2224
|
1361 }
|
nicholas@2224
|
1362 };
|
nicholas@2224
|
1363
|
nicholas@2224
|
1364 this.stop = function() {
|
nicholas@2224
|
1365 // Send stop and reset command to all playback buffers
|
nicholas@2224
|
1366 if (this.status == 1) {
|
nicholas@2224
|
1367 var setTime = audioContext.currentTime+0.1;
|
nicholas@2224
|
1368 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1369 {
|
nicholas@2224
|
1370 this.audioObjects[i].stop(setTime);
|
nicholas@2224
|
1371 }
|
nicholas@2224
|
1372 interfaceContext.playhead.stop();
|
nicholas@2224
|
1373 }
|
nicholas@2224
|
1374 };
|
nicholas@2224
|
1375
|
nicholas@2224
|
1376 this.newTrack = function(element) {
|
nicholas@2224
|
1377 // Pull data from given URL into new audio buffer
|
nicholas@2224
|
1378 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
|
nicholas@2224
|
1379
|
nicholas@2224
|
1380 // Create the audioObject with ID of the new track length;
|
nicholas@2224
|
1381 audioObjectId = this.audioObjects.length;
|
nicholas@2224
|
1382 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
|
nicholas@2224
|
1383
|
nicholas@2224
|
1384 // Check if audioObject buffer is currently stored by full URL
|
nicholas@2224
|
1385 var URL = testState.currentStateMap.hostURL + element.url;
|
nicholas@2224
|
1386 var buffer = null;
|
nicholas@2224
|
1387 for (var i=0; i<this.buffers.length; i++)
|
nicholas@2224
|
1388 {
|
nicholas@2224
|
1389 if (URL == this.buffers[i].url)
|
nicholas@2224
|
1390 {
|
nicholas@2224
|
1391 buffer = this.buffers[i];
|
nicholas@2224
|
1392 break;
|
nicholas@2224
|
1393 }
|
nicholas@2224
|
1394 }
|
nicholas@2224
|
1395 if (buffer == null)
|
nicholas@2224
|
1396 {
|
nicholas@2224
|
1397 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
|
nicholas@2224
|
1398 buffer = new this.bufferObj();
|
nicholas@2224
|
1399 this.buffers.push(buffer);
|
nicholas@2224
|
1400 buffer.getMedia(URL);
|
nicholas@2224
|
1401 }
|
nicholas@2224
|
1402 this.audioObjects[audioObjectId].specification = element;
|
nicholas@2224
|
1403 this.audioObjects[audioObjectId].url = URL;
|
nicholas@2224
|
1404 // Obtain store node
|
nicholas@2224
|
1405 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
|
nicholas@2224
|
1406 for (var i=0; i<aeNodes.length; i++)
|
nicholas@2224
|
1407 {
|
nicholas@2224
|
1408 if(aeNodes[i].getAttribute("ref") == element.id)
|
nicholas@2224
|
1409 {
|
nicholas@2224
|
1410 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
|
nicholas@2224
|
1411 break;
|
nicholas@2224
|
1412 }
|
nicholas@2224
|
1413 }
|
nicholas@2224
|
1414 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
|
nicholas@2224
|
1415 return this.audioObjects[audioObjectId];
|
nicholas@2224
|
1416 };
|
nicholas@2224
|
1417
|
nicholas@2224
|
1418 this.newTestPage = function(audioHolderObject,store) {
|
nicholas@2224
|
1419 this.pageStore = store;
|
nicholas@2224
|
1420 this.status = 0;
|
nicholas@2224
|
1421 this.audioObjectsReady = false;
|
nicholas@2224
|
1422 this.metric.reset();
|
nicholas@2224
|
1423 for (var i=0; i < this.buffers.length; i++)
|
nicholas@2224
|
1424 {
|
nicholas@2224
|
1425 this.buffers[i].users = [];
|
nicholas@2224
|
1426 }
|
nicholas@2224
|
1427 this.audioObjects = [];
|
nicholas@2224
|
1428 this.timer = new timer();
|
nicholas@2224
|
1429 this.loopPlayback = audioHolderObject.loop;
|
nicholas@2224
|
1430 };
|
nicholas@2224
|
1431
|
nicholas@2224
|
1432 this.checkAllPlayed = function() {
|
nicholas@2224
|
1433 arr = [];
|
nicholas@2224
|
1434 for (var id=0; id<this.audioObjects.length; id++) {
|
nicholas@2224
|
1435 if (this.audioObjects[id].metric.wasListenedTo == false) {
|
nicholas@2224
|
1436 arr.push(this.audioObjects[id].id);
|
nicholas@2224
|
1437 }
|
nicholas@2224
|
1438 }
|
nicholas@2224
|
1439 return arr;
|
nicholas@2224
|
1440 };
|
nicholas@2224
|
1441
|
nicholas@2224
|
1442 this.checkAllReady = function() {
|
nicholas@2224
|
1443 var ready = true;
|
nicholas@2224
|
1444 for (var i=0; i<this.audioObjects.length; i++) {
|
nicholas@2224
|
1445 if (this.audioObjects[i].state == 0) {
|
nicholas@2224
|
1446 // Track not ready
|
nicholas@2224
|
1447 console.log('WAIT -- audioObject '+i+' not ready yet!');
|
nicholas@2224
|
1448 ready = false;
|
nicholas@2224
|
1449 };
|
nicholas@2224
|
1450 }
|
nicholas@2224
|
1451 return ready;
|
nicholas@2224
|
1452 };
|
nicholas@2224
|
1453
|
nicholas@2224
|
1454 this.setSynchronousLoop = function() {
|
nicholas@2224
|
1455 // Pads the signals so they are all exactly the same length
|
nicholas@2224
|
1456 // Get the length of the longest signal.
|
nicholas@2224
|
1457 var length = 0;
|
nicholas@2224
|
1458 var maxId;
|
nicholas@2224
|
1459 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1460 {
|
nicholas@2224
|
1461 if (length < this.audioObjects[i].buffer.buffer.length)
|
nicholas@2224
|
1462 {
|
nicholas@2224
|
1463 length = this.audioObjects[i].buffer.buffer.length;
|
nicholas@2224
|
1464 maxId = i;
|
nicholas@2224
|
1465 }
|
nicholas@2224
|
1466 }
|
nicholas@2224
|
1467 // Extract the audio and zero-pad
|
nicholas@2224
|
1468 for (var ao of this.audioObjects)
|
nicholas@2224
|
1469 {
|
nicholas@2224
|
1470 var lengthDiff = length - ao.buffer.buffer.length;
|
nicholas@2224
|
1471 ao.buffer = ao.buffer.copyBuffer(0,samplesToSeconds(lengthDiff,ao.buffer.buffer.sampleRate));
|
nicholas@2224
|
1472 }
|
nicholas@2224
|
1473 };
|
nicholas@2224
|
1474
|
nicholas@2224
|
1475 this.exportXML = function()
|
nicholas@2224
|
1476 {
|
nicholas@2224
|
1477
|
nicholas@2224
|
1478 };
|
nicholas@2224
|
1479
|
nicholas@2224
|
1480 }
|
nicholas@2224
|
1481
|
nicholas@2224
|
1482 function audioObject(id) {
|
nicholas@2224
|
1483 // The main buffer object with common control nodes to the AudioEngine
|
nicholas@2224
|
1484
|
nicholas@2224
|
1485 this.specification;
|
nicholas@2224
|
1486 this.id = id;
|
nicholas@2224
|
1487 this.state = 0; // 0 - no data, 1 - ready
|
nicholas@2224
|
1488 this.url = null; // Hold the URL given for the output back to the results.
|
nicholas@2224
|
1489 this.metric = new metricTracker(this);
|
nicholas@2224
|
1490 this.storeDOM = null;
|
nicholas@2224
|
1491
|
nicholas@2224
|
1492 // Bindings for GUI
|
nicholas@2224
|
1493 this.interfaceDOM = null;
|
nicholas@2224
|
1494 this.commentDOM = null;
|
nicholas@2224
|
1495
|
nicholas@2224
|
1496 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
|
nicholas@2224
|
1497 this.bufferNode = undefined;
|
nicholas@2224
|
1498 this.outputGain = audioContext.createGain();
|
nicholas@2224
|
1499
|
nicholas@2224
|
1500 this.onplayGain = 1.0;
|
nicholas@2224
|
1501
|
nicholas@2224
|
1502 // Connect buffer to the audio graph
|
nicholas@2224
|
1503 this.outputGain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
1504
|
nicholas@2224
|
1505 // the audiobuffer is not designed for multi-start playback
|
nicholas@2224
|
1506 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
|
nicholas@2224
|
1507 this.buffer;
|
nicholas@2224
|
1508
|
nicholas@2224
|
1509 this.bufferLoaded = function(callee)
|
nicholas@2224
|
1510 {
|
nicholas@2224
|
1511 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
|
nicholas@2224
|
1512 // audioObject and trigger the interfaceDOM.enable() function for user feedback
|
nicholas@2224
|
1513 if (callee.status == -1) {
|
nicholas@2224
|
1514 // ERROR
|
nicholas@2224
|
1515 this.state = -1;
|
nicholas@2224
|
1516 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
|
nicholas@2224
|
1517 this.buffer = callee;
|
nicholas@2224
|
1518 return;
|
nicholas@2224
|
1519 }
|
nicholas@2224
|
1520 this.buffer = callee;
|
nicholas@2224
|
1521 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
|
nicholas@2224
|
1522 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
|
nicholas@2224
|
1523 if (preSilenceTime != 0 || postSilenceTime != 0) {
|
nicholas@2224
|
1524 this.buffer = callee.copyBuffer(preSilenceTime,postSilenceTime);
|
nicholas@2224
|
1525 }
|
nicholas@2224
|
1526 this.state = 1;
|
nicholas@2224
|
1527 var targetLUFS = this.specification.parent.loudness || specification.loudness;
|
nicholas@2224
|
1528 if (typeof targetLUFS === "number")
|
nicholas@2224
|
1529 {
|
nicholas@2224
|
1530 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
|
nicholas@2224
|
1531 } else {
|
nicholas@2224
|
1532 this.buffer.buffer.playbackGain = 1.0;
|
nicholas@2224
|
1533 }
|
nicholas@2224
|
1534 if (this.interfaceDOM != null) {
|
nicholas@2224
|
1535 this.interfaceDOM.enable();
|
nicholas@2224
|
1536 }
|
nicholas@2224
|
1537 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
|
nicholas@2224
|
1538 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
|
nicholas@2224
|
1539 };
|
nicholas@2224
|
1540
|
nicholas@2224
|
1541 this.bindInterface = function(interfaceObject)
|
nicholas@2224
|
1542 {
|
nicholas@2224
|
1543 this.interfaceDOM = interfaceObject;
|
nicholas@2224
|
1544 this.metric.initialise(interfaceObject.getValue());
|
nicholas@2224
|
1545 if (this.state == 1)
|
nicholas@2224
|
1546 {
|
nicholas@2224
|
1547 this.interfaceDOM.enable();
|
nicholas@2224
|
1548 } else if (this.state == -1) {
|
nicholas@2224
|
1549 // ERROR
|
nicholas@2224
|
1550 this.interfaceDOM.error();
|
nicholas@2224
|
1551 return;
|
nicholas@2224
|
1552 }
|
nicholas@2224
|
1553 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
|
nicholas@2224
|
1554 };
|
nicholas@2224
|
1555
|
nicholas@2224
|
1556 this.loopStart = function(setTime) {
|
nicholas@2224
|
1557 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
|
nicholas@2224
|
1558 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1559 this.interfaceDOM.startPlayback();
|
nicholas@2224
|
1560 };
|
nicholas@2224
|
1561
|
nicholas@2224
|
1562 this.loopStop = function(setTime) {
|
nicholas@2224
|
1563 if (this.outputGain.gain.value != 0.0) {
|
nicholas@2224
|
1564 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
|
nicholas@2224
|
1565 this.metric.stopListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1566 }
|
nicholas@2224
|
1567 this.interfaceDOM.stopPlayback();
|
nicholas@2224
|
1568 };
|
nicholas@2224
|
1569
|
nicholas@2224
|
1570 this.play = function(startTime) {
|
nicholas@2224
|
1571 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
|
nicholas@2224
|
1572 this.bufferNode = audioContext.createBufferSource();
|
nicholas@2224
|
1573 this.bufferNode.owner = this;
|
nicholas@2224
|
1574 this.bufferNode.connect(this.outputGain);
|
nicholas@2224
|
1575 this.bufferNode.buffer = this.buffer.buffer;
|
nicholas@2224
|
1576 this.bufferNode.loop = audioEngineContext.loopPlayback;
|
nicholas@2224
|
1577 this.bufferNode.onended = function(event) {
|
nicholas@2224
|
1578 // Safari does not like using 'this' to reference the calling object!
|
nicholas@2224
|
1579 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
|
nicholas@2224
|
1580 if (event.currentTarget != null) {
|
nicholas@2224
|
1581 event.currentTarget.owner.stop(audioContext.currentTime+1);
|
nicholas@2224
|
1582 }
|
nicholas@2224
|
1583 };
|
nicholas@2224
|
1584 if (this.bufferNode.loop == false) {
|
nicholas@2224
|
1585 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1586 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
|
nicholas@2224
|
1587 this.interfaceDOM.startPlayback();
|
nicholas@2224
|
1588 } else {
|
nicholas@2224
|
1589 this.outputGain.gain.setValueAtTime(0.0,startTime);
|
nicholas@2224
|
1590 }
|
nicholas@2224
|
1591 this.bufferNode.start(startTime);
|
nicholas@2224
|
1592 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
1593 }
|
nicholas@2224
|
1594 };
|
nicholas@2224
|
1595
|
nicholas@2224
|
1596 this.stop = function(stopTime) {
|
nicholas@2224
|
1597 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
nicholas@2224
|
1598 if (this.bufferNode != undefined)
|
nicholas@2224
|
1599 {
|
nicholas@2224
|
1600 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
|
nicholas@2224
|
1601 this.bufferNode.stop(stopTime);
|
nicholas@2224
|
1602 this.bufferNode = undefined;
|
nicholas@2224
|
1603 }
|
nicholas@2224
|
1604 this.outputGain.gain.value = 0.0;
|
nicholas@2224
|
1605 this.interfaceDOM.stopPlayback();
|
nicholas@2224
|
1606 };
|
nicholas@2224
|
1607
|
nicholas@2224
|
1608 this.getCurrentPosition = function() {
|
nicholas@2224
|
1609 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
1610 if (this.bufferNode != undefined) {
|
nicholas@2224
|
1611 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
|
nicholas@2224
|
1612 if (isNaN(position)){return 0;}
|
nicholas@2224
|
1613 return position;
|
nicholas@2224
|
1614 } else {
|
nicholas@2224
|
1615 return 0;
|
nicholas@2224
|
1616 }
|
nicholas@2224
|
1617 };
|
nicholas@2224
|
1618
|
nicholas@2224
|
1619 this.exportXMLDOM = function() {
|
nicholas@2224
|
1620 var file = storage.document.createElement('file');
|
nicholas@2224
|
1621 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
|
nicholas@2224
|
1622 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
|
nicholas@2224
|
1623 file.setAttribute('sampleCount',this.buffer.buffer.length);
|
nicholas@2224
|
1624 file.setAttribute('duration',this.buffer.buffer.duration);
|
nicholas@2224
|
1625 this.storeDOM.appendChild(file);
|
nicholas@2224
|
1626 if (this.specification.type != 'outside-reference') {
|
nicholas@2224
|
1627 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
nicholas@2224
|
1628 if (interfaceXML != null)
|
nicholas@2224
|
1629 {
|
nicholas@2224
|
1630 if (interfaceXML.length == undefined) {
|
nicholas@2224
|
1631 this.storeDOM.appendChild(interfaceXML);
|
nicholas@2224
|
1632 } else {
|
nicholas@2224
|
1633 for (var i=0; i<interfaceXML.length; i++)
|
nicholas@2224
|
1634 {
|
nicholas@2224
|
1635 this.storeDOM.appendChild(interfaceXML[i]);
|
nicholas@2224
|
1636 }
|
nicholas@2224
|
1637 }
|
nicholas@2224
|
1638 }
|
nicholas@2224
|
1639 if (this.commentDOM != null) {
|
nicholas@2224
|
1640 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
nicholas@2224
|
1641 }
|
nicholas@2224
|
1642 }
|
nicholas@2224
|
1643 var nodes = this.metric.exportXMLDOM();
|
nicholas@2224
|
1644 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
|
nicholas@2224
|
1645 for (var i=0; i<nodes.length; i++)
|
nicholas@2224
|
1646 {
|
nicholas@2224
|
1647 mroot.appendChild(nodes[i]);
|
nicholas@2224
|
1648 }
|
nicholas@2224
|
1649 };
|
nicholas@2224
|
1650 }
|
nicholas@2224
|
1651
|
nicholas@2224
|
1652 function timer()
|
nicholas@2224
|
1653 {
|
nicholas@2224
|
1654 /* Timer object used in audioEngine to keep track of session timings
|
nicholas@2224
|
1655 * Uses the timer of the web audio API, so sample resolution
|
nicholas@2224
|
1656 */
|
nicholas@2224
|
1657 this.testStarted = false;
|
nicholas@2224
|
1658 this.testStartTime = 0;
|
nicholas@2224
|
1659 this.testDuration = 0;
|
nicholas@2224
|
1660 this.minimumTestTime = 0; // No minimum test time
|
nicholas@2224
|
1661 this.startTest = function()
|
nicholas@2224
|
1662 {
|
nicholas@2224
|
1663 if (this.testStarted == false)
|
nicholas@2224
|
1664 {
|
nicholas@2224
|
1665 this.testStartTime = audioContext.currentTime;
|
nicholas@2224
|
1666 this.testStarted = true;
|
nicholas@2224
|
1667 this.updateTestTime();
|
nicholas@2224
|
1668 audioEngineContext.metric.initialiseTest();
|
nicholas@2224
|
1669 }
|
nicholas@2224
|
1670 };
|
nicholas@2224
|
1671 this.stopTest = function()
|
nicholas@2224
|
1672 {
|
nicholas@2224
|
1673 if (this.testStarted)
|
nicholas@2224
|
1674 {
|
nicholas@2224
|
1675 this.testDuration = this.getTestTime();
|
nicholas@2224
|
1676 this.testStarted = false;
|
nicholas@2224
|
1677 } else {
|
nicholas@2224
|
1678 console.log('ERR: Test tried to end before beginning');
|
nicholas@2224
|
1679 }
|
nicholas@2224
|
1680 };
|
nicholas@2224
|
1681 this.updateTestTime = function()
|
nicholas@2224
|
1682 {
|
nicholas@2224
|
1683 if (this.testStarted)
|
nicholas@2224
|
1684 {
|
nicholas@2224
|
1685 this.testDuration = audioContext.currentTime - this.testStartTime;
|
nicholas@2224
|
1686 }
|
nicholas@2224
|
1687 };
|
nicholas@2224
|
1688 this.getTestTime = function()
|
nicholas@2224
|
1689 {
|
nicholas@2224
|
1690 this.updateTestTime();
|
nicholas@2224
|
1691 return this.testDuration;
|
nicholas@2224
|
1692 };
|
nicholas@2224
|
1693 }
|
nicholas@2224
|
1694
|
nicholas@2224
|
1695 function sessionMetrics(engine,specification)
|
nicholas@2224
|
1696 {
|
nicholas@2224
|
1697 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
nicholas@2224
|
1698 */
|
nicholas@2224
|
1699 this.engine = engine;
|
nicholas@2224
|
1700 this.lastClicked = -1;
|
nicholas@2224
|
1701 this.data = -1;
|
nicholas@2224
|
1702 this.reset = function() {
|
nicholas@2224
|
1703 this.lastClicked = -1;
|
nicholas@2224
|
1704 this.data = -1;
|
nicholas@2224
|
1705 };
|
nicholas@2224
|
1706
|
nicholas@2224
|
1707 this.enableElementInitialPosition = false;
|
nicholas@2224
|
1708 this.enableElementListenTracker = false;
|
nicholas@2224
|
1709 this.enableElementTimer = false;
|
nicholas@2224
|
1710 this.enableElementTracker = false;
|
nicholas@2224
|
1711 this.enableFlagListenedTo = false;
|
nicholas@2224
|
1712 this.enableFlagMoved = false;
|
nicholas@2224
|
1713 this.enableTestTimer = false;
|
nicholas@2224
|
1714 // Obtain the metrics enabled
|
nicholas@2224
|
1715 for (var i=0; i<specification.metrics.enabled.length; i++)
|
nicholas@2224
|
1716 {
|
nicholas@2224
|
1717 var node = specification.metrics.enabled[i];
|
nicholas@2224
|
1718 switch(node)
|
nicholas@2224
|
1719 {
|
nicholas@2224
|
1720 case 'testTimer':
|
nicholas@2224
|
1721 this.enableTestTimer = true;
|
nicholas@2224
|
1722 break;
|
nicholas@2224
|
1723 case 'elementTimer':
|
nicholas@2224
|
1724 this.enableElementTimer = true;
|
nicholas@2224
|
1725 break;
|
nicholas@2224
|
1726 case 'elementTracker':
|
nicholas@2224
|
1727 this.enableElementTracker = true;
|
nicholas@2224
|
1728 break;
|
nicholas@2224
|
1729 case 'elementListenTracker':
|
nicholas@2224
|
1730 this.enableElementListenTracker = true;
|
nicholas@2224
|
1731 break;
|
nicholas@2224
|
1732 case 'elementInitialPosition':
|
nicholas@2224
|
1733 this.enableElementInitialPosition = true;
|
nicholas@2224
|
1734 break;
|
nicholas@2224
|
1735 case 'elementFlagListenedTo':
|
nicholas@2224
|
1736 this.enableFlagListenedTo = true;
|
nicholas@2224
|
1737 break;
|
nicholas@2224
|
1738 case 'elementFlagMoved':
|
nicholas@2224
|
1739 this.enableFlagMoved = true;
|
nicholas@2224
|
1740 break;
|
nicholas@2224
|
1741 case 'elementFlagComments':
|
nicholas@2224
|
1742 this.enableFlagComments = true;
|
nicholas@2224
|
1743 break;
|
nicholas@2224
|
1744 }
|
nicholas@2224
|
1745 }
|
nicholas@2224
|
1746 this.initialiseTest = function(){};
|
nicholas@2224
|
1747 }
|
nicholas@2224
|
1748
|
nicholas@2224
|
1749 function metricTracker(caller)
|
nicholas@2224
|
1750 {
|
nicholas@2224
|
1751 /* Custom object to track and collect metric data
|
nicholas@2224
|
1752 * Used only inside the audioObjects object.
|
nicholas@2224
|
1753 */
|
nicholas@2224
|
1754
|
nicholas@2224
|
1755 this.listenedTimer = 0;
|
nicholas@2224
|
1756 this.listenStart = 0;
|
nicholas@2224
|
1757 this.listenHold = false;
|
nicholas@2224
|
1758 this.initialPosition = -1;
|
nicholas@2224
|
1759 this.movementTracker = [];
|
nicholas@2224
|
1760 this.listenTracker =[];
|
nicholas@2224
|
1761 this.wasListenedTo = false;
|
nicholas@2224
|
1762 this.wasMoved = false;
|
nicholas@2224
|
1763 this.hasComments = false;
|
nicholas@2224
|
1764 this.parent = caller;
|
nicholas@2224
|
1765
|
nicholas@2224
|
1766 this.initialise = function(position)
|
nicholas@2224
|
1767 {
|
nicholas@2224
|
1768 if (this.initialPosition == -1) {
|
nicholas@2224
|
1769 this.initialPosition = position;
|
nicholas@2224
|
1770 this.moved(0,position);
|
nicholas@2224
|
1771 }
|
nicholas@2224
|
1772 };
|
nicholas@2224
|
1773
|
nicholas@2224
|
1774 this.moved = function(time,position)
|
nicholas@2224
|
1775 {
|
nicholas@2224
|
1776 if (time > 0) {this.wasMoved = true;}
|
nicholas@2224
|
1777 this.movementTracker[this.movementTracker.length] = [time, position];
|
nicholas@2224
|
1778 };
|
nicholas@2224
|
1779
|
nicholas@2224
|
1780 this.startListening = function(time)
|
nicholas@2224
|
1781 {
|
nicholas@2224
|
1782 if (this.listenHold == false)
|
nicholas@2224
|
1783 {
|
nicholas@2224
|
1784 this.wasListenedTo = true;
|
nicholas@2224
|
1785 this.listenStart = time;
|
nicholas@2224
|
1786 this.listenHold = true;
|
nicholas@2224
|
1787
|
nicholas@2224
|
1788 var evnt = document.createElement('event');
|
nicholas@2224
|
1789 var testTime = document.createElement('testTime');
|
nicholas@2224
|
1790 testTime.setAttribute('start',time);
|
nicholas@2224
|
1791 var bufferTime = document.createElement('bufferTime');
|
nicholas@2224
|
1792 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
|
nicholas@2224
|
1793 evnt.appendChild(testTime);
|
nicholas@2224
|
1794 evnt.appendChild(bufferTime);
|
nicholas@2224
|
1795 this.listenTracker.push(evnt);
|
nicholas@2224
|
1796
|
nicholas@2224
|
1797 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2224
|
1798 }
|
nicholas@2224
|
1799 };
|
nicholas@2224
|
1800
|
nicholas@2224
|
1801 this.stopListening = function(time,bufferStopTime)
|
nicholas@2224
|
1802 {
|
nicholas@2224
|
1803 if (this.listenHold == true)
|
nicholas@2224
|
1804 {
|
nicholas@2224
|
1805 var diff = time - this.listenStart;
|
nicholas@2224
|
1806 this.listenedTimer += (diff);
|
nicholas@2224
|
1807 this.listenStart = 0;
|
nicholas@2224
|
1808 this.listenHold = false;
|
nicholas@2224
|
1809
|
nicholas@2224
|
1810 var evnt = this.listenTracker[this.listenTracker.length-1];
|
nicholas@2224
|
1811 var testTime = evnt.getElementsByTagName('testTime')[0];
|
nicholas@2224
|
1812 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
nicholas@2224
|
1813 testTime.setAttribute('stop',time);
|
nicholas@2224
|
1814 if (bufferStopTime == undefined) {
|
nicholas@2224
|
1815 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
|
nicholas@2224
|
1816 } else {
|
nicholas@2224
|
1817 bufferTime.setAttribute('stop',bufferStopTime);
|
nicholas@2224
|
1818 }
|
nicholas@2224
|
1819 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2224
|
1820 }
|
nicholas@2224
|
1821 };
|
nicholas@2224
|
1822
|
nicholas@2224
|
1823 this.exportXMLDOM = function() {
|
nicholas@2224
|
1824 var storeDOM = [];
|
nicholas@2224
|
1825 if (audioEngineContext.metric.enableElementTimer) {
|
nicholas@2224
|
1826 var mElementTimer = storage.document.createElement('metricresult');
|
nicholas@2224
|
1827 mElementTimer.setAttribute('name','enableElementTimer');
|
nicholas@2224
|
1828 mElementTimer.textContent = this.listenedTimer;
|
nicholas@2224
|
1829 storeDOM.push(mElementTimer);
|
nicholas@2224
|
1830 }
|
nicholas@2224
|
1831 if (audioEngineContext.metric.enableElementTracker) {
|
b@2281
|
1832 var elementTrackerFull = storage.document.createElement('metricresult');
|
nicholas@2224
|
1833 elementTrackerFull.setAttribute('name','elementTrackerFull');
|
nicholas@2224
|
1834 for (var k=0; k<this.movementTracker.length; k++)
|
nicholas@2224
|
1835 {
|
nicholas@2224
|
1836 var timePos = storage.document.createElement('movement');
|
nicholas@2224
|
1837 timePos.setAttribute("time",this.movementTracker[k][0]);
|
nicholas@2224
|
1838 timePos.setAttribute("value",this.movementTracker[k][1]);
|
nicholas@2224
|
1839 elementTrackerFull.appendChild(timePos);
|
nicholas@2224
|
1840 }
|
nicholas@2224
|
1841 storeDOM.push(elementTrackerFull);
|
nicholas@2224
|
1842 }
|
nicholas@2224
|
1843 if (audioEngineContext.metric.enableElementListenTracker) {
|
b@2281
|
1844 var elementListenTracker = storage.document.createElement('metricresult');
|
nicholas@2224
|
1845 elementListenTracker.setAttribute('name','elementListenTracker');
|
nicholas@2224
|
1846 for (var k=0; k<this.listenTracker.length; k++) {
|
nicholas@2224
|
1847 elementListenTracker.appendChild(this.listenTracker[k]);
|
nicholas@2224
|
1848 }
|
nicholas@2224
|
1849 storeDOM.push(elementListenTracker);
|
nicholas@2224
|
1850 }
|
nicholas@2224
|
1851 if (audioEngineContext.metric.enableElementInitialPosition) {
|
b@2281
|
1852 var elementInitial = storage.document.createElement('metricresult');
|
nicholas@2224
|
1853 elementInitial.setAttribute('name','elementInitialPosition');
|
nicholas@2224
|
1854 elementInitial.textContent = this.initialPosition;
|
nicholas@2224
|
1855 storeDOM.push(elementInitial);
|
nicholas@2224
|
1856 }
|
nicholas@2224
|
1857 if (audioEngineContext.metric.enableFlagListenedTo) {
|
b@2281
|
1858 var flagListenedTo = storage.document.createElement('metricresult');
|
nicholas@2224
|
1859 flagListenedTo.setAttribute('name','elementFlagListenedTo');
|
nicholas@2224
|
1860 flagListenedTo.textContent = this.wasListenedTo;
|
nicholas@2224
|
1861 storeDOM.push(flagListenedTo);
|
nicholas@2224
|
1862 }
|
nicholas@2224
|
1863 if (audioEngineContext.metric.enableFlagMoved) {
|
b@2281
|
1864 var flagMoved = storage.document.createElement('metricresult');
|
nicholas@2224
|
1865 flagMoved.setAttribute('name','elementFlagMoved');
|
nicholas@2224
|
1866 flagMoved.textContent = this.wasMoved;
|
nicholas@2224
|
1867 storeDOM.push(flagMoved);
|
nicholas@2224
|
1868 }
|
nicholas@2224
|
1869 if (audioEngineContext.metric.enableFlagComments) {
|
b@2281
|
1870 var flagComments = storage.document.createElement('metricresult');
|
nicholas@2224
|
1871 flagComments.setAttribute('name','elementFlagComments');
|
nicholas@2224
|
1872 if (this.parent.commentDOM == null)
|
nicholas@2224
|
1873 {flag.textContent = 'false';}
|
nicholas@2224
|
1874 else if (this.parent.commentDOM.textContent.length == 0)
|
nicholas@2224
|
1875 {flag.textContent = 'false';}
|
nicholas@2224
|
1876 else
|
nicholas@2224
|
1877 {flag.textContet = 'true';}
|
nicholas@2224
|
1878 storeDOM.push(flagComments);
|
nicholas@2224
|
1879 }
|
nicholas@2224
|
1880 return storeDOM;
|
nicholas@2224
|
1881 };
|
nicholas@2224
|
1882 }
|
nicholas@2224
|
1883
|
nicholas@2224
|
1884 function Interface(specificationObject) {
|
nicholas@2224
|
1885 // This handles the bindings between the interface and the audioEngineContext;
|
nicholas@2224
|
1886 this.specification = specificationObject;
|
nicholas@2224
|
1887 this.insertPoint = document.getElementById("topLevelBody");
|
nicholas@2224
|
1888
|
nicholas@2224
|
1889 this.newPage = function(audioHolderObject,store)
|
nicholas@2224
|
1890 {
|
nicholas@2224
|
1891 audioEngineContext.newTestPage(audioHolderObject,store);
|
nicholas@2224
|
1892 interfaceContext.commentBoxes.deleteCommentBoxes();
|
nicholas@2224
|
1893 interfaceContext.deleteCommentQuestions();
|
nicholas@2224
|
1894 loadTest(audioHolderObject,store);
|
nicholas@2224
|
1895 };
|
nicholas@2224
|
1896
|
nicholas@2224
|
1897 // Bounded by interface!!
|
nicholas@2224
|
1898 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
nicholas@2224
|
1899 // For example, APE returns the slider position normalised in a <value> tag.
|
nicholas@2224
|
1900 this.interfaceObjects = [];
|
nicholas@2224
|
1901 this.interfaceObject = function(){};
|
nicholas@2224
|
1902
|
nicholas@2224
|
1903 this.resizeWindow = function(event)
|
nicholas@2224
|
1904 {
|
nicholas@2224
|
1905 popup.resize(event);
|
nicholas@2224
|
1906 for(var i=0; i<this.commentBoxes.length; i++)
|
nicholas@2224
|
1907 {this.commentBoxes[i].resize();}
|
nicholas@2224
|
1908 for(var i=0; i<this.commentQuestions.length; i++)
|
nicholas@2224
|
1909 {this.commentQuestions[i].resize();}
|
nicholas@2224
|
1910 try
|
nicholas@2224
|
1911 {
|
nicholas@2224
|
1912 resizeWindow(event);
|
nicholas@2224
|
1913 }
|
nicholas@2224
|
1914 catch(err)
|
nicholas@2224
|
1915 {
|
nicholas@2224
|
1916 console.log("Warning - Interface does not have Resize option");
|
nicholas@2224
|
1917 console.log(err);
|
nicholas@2224
|
1918 }
|
nicholas@2224
|
1919 };
|
nicholas@2224
|
1920
|
nicholas@2224
|
1921 this.returnNavigator = function()
|
nicholas@2224
|
1922 {
|
nicholas@2224
|
1923 var node = storage.document.createElement("navigator");
|
nicholas@2224
|
1924 var platform = storage.document.createElement("platform");
|
nicholas@2224
|
1925 platform.textContent = navigator.platform;
|
nicholas@2224
|
1926 var vendor = storage.document.createElement("vendor");
|
nicholas@2224
|
1927 vendor.textContent = navigator.vendor;
|
nicholas@2224
|
1928 var userAgent = storage.document.createElement("uagent");
|
nicholas@2224
|
1929 userAgent.textContent = navigator.userAgent;
|
nicholas@2224
|
1930 var screen = storage.document.createElement("window");
|
nicholas@2224
|
1931 screen.setAttribute('innerWidth',window.innerWidth);
|
nicholas@2224
|
1932 screen.setAttribute('innerHeight',window.innerHeight);
|
nicholas@2224
|
1933 node.appendChild(platform);
|
nicholas@2224
|
1934 node.appendChild(vendor);
|
nicholas@2224
|
1935 node.appendChild(userAgent);
|
nicholas@2224
|
1936 node.appendChild(screen);
|
nicholas@2224
|
1937 return node;
|
nicholas@2224
|
1938 };
|
nicholas@2224
|
1939
|
nicholas@2224
|
1940 this.returnDateNode = function()
|
nicholas@2224
|
1941 {
|
nicholas@2224
|
1942 // Create an XML Node for the Date and Time a test was conducted
|
nicholas@2224
|
1943 // Structure is
|
nicholas@2224
|
1944 // <datetime>
|
nicholas@2224
|
1945 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
nicholas@2224
|
1946 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
nicholas@2224
|
1947 // </datetime>
|
nicholas@2224
|
1948 var dateTime = new Date();
|
nicholas@2224
|
1949 var hold = storage.document.createElement("datetime");
|
nicholas@2224
|
1950 var date = storage.document.createElement("date");
|
nicholas@2224
|
1951 var time = storage.document.createElement("time");
|
nicholas@2224
|
1952 date.setAttribute('year',dateTime.getFullYear());
|
nicholas@2224
|
1953 date.setAttribute('month',dateTime.getMonth()+1);
|
nicholas@2224
|
1954 date.setAttribute('day',dateTime.getDate());
|
nicholas@2224
|
1955 time.setAttribute('hour',dateTime.getHours());
|
nicholas@2224
|
1956 time.setAttribute('minute',dateTime.getMinutes());
|
nicholas@2224
|
1957 time.setAttribute('secs',dateTime.getSeconds());
|
nicholas@2224
|
1958
|
nicholas@2224
|
1959 hold.appendChild(date);
|
nicholas@2224
|
1960 hold.appendChild(time);
|
nicholas@2224
|
1961 return hold;
|
nicholas@2224
|
1962
|
nicholas@2224
|
1963 }
|
nicholas@2224
|
1964
|
nicholas@2224
|
1965 this.commentBoxes = new function() {
|
nicholas@2224
|
1966 this.boxes = [];
|
nicholas@2224
|
1967 this.injectPoint = null;
|
nicholas@2224
|
1968 this.elementCommentBox = function(audioObject) {
|
nicholas@2224
|
1969 var element = audioObject.specification;
|
nicholas@2224
|
1970 this.audioObject = audioObject;
|
nicholas@2224
|
1971 this.id = audioObject.id;
|
nicholas@2224
|
1972 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
1973 // Create document objects to hold the comment boxes
|
nicholas@2224
|
1974 this.trackComment = document.createElement('div');
|
nicholas@2224
|
1975 this.trackComment.className = 'comment-div';
|
nicholas@2224
|
1976 this.trackComment.id = 'comment-div-'+audioObject.id;
|
nicholas@2224
|
1977 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
1978 this.trackString = document.createElement('span');
|
nicholas@2224
|
1979 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
1980 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
1981 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
1982 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
1983 this.trackCommentBox.cols = '100';
|
nicholas@2224
|
1984 this.trackCommentBox.name = 'trackComment'+audioObject.id;
|
nicholas@2224
|
1985 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
1986 var br = document.createElement('br');
|
nicholas@2224
|
1987 // Add to the holder.
|
nicholas@2224
|
1988 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
1989 this.trackComment.appendChild(br);
|
nicholas@2224
|
1990 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
1991
|
nicholas@2224
|
1992 this.exportXMLDOM = function() {
|
nicholas@2224
|
1993 var root = document.createElement('comment');
|
nicholas@2224
|
1994 var question = document.createElement('question');
|
nicholas@2224
|
1995 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
1996 var response = document.createElement('response');
|
nicholas@2224
|
1997 response.textContent = this.trackCommentBox.value;
|
nicholas@2224
|
1998 console.log("Comment frag-"+this.id+": "+response.textContent);
|
nicholas@2224
|
1999 root.appendChild(question);
|
nicholas@2224
|
2000 root.appendChild(response);
|
nicholas@2224
|
2001 return root;
|
nicholas@2224
|
2002 };
|
nicholas@2224
|
2003 this.resize = function()
|
nicholas@2224
|
2004 {
|
nicholas@2224
|
2005 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2006 if (boxwidth >= 600)
|
nicholas@2224
|
2007 {
|
nicholas@2224
|
2008 boxwidth = 600;
|
nicholas@2224
|
2009 }
|
nicholas@2224
|
2010 else if (boxwidth < 400)
|
nicholas@2224
|
2011 {
|
nicholas@2224
|
2012 boxwidth = 400;
|
nicholas@2224
|
2013 }
|
nicholas@2224
|
2014 this.trackComment.style.width = boxwidth+"px";
|
nicholas@2224
|
2015 this.trackCommentBox.style.width = boxwidth-6+"px";
|
nicholas@2224
|
2016 };
|
nicholas@2224
|
2017 this.resize();
|
nicholas@2224
|
2018 };
|
nicholas@2224
|
2019 this.createCommentBox = function(audioObject) {
|
nicholas@2224
|
2020 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2021 this.boxes.push(node);
|
nicholas@2224
|
2022 audioObject.commentDOM = node;
|
nicholas@2224
|
2023 return node;
|
nicholas@2224
|
2024 };
|
nicholas@2224
|
2025 this.sortCommentBoxes = function() {
|
nicholas@2224
|
2026 this.boxes.sort(function(a,b){return a.id - b.id;});
|
nicholas@2224
|
2027 };
|
nicholas@2224
|
2028
|
nicholas@2224
|
2029 this.showCommentBoxes = function(inject, sort) {
|
nicholas@2224
|
2030 this.injectPoint = inject;
|
nicholas@2224
|
2031 if (sort) {this.sortCommentBoxes();}
|
nicholas@2224
|
2032 for (var box of this.boxes) {
|
nicholas@2224
|
2033 inject.appendChild(box.trackComment);
|
nicholas@2224
|
2034 }
|
nicholas@2224
|
2035 };
|
nicholas@2224
|
2036
|
nicholas@2224
|
2037 this.deleteCommentBoxes = function() {
|
nicholas@2224
|
2038 if (this.injectPoint != null) {
|
nicholas@2224
|
2039 for (var box of this.boxes) {
|
nicholas@2224
|
2040 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2224
|
2041 }
|
nicholas@2224
|
2042 this.injectPoint = null;
|
nicholas@2224
|
2043 }
|
nicholas@2224
|
2044 this.boxes = [];
|
nicholas@2224
|
2045 };
|
nicholas@2224
|
2046 }
|
nicholas@2224
|
2047
|
nicholas@2224
|
2048 this.commentQuestions = [];
|
nicholas@2224
|
2049
|
nicholas@2224
|
2050 this.commentBox = function(commentQuestion) {
|
nicholas@2224
|
2051 this.specification = commentQuestion;
|
nicholas@2224
|
2052 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2053 this.holder = document.createElement('div');
|
nicholas@2224
|
2054 this.holder.className = 'comment-div';
|
nicholas@2224
|
2055 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2056 this.string = document.createElement('span');
|
nicholas@2224
|
2057 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2058 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2059 this.textArea = document.createElement('textarea');
|
nicholas@2224
|
2060 this.textArea.rows = '4';
|
nicholas@2224
|
2061 this.textArea.cols = '100';
|
nicholas@2224
|
2062 this.textArea.className = 'trackComment';
|
nicholas@2224
|
2063 var br = document.createElement('br');
|
nicholas@2224
|
2064 // Add to the holder.
|
nicholas@2224
|
2065 this.holder.appendChild(this.string);
|
nicholas@2224
|
2066 this.holder.appendChild(br);
|
nicholas@2224
|
2067 this.holder.appendChild(this.textArea);
|
nicholas@2224
|
2068
|
nicholas@2224
|
2069 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2070 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2071 root.id = this.specification.id;
|
nicholas@2224
|
2072 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2073 console.log("Question: "+this.string.textContent);
|
nicholas@2224
|
2074 console.log("Response: "+root.textContent);
|
nicholas@2224
|
2075 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2076 question.textContent = this.string.textContent;
|
nicholas@2224
|
2077 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2078 response.textContent = this.textArea.value;
|
nicholas@2224
|
2079 root.appendChild(question);
|
nicholas@2224
|
2080 root.appendChild(response);
|
nicholas@2224
|
2081 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2082 return root;
|
nicholas@2224
|
2083 };
|
nicholas@2224
|
2084 this.resize = function()
|
nicholas@2224
|
2085 {
|
nicholas@2224
|
2086 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2087 if (boxwidth >= 600)
|
nicholas@2224
|
2088 {
|
nicholas@2224
|
2089 boxwidth = 600;
|
nicholas@2224
|
2090 }
|
nicholas@2224
|
2091 else if (boxwidth < 400)
|
nicholas@2224
|
2092 {
|
nicholas@2224
|
2093 boxwidth = 400;
|
nicholas@2224
|
2094 }
|
nicholas@2224
|
2095 this.holder.style.width = boxwidth+"px";
|
nicholas@2224
|
2096 this.textArea.style.width = boxwidth-6+"px";
|
nicholas@2224
|
2097 };
|
nicholas@2224
|
2098 this.resize();
|
nicholas@2224
|
2099 };
|
nicholas@2224
|
2100
|
nicholas@2224
|
2101 this.radioBox = function(commentQuestion) {
|
nicholas@2224
|
2102 this.specification = commentQuestion;
|
nicholas@2224
|
2103 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2104 this.holder = document.createElement('div');
|
nicholas@2224
|
2105 this.holder.className = 'comment-div';
|
nicholas@2224
|
2106 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2107 this.string = document.createElement('span');
|
nicholas@2224
|
2108 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2109 var br = document.createElement('br');
|
nicholas@2224
|
2110 // Add to the holder.
|
nicholas@2224
|
2111 this.holder.appendChild(this.string);
|
nicholas@2224
|
2112 this.holder.appendChild(br);
|
nicholas@2224
|
2113 this.options = [];
|
nicholas@2224
|
2114 this.inputs = document.createElement('div');
|
nicholas@2224
|
2115 this.span = document.createElement('div');
|
nicholas@2224
|
2116 this.inputs.align = 'center';
|
nicholas@2224
|
2117 this.inputs.style.marginLeft = '12px';
|
nicholas@2294
|
2118 this.inputs.className = "comment-radio-inputs-holder";
|
nicholas@2224
|
2119 this.span.style.marginLeft = '12px';
|
nicholas@2224
|
2120 this.span.align = 'center';
|
nicholas@2224
|
2121 this.span.style.marginTop = '15px';
|
nicholas@2294
|
2122 this.span.className = "comment-radio-span-holder";
|
nicholas@2224
|
2123
|
nicholas@2224
|
2124 var optCount = commentQuestion.options.length;
|
nicholas@2224
|
2125 for (var optNode of commentQuestion.options)
|
nicholas@2224
|
2126 {
|
nicholas@2224
|
2127 var div = document.createElement('div');
|
nicholas@2224
|
2128 div.style.width = '80px';
|
nicholas@2224
|
2129 div.style.float = 'left';
|
nicholas@2224
|
2130 var input = document.createElement('input');
|
nicholas@2224
|
2131 input.type = 'radio';
|
nicholas@2224
|
2132 input.name = commentQuestion.id;
|
nicholas@2224
|
2133 input.setAttribute('setvalue',optNode.name);
|
nicholas@2224
|
2134 input.className = 'comment-radio';
|
nicholas@2224
|
2135 div.appendChild(input);
|
nicholas@2224
|
2136 this.inputs.appendChild(div);
|
nicholas@2224
|
2137
|
nicholas@2224
|
2138
|
nicholas@2224
|
2139 div = document.createElement('div');
|
nicholas@2224
|
2140 div.style.width = '80px';
|
nicholas@2224
|
2141 div.style.float = 'left';
|
nicholas@2224
|
2142 div.align = 'center';
|
nicholas@2224
|
2143 var span = document.createElement('span');
|
nicholas@2224
|
2144 span.textContent = optNode.text;
|
nicholas@2224
|
2145 span.className = 'comment-radio-span';
|
nicholas@2224
|
2146 div.appendChild(span);
|
nicholas@2224
|
2147 this.span.appendChild(div);
|
nicholas@2224
|
2148 this.options.push(input);
|
nicholas@2224
|
2149 }
|
nicholas@2224
|
2150 this.holder.appendChild(this.span);
|
nicholas@2224
|
2151 this.holder.appendChild(this.inputs);
|
nicholas@2224
|
2152
|
nicholas@2224
|
2153 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2154 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2155 root.id = this.specification.id;
|
nicholas@2224
|
2156 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2157 var question = document.createElement('question');
|
nicholas@2224
|
2158 question.textContent = this.string.textContent;
|
nicholas@2224
|
2159 var response = document.createElement('response');
|
nicholas@2224
|
2160 var i=0;
|
nicholas@2224
|
2161 while(this.options[i].checked == false) {
|
nicholas@2224
|
2162 i++;
|
nicholas@2224
|
2163 if (i >= this.options.length) {
|
nicholas@2224
|
2164 break;
|
nicholas@2224
|
2165 }
|
nicholas@2224
|
2166 }
|
nicholas@2224
|
2167 if (i >= this.options.length) {
|
nicholas@2224
|
2168 response.textContent = 'null';
|
nicholas@2224
|
2169 } else {
|
nicholas@2224
|
2170 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2224
|
2171 response.setAttribute('number',i);
|
nicholas@2224
|
2172 }
|
nicholas@2224
|
2173 console.log('Comment: '+question.textContent);
|
nicholas@2224
|
2174 console.log('Response: '+response.textContent);
|
nicholas@2224
|
2175 root.appendChild(question);
|
nicholas@2224
|
2176 root.appendChild(response);
|
nicholas@2224
|
2177 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2178 return root;
|
nicholas@2224
|
2179 };
|
nicholas@2224
|
2180 this.resize = function()
|
nicholas@2224
|
2181 {
|
nicholas@2224
|
2182 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2183 if (boxwidth >= 600)
|
nicholas@2224
|
2184 {
|
nicholas@2224
|
2185 boxwidth = 600;
|
nicholas@2224
|
2186 }
|
nicholas@2224
|
2187 else if (boxwidth < 400)
|
nicholas@2224
|
2188 {
|
nicholas@2224
|
2189 boxwidth = 400;
|
nicholas@2224
|
2190 }
|
nicholas@2224
|
2191 this.holder.style.width = boxwidth+"px";
|
nicholas@2294
|
2192 var text = this.holder.getElementsByClassName("comment-radio-span-holder")[0];
|
nicholas@2294
|
2193 var options = this.holder.getElementsByClassName("comment-radio-inputs-holder")[0];
|
nicholas@2294
|
2194 var optCount = options.childElementCount;
|
nicholas@2224
|
2195 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
nicholas@2224
|
2196 var options = options.firstChild;
|
nicholas@2224
|
2197 var text = text.firstChild;
|
nicholas@2224
|
2198 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2199 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2200 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2201 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2202 while(options.nextSibling != undefined)
|
nicholas@2224
|
2203 {
|
nicholas@2224
|
2204 options = options.nextSibling;
|
nicholas@2224
|
2205 text = text.nextSibling;
|
nicholas@2224
|
2206 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2207 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2208 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2209 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2210 }
|
nicholas@2224
|
2211 };
|
nicholas@2224
|
2212 this.resize();
|
nicholas@2224
|
2213 };
|
nicholas@2224
|
2214
|
nicholas@2224
|
2215 this.checkboxBox = function(commentQuestion) {
|
nicholas@2224
|
2216 this.specification = commentQuestion;
|
nicholas@2224
|
2217 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2218 this.holder = document.createElement('div');
|
nicholas@2224
|
2219 this.holder.className = 'comment-div';
|
nicholas@2224
|
2220 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2221 this.string = document.createElement('span');
|
nicholas@2224
|
2222 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2223 var br = document.createElement('br');
|
nicholas@2224
|
2224 // Add to the holder.
|
nicholas@2224
|
2225 this.holder.appendChild(this.string);
|
nicholas@2224
|
2226 this.holder.appendChild(br);
|
nicholas@2224
|
2227 this.options = [];
|
nicholas@2224
|
2228 this.inputs = document.createElement('div');
|
nicholas@2224
|
2229 this.span = document.createElement('div');
|
nicholas@2224
|
2230 this.inputs.align = 'center';
|
nicholas@2224
|
2231 this.inputs.style.marginLeft = '12px';
|
nicholas@2294
|
2232 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2224
|
2233 this.span.style.marginLeft = '12px';
|
nicholas@2224
|
2234 this.span.align = 'center';
|
nicholas@2224
|
2235 this.span.style.marginTop = '15px';
|
nicholas@2294
|
2236 this.span.className = "comment-checkbox-span-holder";
|
nicholas@2224
|
2237
|
nicholas@2224
|
2238 var optCount = commentQuestion.options.length;
|
nicholas@2224
|
2239 for (var i=0; i<optCount; i++)
|
nicholas@2224
|
2240 {
|
nicholas@2224
|
2241 var div = document.createElement('div');
|
nicholas@2224
|
2242 div.style.width = '80px';
|
nicholas@2224
|
2243 div.style.float = 'left';
|
nicholas@2224
|
2244 var input = document.createElement('input');
|
nicholas@2224
|
2245 input.type = 'checkbox';
|
nicholas@2224
|
2246 input.name = commentQuestion.id;
|
nicholas@2224
|
2247 input.setAttribute('setvalue',commentQuestion.options[i].name);
|
nicholas@2224
|
2248 input.className = 'comment-radio';
|
nicholas@2224
|
2249 div.appendChild(input);
|
nicholas@2224
|
2250 this.inputs.appendChild(div);
|
nicholas@2224
|
2251
|
nicholas@2224
|
2252
|
nicholas@2224
|
2253 div = document.createElement('div');
|
nicholas@2224
|
2254 div.style.width = '80px';
|
nicholas@2224
|
2255 div.style.float = 'left';
|
nicholas@2224
|
2256 div.align = 'center';
|
nicholas@2224
|
2257 var span = document.createElement('span');
|
nicholas@2224
|
2258 span.textContent = commentQuestion.options[i].text;
|
nicholas@2224
|
2259 span.className = 'comment-radio-span';
|
nicholas@2224
|
2260 div.appendChild(span);
|
nicholas@2224
|
2261 this.span.appendChild(div);
|
nicholas@2224
|
2262 this.options.push(input);
|
nicholas@2224
|
2263 }
|
nicholas@2224
|
2264 this.holder.appendChild(this.span);
|
nicholas@2224
|
2265 this.holder.appendChild(this.inputs);
|
nicholas@2224
|
2266
|
nicholas@2224
|
2267 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2268 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2269 root.id = this.specification.id;
|
nicholas@2224
|
2270 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2271 var question = document.createElement('question');
|
nicholas@2224
|
2272 question.textContent = this.string.textContent;
|
nicholas@2224
|
2273 root.appendChild(question);
|
nicholas@2224
|
2274 console.log('Comment: '+question.textContent);
|
nicholas@2224
|
2275 for (var i=0; i<this.options.length; i++) {
|
nicholas@2224
|
2276 var response = document.createElement('response');
|
nicholas@2224
|
2277 response.textContent = this.options[i].checked;
|
nicholas@2224
|
2278 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
|
nicholas@2224
|
2279 root.appendChild(response);
|
nicholas@2224
|
2280 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
|
nicholas@2224
|
2281 }
|
nicholas@2224
|
2282 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2283 return root;
|
nicholas@2224
|
2284 };
|
nicholas@2224
|
2285 this.resize = function()
|
nicholas@2224
|
2286 {
|
nicholas@2224
|
2287 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2288 if (boxwidth >= 600)
|
nicholas@2224
|
2289 {
|
nicholas@2224
|
2290 boxwidth = 600;
|
nicholas@2224
|
2291 }
|
nicholas@2224
|
2292 else if (boxwidth < 400)
|
nicholas@2224
|
2293 {
|
nicholas@2224
|
2294 boxwidth = 400;
|
nicholas@2224
|
2295 }
|
nicholas@2224
|
2296 this.holder.style.width = boxwidth+"px";
|
nicholas@2294
|
2297 var text = this.holder.getElementsByClassName("comment-checkbox-span-holder")[0];
|
nicholas@2294
|
2298 var options = this.holder.getElementsByClassName("comment-checkbox-inputs-holder")[0];
|
nicholas@2294
|
2299 var optCount = options.childElementCount;
|
nicholas@2224
|
2300 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
nicholas@2224
|
2301 var options = options.firstChild;
|
nicholas@2224
|
2302 var text = text.firstChild;
|
nicholas@2224
|
2303 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2304 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2305 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2306 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2307 while(options.nextSibling != undefined)
|
nicholas@2224
|
2308 {
|
nicholas@2224
|
2309 options = options.nextSibling;
|
nicholas@2224
|
2310 text = text.nextSibling;
|
nicholas@2224
|
2311 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2312 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2313 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2314 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2315 }
|
nicholas@2224
|
2316 };
|
nicholas@2224
|
2317 this.resize();
|
nicholas@2224
|
2318 };
|
nicholas@2224
|
2319
|
nicholas@2224
|
2320 this.createCommentQuestion = function(element) {
|
nicholas@2224
|
2321 var node;
|
nicholas@2224
|
2322 if (element.type == 'question') {
|
nicholas@2224
|
2323 node = new this.commentBox(element);
|
nicholas@2224
|
2324 } else if (element.type == 'radio') {
|
nicholas@2224
|
2325 node = new this.radioBox(element);
|
nicholas@2224
|
2326 } else if (element.type == 'checkbox') {
|
nicholas@2224
|
2327 node = new this.checkboxBox(element);
|
nicholas@2224
|
2328 }
|
nicholas@2224
|
2329 this.commentQuestions.push(node);
|
nicholas@2224
|
2330 return node;
|
nicholas@2224
|
2331 };
|
nicholas@2224
|
2332
|
nicholas@2224
|
2333 this.deleteCommentQuestions = function()
|
nicholas@2224
|
2334 {
|
nicholas@2224
|
2335 this.commentQuestions = [];
|
nicholas@2224
|
2336 };
|
nicholas@2224
|
2337
|
nicholas@2224
|
2338 this.outsideReferenceDOM = function(audioObject,index,inject)
|
nicholas@2224
|
2339 {
|
nicholas@2224
|
2340 this.parent = audioObject;
|
nicholas@2224
|
2341 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2342 this.outsideReferenceHolder.id = 'outside-reference';
|
nicholas@2224
|
2343 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2224
|
2344 this.outsideReferenceHolder.setAttribute('track-id',index);
|
nicholas@2224
|
2345 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2346 this.outsideReferenceHolder.disabled = true;
|
nicholas@2224
|
2347
|
nicholas@2224
|
2348 this.outsideReferenceHolder.onclick = function(event)
|
nicholas@2224
|
2349 {
|
nicholas@2224
|
2350 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
|
nicholas@2224
|
2351 };
|
nicholas@2224
|
2352 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2224
|
2353 this.enable = function()
|
nicholas@2224
|
2354 {
|
nicholas@2224
|
2355 if (this.parent.state == 1)
|
nicholas@2224
|
2356 {
|
nicholas@2224
|
2357 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2358 }
|
nicholas@2224
|
2359 };
|
nicholas@2224
|
2360 this.updateLoading = function(progress)
|
nicholas@2224
|
2361 {
|
nicholas@2224
|
2362 if (progress != 100)
|
nicholas@2224
|
2363 {
|
nicholas@2224
|
2364 progress = String(progress);
|
nicholas@2224
|
2365 progress = progress.split('.')[0];
|
nicholas@2224
|
2366 this.outsideReferenceHolder.textContent = progress+'%';
|
nicholas@2224
|
2367 } else {
|
nicholas@2224
|
2368 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2369 }
|
nicholas@2224
|
2370 };
|
nicholas@2224
|
2371 this.startPlayback = function()
|
nicholas@2224
|
2372 {
|
nicholas@2224
|
2373 // Called when playback has begun
|
nicholas@2224
|
2374 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2375 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2376 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2377 };
|
nicholas@2224
|
2378 this.stopPlayback = function()
|
nicholas@2224
|
2379 {
|
nicholas@2224
|
2380 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2381 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2382 };
|
nicholas@2224
|
2383 this.exportXMLDOM = function(audioObject)
|
nicholas@2224
|
2384 {
|
nicholas@2224
|
2385 return null;
|
nicholas@2224
|
2386 };
|
nicholas@2224
|
2387 this.getValue = function()
|
nicholas@2224
|
2388 {
|
nicholas@2224
|
2389 return 0;
|
nicholas@2224
|
2390 };
|
nicholas@2224
|
2391 this.getPresentedId = function()
|
nicholas@2224
|
2392 {
|
nicholas@2224
|
2393 return 'Reference';
|
nicholas@2224
|
2394 };
|
nicholas@2224
|
2395 this.canMove = function()
|
nicholas@2224
|
2396 {
|
nicholas@2224
|
2397 return false;
|
nicholas@2224
|
2398 };
|
nicholas@2224
|
2399 this.error = function() {
|
nicholas@2224
|
2400 // audioObject has an error!!
|
nicholas@2224
|
2401 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2402 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2224
|
2403 }
|
nicholas@2224
|
2404 }
|
nicholas@2224
|
2405
|
nicholas@2224
|
2406 this.playhead = new function()
|
nicholas@2224
|
2407 {
|
nicholas@2224
|
2408 this.object = document.createElement('div');
|
nicholas@2224
|
2409 this.object.className = 'playhead';
|
nicholas@2224
|
2410 this.object.align = 'left';
|
nicholas@2224
|
2411 var curTime = document.createElement('div');
|
nicholas@2224
|
2412 curTime.style.width = '50px';
|
nicholas@2224
|
2413 this.curTimeSpan = document.createElement('span');
|
nicholas@2224
|
2414 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2415 curTime.appendChild(this.curTimeSpan);
|
nicholas@2224
|
2416 this.object.appendChild(curTime);
|
nicholas@2224
|
2417 this.scrubberTrack = document.createElement('div');
|
nicholas@2224
|
2418 this.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2224
|
2419
|
nicholas@2224
|
2420 this.scrubberHead = document.createElement('div');
|
nicholas@2224
|
2421 this.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2224
|
2422 this.scrubberTrack.appendChild(this.scrubberHead);
|
nicholas@2224
|
2423 this.object.appendChild(this.scrubberTrack);
|
nicholas@2224
|
2424
|
nicholas@2224
|
2425 this.timePerPixel = 0;
|
nicholas@2224
|
2426 this.maxTime = 0;
|
nicholas@2224
|
2427
|
nicholas@2224
|
2428 this.playbackObject;
|
nicholas@2224
|
2429
|
nicholas@2224
|
2430 this.setTimePerPixel = function(audioObject) {
|
nicholas@2224
|
2431 //maxTime must be in seconds
|
nicholas@2224
|
2432 this.playbackObject = audioObject;
|
nicholas@2224
|
2433 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2224
|
2434 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2224
|
2435 this.timePerPixel = this.maxTime/490;
|
nicholas@2224
|
2436 if (this.maxTime < 60) {
|
nicholas@2224
|
2437 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2438 } else {
|
nicholas@2224
|
2439 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2440 }
|
nicholas@2224
|
2441 };
|
nicholas@2224
|
2442
|
nicholas@2224
|
2443 this.update = function() {
|
nicholas@2224
|
2444 // Update the playhead position, startPlay must be called
|
nicholas@2224
|
2445 if (this.timePerPixel > 0) {
|
nicholas@2224
|
2446 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2224
|
2447 if (time > 0 && time < this.maxTime) {
|
nicholas@2224
|
2448 var width = 490;
|
nicholas@2224
|
2449 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@2224
|
2450 this.scrubberHead.style.left = pix+'px';
|
nicholas@2224
|
2451 if (this.maxTime > 60.0) {
|
nicholas@2224
|
2452 var secs = time%60;
|
nicholas@2224
|
2453 var mins = Math.floor((time-secs)/60);
|
nicholas@2224
|
2454 secs = secs.toString();
|
nicholas@2224
|
2455 secs = secs.substr(0,2);
|
nicholas@2224
|
2456 mins = mins.toString();
|
nicholas@2224
|
2457 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@2224
|
2458 } else {
|
nicholas@2224
|
2459 time = time.toString();
|
nicholas@2224
|
2460 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@2224
|
2461 }
|
nicholas@2224
|
2462 } else {
|
nicholas@2224
|
2463 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2464 if (this.maxTime < 60) {
|
nicholas@2224
|
2465 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2466 } else {
|
nicholas@2224
|
2467 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2468 }
|
nicholas@2224
|
2469 }
|
nicholas@2224
|
2470 }
|
nicholas@2224
|
2471 };
|
nicholas@2224
|
2472
|
nicholas@2224
|
2473 this.interval = undefined;
|
nicholas@2224
|
2474
|
nicholas@2224
|
2475 this.start = function() {
|
nicholas@2224
|
2476 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@2224
|
2477 if (this.maxTime < 60) {
|
nicholas@2224
|
2478 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@2224
|
2479 } else {
|
nicholas@2224
|
2480 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@2224
|
2481 }
|
nicholas@2224
|
2482 }
|
nicholas@2224
|
2483 };
|
nicholas@2224
|
2484 this.stop = function() {
|
nicholas@2224
|
2485 clearInterval(this.interval);
|
nicholas@2224
|
2486 this.interval = undefined;
|
nicholas@2224
|
2487 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2488 if (this.maxTime < 60) {
|
nicholas@2224
|
2489 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2490 } else {
|
nicholas@2224
|
2491 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2492 }
|
nicholas@2224
|
2493 };
|
nicholas@2224
|
2494 };
|
nicholas@2224
|
2495
|
nicholas@2224
|
2496 this.volume = new function()
|
nicholas@2224
|
2497 {
|
nicholas@2224
|
2498 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
2499 // Includes trackers on page-by-page data
|
nicholas@2224
|
2500 // Volume does NOT reset to 0dB on each page load
|
nicholas@2224
|
2501 this.valueLin = 1.0;
|
nicholas@2224
|
2502 this.valueDB = 0.0;
|
nicholas@2224
|
2503 this.object = document.createElement('div');
|
nicholas@2224
|
2504 this.object.id = 'master-volume-holder';
|
nicholas@2224
|
2505 this.slider = document.createElement('input');
|
nicholas@2224
|
2506 this.slider.id = 'master-volume-control';
|
nicholas@2224
|
2507 this.slider.type = 'range';
|
nicholas@2224
|
2508 this.valueText = document.createElement('span');
|
nicholas@2224
|
2509 this.valueText.id = 'master-volume-feedback';
|
nicholas@2224
|
2510 this.valueText.textContent = '0dB';
|
nicholas@2224
|
2511
|
nicholas@2224
|
2512 this.slider.min = -60;
|
nicholas@2224
|
2513 this.slider.max = 12;
|
nicholas@2224
|
2514 this.slider.value = 0;
|
nicholas@2224
|
2515 this.slider.step = 1;
|
nicholas@2224
|
2516 this.slider.onmousemove = function(event)
|
nicholas@2224
|
2517 {
|
nicholas@2224
|
2518 interfaceContext.volume.valueDB = event.currentTarget.value;
|
nicholas@2224
|
2519 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
nicholas@2224
|
2520 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
nicholas@2224
|
2521 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
nicholas@2224
|
2522 }
|
nicholas@2224
|
2523 this.slider.onmouseup = function(event)
|
nicholas@2224
|
2524 {
|
nicholas@2224
|
2525 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2224
|
2526 if (storePoint.length == 0)
|
nicholas@2224
|
2527 {
|
nicholas@2224
|
2528 storePoint = storage.document.createElement('metricresult');
|
nicholas@2224
|
2529 storePoint.setAttribute('name','volumeTracker');
|
nicholas@2224
|
2530 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2224
|
2531 }
|
nicholas@2224
|
2532 else {
|
nicholas@2224
|
2533 storePoint = storePoint[0];
|
nicholas@2224
|
2534 }
|
nicholas@2224
|
2535 var node = storage.document.createElement('movement');
|
nicholas@2224
|
2536 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
2537 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
nicholas@2224
|
2538 node.setAttribute('format','dBFS');
|
nicholas@2224
|
2539 storePoint.appendChild(node);
|
nicholas@2224
|
2540 }
|
nicholas@2224
|
2541
|
nicholas@2224
|
2542 var title = document.createElement('div');
|
nicholas@2224
|
2543 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
2544 title.style.fontSize = '0.75em';
|
nicholas@2224
|
2545 title.style.width = "100%";
|
nicholas@2224
|
2546 title.align = 'center';
|
nicholas@2224
|
2547 this.object.appendChild(title);
|
nicholas@2224
|
2548
|
nicholas@2224
|
2549 this.object.appendChild(this.slider);
|
nicholas@2224
|
2550 this.object.appendChild(this.valueText);
|
nicholas@2224
|
2551 }
|
nicholas@2224
|
2552
|
nicholas@2224
|
2553 this.calibrationModuleObject = null;
|
nicholas@2224
|
2554 this.calibrationModule = function() {
|
nicholas@2224
|
2555 // This creates an on-page calibration module
|
nicholas@2224
|
2556 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
2557 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
2558 // The calibration is a fixed state module
|
nicholas@2224
|
2559 this.calibrationNodes = [];
|
nicholas@2224
|
2560 this.holder = null;
|
nicholas@2224
|
2561 this.build = function(inject) {
|
nicholas@2224
|
2562 var f0 = 62.5;
|
nicholas@2224
|
2563 this.holder = document.createElement("div");
|
nicholas@2224
|
2564 this.holder.className = "calibration-holder";
|
nicholas@2224
|
2565 this.calibrationNodes = [];
|
nicholas@2224
|
2566 while(f0 < 20000) {
|
nicholas@2224
|
2567 var obj = {
|
nicholas@2224
|
2568 root: document.createElement("div"),
|
nicholas@2224
|
2569 input: document.createElement("input"),
|
nicholas@2224
|
2570 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
2571 gain: audioContext.createGain(),
|
nicholas@2224
|
2572 f: f0,
|
nicholas@2224
|
2573 parent: this,
|
nicholas@2224
|
2574 handleEvent: function(event) {
|
nicholas@2224
|
2575 switch(event.type) {
|
nicholas@2224
|
2576 case "mouseenter":
|
nicholas@2224
|
2577 this.oscillator.start(0);
|
nicholas@2224
|
2578 break;
|
nicholas@2224
|
2579 case "mouseleave":
|
nicholas@2224
|
2580 this.oscillator.stop(0);
|
nicholas@2224
|
2581 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
2582 this.oscillator.connect(this.gain);
|
nicholas@2224
|
2583 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
2584 break;
|
nicholas@2224
|
2585 case "mousemove":
|
nicholas@2224
|
2586 var value = Math.pow(10,this.input.value/20);
|
nicholas@2224
|
2587 if (this.f == 1000) {
|
nicholas@2224
|
2588 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
2589 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
2590 } else {
|
nicholas@2224
|
2591 this.gain.gain.value = value
|
nicholas@2224
|
2592 }
|
nicholas@2224
|
2593 break;
|
nicholas@2224
|
2594 }
|
nicholas@2224
|
2595 },
|
nicholas@2224
|
2596 disconnect: function() {
|
nicholas@2224
|
2597 this.gain.disconnect();
|
nicholas@2224
|
2598 }
|
nicholas@2224
|
2599 }
|
nicholas@2224
|
2600 obj.root.className = "calibration-slider";
|
nicholas@2224
|
2601 obj.root.appendChild(obj.input);
|
nicholas@2224
|
2602 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
2603 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
2604 obj.gain.gain.value = Math.random()*2;
|
nicholas@2224
|
2605 obj.input.value = obj.gain.gain.value;
|
nicholas@2224
|
2606 obj.input.setAttribute('orient','vertical');
|
nicholas@2224
|
2607 obj.input.type = "range";
|
nicholas@2224
|
2608 obj.input.min = -6;
|
nicholas@2224
|
2609 obj.input.max = 6;
|
nicholas@2224
|
2610 obj.input.step = 0.25;
|
nicholas@2224
|
2611 if (f0 != 1000) {
|
nicholas@2224
|
2612 obj.input.value = (Math.random()*12)-6;
|
nicholas@2224
|
2613 } else {
|
nicholas@2224
|
2614 obj.input.value = 0;
|
nicholas@2224
|
2615 obj.root.style.backgroundColor="rgb(255,125,125)";
|
nicholas@2224
|
2616 }
|
nicholas@2224
|
2617 obj.input.addEventListener("mousemove",obj);
|
nicholas@2224
|
2618 obj.input.addEventListener("mouseenter",obj);
|
nicholas@2224
|
2619 obj.input.addEventListener("mouseleave",obj);
|
nicholas@2224
|
2620 obj.gain.gain.value = Math.pow(10,obj.input.value/20);
|
nicholas@2224
|
2621 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
2622 this.calibrationNodes.push(obj);
|
nicholas@2224
|
2623 this.holder.appendChild(obj.root);
|
nicholas@2224
|
2624 f0 *= 2;
|
nicholas@2224
|
2625 }
|
nicholas@2224
|
2626 inject.appendChild(this.holder);
|
nicholas@2224
|
2627 }
|
nicholas@2224
|
2628 this.collect = function() {
|
nicholas@2224
|
2629 for (var obj of this.calibrationNodes) {
|
nicholas@2224
|
2630 var node = storage.document.createElement("calibrationresult");
|
nicholas@2224
|
2631 node.setAttribute("frequency",obj.f);
|
nicholas@2224
|
2632 node.setAttribute("range-min",obj.input.min);
|
nicholas@2224
|
2633 node.setAttribute("range-max",obj.input.max);
|
nicholas@2224
|
2634 node.setAttribute("gain-lin",obj.gain.gain.value);
|
nicholas@2224
|
2635 this.storeDOM.appendChild(node);
|
nicholas@2224
|
2636 }
|
nicholas@2224
|
2637 }
|
nicholas@2224
|
2638 }
|
nicholas@2224
|
2639
|
nicholas@2224
|
2640
|
nicholas@2224
|
2641 // Global Checkers
|
nicholas@2224
|
2642 // These functions will help enforce the checkers
|
nicholas@2224
|
2643 this.checkHiddenAnchor = function()
|
nicholas@2224
|
2644 {
|
nicholas@2224
|
2645 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2646 {
|
nicholas@2224
|
2647 if (ao.specification.type == "anchor")
|
nicholas@2224
|
2648 {
|
nicholas@2224
|
2649 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2650 // Anchor is not set below
|
nicholas@2224
|
2651 console.log('Anchor node not below marker value');
|
nicholas@2224
|
2652 alert('Please keep listening');
|
nicholas@2224
|
2653 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2224
|
2654 return false;
|
nicholas@2224
|
2655 }
|
nicholas@2224
|
2656 }
|
nicholas@2224
|
2657 }
|
nicholas@2224
|
2658 return true;
|
nicholas@2224
|
2659 };
|
nicholas@2224
|
2660
|
nicholas@2224
|
2661 this.checkHiddenReference = function()
|
nicholas@2224
|
2662 {
|
nicholas@2224
|
2663 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2664 {
|
nicholas@2224
|
2665 if (ao.specification.type == "reference")
|
nicholas@2224
|
2666 {
|
nicholas@2224
|
2667 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2668 // Anchor is not set below
|
nicholas@2224
|
2669 console.log('Reference node not above marker value');
|
nicholas@2224
|
2670 this.storeErrorNode('Reference node not above marker value');
|
nicholas@2224
|
2671 alert('Please keep listening');
|
nicholas@2224
|
2672 return false;
|
nicholas@2224
|
2673 }
|
nicholas@2224
|
2674 }
|
nicholas@2224
|
2675 }
|
nicholas@2224
|
2676 return true;
|
nicholas@2224
|
2677 };
|
nicholas@2224
|
2678
|
nicholas@2224
|
2679 this.checkFragmentsFullyPlayed = function ()
|
nicholas@2224
|
2680 {
|
nicholas@2224
|
2681 // Checks the entire file has been played back
|
nicholas@2224
|
2682 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2224
|
2683 if (audioEngineContext.loopPlayback)
|
nicholas@2224
|
2684 {
|
nicholas@2224
|
2685 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2224
|
2686 return true;
|
nicholas@2224
|
2687 }
|
nicholas@2224
|
2688 var check_pass = true;
|
nicholas@2224
|
2689 var error_obj = [];
|
nicholas@2224
|
2690 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
nicholas@2224
|
2691 {
|
nicholas@2224
|
2692 var object = audioEngineContext.audioObjects[i];
|
nicholas@2224
|
2693 var time = object.buffer.buffer.duration;
|
nicholas@2224
|
2694 var metric = object.metric;
|
nicholas@2224
|
2695 var passed = false;
|
nicholas@2224
|
2696 for (var j=0; j<metric.listenTracker.length; j++)
|
nicholas@2224
|
2697 {
|
nicholas@2224
|
2698 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
nicholas@2224
|
2699 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2224
|
2700 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2224
|
2701 var delta = stop_time - start_time;
|
nicholas@2224
|
2702 if (delta >= time)
|
nicholas@2224
|
2703 {
|
nicholas@2224
|
2704 passed = true;
|
nicholas@2224
|
2705 break;
|
nicholas@2224
|
2706 }
|
nicholas@2224
|
2707 }
|
nicholas@2224
|
2708 if (passed == false)
|
nicholas@2224
|
2709 {
|
nicholas@2224
|
2710 check_pass = false;
|
b@2258
|
2711 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2712 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2713 }
|
nicholas@2224
|
2714 }
|
nicholas@2224
|
2715 if (check_pass == false)
|
nicholas@2224
|
2716 {
|
nicholas@2224
|
2717 var str_start = "You have not completely listened to fragments ";
|
nicholas@2224
|
2718 for (var i=0; i<error_obj.length; i++)
|
nicholas@2224
|
2719 {
|
nicholas@2224
|
2720 str_start += error_obj[i];
|
nicholas@2224
|
2721 if (i != error_obj.length-1)
|
nicholas@2224
|
2722 {
|
nicholas@2224
|
2723 str_start += ', ';
|
nicholas@2224
|
2724 }
|
nicholas@2224
|
2725 }
|
nicholas@2224
|
2726 str_start += ". Please keep listening";
|
nicholas@2224
|
2727 console.log("[ALERT]: "+str_start);
|
nicholas@2224
|
2728 this.storeErrorNode("[ALERT]: "+str_start);
|
nicholas@2224
|
2729 alert(str_start);
|
nicholas@2224
|
2730 }
|
nicholas@2224
|
2731 };
|
nicholas@2224
|
2732 this.checkAllMoved = function()
|
nicholas@2224
|
2733 {
|
nicholas@2224
|
2734 var str = "You have not moved ";
|
nicholas@2224
|
2735 var failed = [];
|
nicholas@2224
|
2736 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2737 {
|
nicholas@2224
|
2738 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@2224
|
2739 {
|
b@2258
|
2740 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2741 }
|
nicholas@2224
|
2742 }
|
nicholas@2224
|
2743 if (failed.length == 0)
|
nicholas@2224
|
2744 {
|
nicholas@2224
|
2745 return true;
|
nicholas@2224
|
2746 } else if (failed.length == 1)
|
nicholas@2224
|
2747 {
|
nicholas@2224
|
2748 str += 'track '+failed[0];
|
nicholas@2224
|
2749 } else {
|
nicholas@2224
|
2750 str += 'tracks ';
|
nicholas@2224
|
2751 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2752 {
|
nicholas@2224
|
2753 str += failed[i]+', ';
|
nicholas@2224
|
2754 }
|
nicholas@2224
|
2755 str += 'and '+failed[i];
|
nicholas@2224
|
2756 }
|
nicholas@2224
|
2757 str +='.';
|
nicholas@2224
|
2758 alert(str);
|
nicholas@2224
|
2759 console.log(str);
|
nicholas@2224
|
2760 this.storeErrorNode(str);
|
nicholas@2224
|
2761 return false;
|
nicholas@2224
|
2762 };
|
nicholas@2224
|
2763 this.checkAllPlayed = function()
|
nicholas@2224
|
2764 {
|
nicholas@2224
|
2765 var str = "You have not played ";
|
nicholas@2224
|
2766 var failed = [];
|
nicholas@2224
|
2767 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2768 {
|
nicholas@2224
|
2769 if(ao.metric.wasListenedTo == false)
|
nicholas@2224
|
2770 {
|
b@2258
|
2771 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2772 }
|
nicholas@2224
|
2773 }
|
nicholas@2224
|
2774 if (failed.length == 0)
|
nicholas@2224
|
2775 {
|
nicholas@2224
|
2776 return true;
|
nicholas@2224
|
2777 } else if (failed.length == 1)
|
nicholas@2224
|
2778 {
|
nicholas@2224
|
2779 str += 'track '+failed[0];
|
nicholas@2224
|
2780 } else {
|
nicholas@2224
|
2781 str += 'tracks ';
|
nicholas@2224
|
2782 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2783 {
|
nicholas@2224
|
2784 str += failed[i]+', ';
|
nicholas@2224
|
2785 }
|
nicholas@2224
|
2786 str += 'and '+failed[i];
|
nicholas@2224
|
2787 }
|
nicholas@2224
|
2788 str +='.';
|
nicholas@2224
|
2789 alert(str);
|
nicholas@2224
|
2790 console.log(str);
|
nicholas@2224
|
2791 this.storeErrorNode(str);
|
nicholas@2224
|
2792 return false;
|
nicholas@2224
|
2793 };
|
nicholas@2310
|
2794 this.checkScaleRange = function(min, max) {
|
nicholas@2310
|
2795 var page = testState.getCurrentTestPage();
|
nicholas@2310
|
2796 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2310
|
2797 var state = true;
|
nicholas@2310
|
2798 var str = "Please keep listening. ";
|
nicholas@2310
|
2799 var minRanking = Infinity;
|
nicholas@2310
|
2800 var maxRanking = -Infinity;
|
nicholas@2310
|
2801 for (var ao of audioObjects) {
|
nicholas@2310
|
2802 var rank = ao.interfaceDOM.getValue();
|
nicholas@2310
|
2803 if (rank < minRanking) {minRanking = rank;}
|
nicholas@2310
|
2804 if (rank > maxRanking) {maxRanking = rank;}
|
nicholas@2310
|
2805 }
|
nicholas@2310
|
2806 if (minRanking*100 > min) {
|
nicholas@2310
|
2807 str += "At least one fragment must be below the "+min+" mark.";
|
nicholas@2310
|
2808 state = false;
|
nicholas@2310
|
2809 }
|
nicholas@2310
|
2810 if (maxRanking*100 < max) {
|
nicholas@2310
|
2811 str += "At least one fragment must be above the "+max+" mark."
|
nicholas@2310
|
2812 state = false;
|
nicholas@2310
|
2813 }
|
nicholas@2310
|
2814 if (!state) {
|
nicholas@2310
|
2815 console.log(str);
|
nicholas@2310
|
2816 this.storeErrorNode(str);
|
nicholas@2310
|
2817 alert(str);
|
nicholas@2310
|
2818 }
|
nicholas@2310
|
2819 return state;
|
nicholas@2310
|
2820 }
|
nicholas@2224
|
2821
|
nicholas@2224
|
2822 this.storeErrorNode = function(errorMessage)
|
nicholas@2224
|
2823 {
|
nicholas@2224
|
2824 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
2825 var node = storage.document.createElement('error');
|
nicholas@2224
|
2826 node.setAttribute('time',time);
|
nicholas@2224
|
2827 node.textContent = errorMessage;
|
nicholas@2224
|
2828 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
2829 };
|
nicholas@2224
|
2830 }
|
nicholas@2224
|
2831
|
nicholas@2224
|
2832 function Storage()
|
nicholas@2224
|
2833 {
|
nicholas@2224
|
2834 // Holds results in XML format until ready for collection
|
nicholas@2224
|
2835 this.globalPreTest = null;
|
nicholas@2224
|
2836 this.globalPostTest = null;
|
nicholas@2224
|
2837 this.testPages = [];
|
nicholas@2224
|
2838 this.document = null;
|
nicholas@2224
|
2839 this.root = null;
|
nicholas@2224
|
2840 this.state = 0;
|
nicholas@2224
|
2841
|
nicholas@2224
|
2842 this.initialise = function(existingStore)
|
nicholas@2224
|
2843 {
|
nicholas@2224
|
2844 if (existingStore == undefined) {
|
nicholas@2224
|
2845 // We need to get the sessionKey
|
nicholas@2224
|
2846 this.SessionKey.generateKey();
|
nicholas@2224
|
2847 this.document = document.implementation.createDocument(null,"waetresult");
|
nicholas@2224
|
2848 this.root = this.document.childNodes[0];
|
nicholas@2224
|
2849 var projectDocument = specification.projectXML;
|
nicholas@2224
|
2850 projectDocument.setAttribute('file-name',url);
|
nicholas@2224
|
2851 projectDocument.setAttribute('url',qualifyURL(url));
|
nicholas@2224
|
2852 this.root.appendChild(projectDocument);
|
nicholas@2224
|
2853 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
2854 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
2855 } else {
|
nicholas@2224
|
2856 this.document = existingStore;
|
nicholas@2294
|
2857 this.root = existingStore.firstChild;
|
nicholas@2224
|
2858 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
2859 }
|
nicholas@2224
|
2860 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
nicholas@2224
|
2861 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
nicholas@2224
|
2862 };
|
nicholas@2224
|
2863
|
nicholas@2224
|
2864 this.SessionKey = {
|
nicholas@2224
|
2865 key: null,
|
nicholas@2224
|
2866 request: new XMLHttpRequest(),
|
nicholas@2224
|
2867 parent: this,
|
nicholas@2224
|
2868 handleEvent: function() {
|
nicholas@2224
|
2869 var parse = new DOMParser();
|
nicholas@2224
|
2870 var xml = parse.parseFromString(this.request.response,"text/xml");
|
giuliomoro@2301
|
2871 var shouldGenerateKey = true;
|
giuliomoro@2301
|
2872 if(xml.getAllElementsByTagName("state").length > 0){
|
giuliomoro@2301
|
2873 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
|
giuliomoro@2301
|
2874 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
giuliomoro@2301
|
2875 this.parent.root.setAttribute("key",this.key);
|
giuliomoro@2301
|
2876 this.parent.root.setAttribute("state","empty");
|
giuliomoro@2301
|
2877 shouldGenerateKey = false;
|
giuliomoro@2301
|
2878 }
|
giuliomoro@2301
|
2879 }
|
giuliomoro@2301
|
2880 if(shouldGenerateKey === true){
|
giuliomoro@2301
|
2881 this.generateKey();
|
giuliomoro@2301
|
2882 }
|
nicholas@2224
|
2883 },
|
nicholas@2224
|
2884 generateKey: function() {
|
nicholas@2224
|
2885 var temp_key = randomString(32);
|
nicholas@2302
|
2886 var returnURL = "";
|
nicholas@2302
|
2887 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
2888 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
2889 returnURL = specification.projectReturn;
|
nicholas@2302
|
2890 }
|
nicholas@2302
|
2891 }
|
nicholas@2302
|
2892 this.request.open("GET",returnURL+"php/keygen.php?key="+temp_key,true);
|
nicholas@2224
|
2893 this.request.addEventListener("load",this);
|
nicholas@2224
|
2894 this.request.send();
|
nicholas@2224
|
2895 },
|
nicholas@2224
|
2896 update: function() {
|
nicholas@2224
|
2897 this.parent.root.setAttribute("state","update");
|
nicholas@2224
|
2898 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
2899 var returnURL = "";
|
nicholas@2302
|
2900 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
2901 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
2902 returnURL = specification.projectReturn;
|
nicholas@2302
|
2903 }
|
nicholas@2302
|
2904 }
|
nicholas@2302
|
2905 xmlhttp.open("POST",returnURL+"php/save.php?key="+this.key);
|
nicholas@2224
|
2906 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2224
|
2907 xmlhttp.onerror = function(){
|
nicholas@2224
|
2908 console.log('Error updating file to server!');
|
nicholas@2224
|
2909 };
|
nicholas@2224
|
2910 var hold = document.createElement("div");
|
nicholas@2224
|
2911 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
2912 hold.appendChild(clone);
|
nicholas@2224
|
2913 xmlhttp.onload = function() {
|
nicholas@2224
|
2914 if (this.status >= 300) {
|
nicholas@2224
|
2915 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
2916 } else {
|
nicholas@2224
|
2917 var parser = new DOMParser();
|
nicholas@2224
|
2918 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
2919 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
2920 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
2921 var file = response.getElementsByTagName("file")[0];
|
nicholas@2224
|
2922 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
|
nicholas@2224
|
2923 } else {
|
nicholas@2224
|
2924 var message = response.getElementsByTagName("message");
|
nicholas@2224
|
2925 console.log("Intermediate save: Error! "+message.textContent);
|
nicholas@2224
|
2926 }
|
nicholas@2224
|
2927 }
|
nicholas@2224
|
2928 }
|
nicholas@2224
|
2929 xmlhttp.send([hold.innerHTML]);
|
nicholas@2224
|
2930 }
|
nicholas@2224
|
2931 }
|
nicholas@2224
|
2932
|
nicholas@2224
|
2933 this.createTestPageStore = function(specification)
|
nicholas@2224
|
2934 {
|
nicholas@2224
|
2935 var store = new this.pageNode(this,specification);
|
nicholas@2224
|
2936 this.testPages.push(store);
|
nicholas@2224
|
2937 return this.testPages[this.testPages.length-1];
|
nicholas@2224
|
2938 };
|
nicholas@2224
|
2939
|
nicholas@2224
|
2940 this.surveyNode = function(parent,root,specification)
|
nicholas@2224
|
2941 {
|
nicholas@2224
|
2942 this.specification = specification;
|
nicholas@2224
|
2943 this.parent = parent;
|
nicholas@2224
|
2944 this.state = "empty";
|
nicholas@2224
|
2945 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2224
|
2946 this.XMLDOM.setAttribute('location',this.specification.location);
|
nicholas@2224
|
2947 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
2948 for (var optNode of this.specification.options)
|
nicholas@2224
|
2949 {
|
nicholas@2224
|
2950 if (optNode.type != 'statement')
|
nicholas@2224
|
2951 {
|
nicholas@2224
|
2952 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2224
|
2953 node.setAttribute("ref",optNode.id);
|
nicholas@2224
|
2954 node.setAttribute('type',optNode.type);
|
nicholas@2224
|
2955 this.XMLDOM.appendChild(node);
|
nicholas@2224
|
2956 }
|
nicholas@2224
|
2957 }
|
nicholas@2224
|
2958 root.appendChild(this.XMLDOM);
|
nicholas@2224
|
2959
|
nicholas@2224
|
2960 this.postResult = function(node)
|
nicholas@2224
|
2961 {
|
nicholas@2224
|
2962 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2224
|
2963 // ID is the position
|
nicholas@2224
|
2964 if (node.specification.type == 'statement'){return;}
|
nicholas@2294
|
2965 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2224
|
2966 while(surveyresult != null) {
|
nicholas@2224
|
2967 if (surveyresult.getAttribute("ref") == node.specification.id)
|
nicholas@2224
|
2968 {
|
nicholas@2224
|
2969 break;
|
nicholas@2224
|
2970 }
|
nicholas@2224
|
2971 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
2972 }
|
nicholas@2224
|
2973 switch(node.specification.type)
|
nicholas@2224
|
2974 {
|
nicholas@2224
|
2975 case "number":
|
nicholas@2224
|
2976 case "question":
|
nicholas@2224
|
2977 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
2978 child.textContent = node.response;
|
nicholas@2224
|
2979 surveyresult.appendChild(child);
|
nicholas@2224
|
2980 break;
|
nicholas@2224
|
2981 case "radio":
|
nicholas@2224
|
2982 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
2983 child.setAttribute('name',node.response.name);
|
nicholas@2224
|
2984 child.textContent = node.response.text;
|
nicholas@2224
|
2985 surveyresult.appendChild(child);
|
nicholas@2224
|
2986 break;
|
nicholas@2224
|
2987 case "checkbox":
|
nicholas@2224
|
2988 for (var i=0; i<node.response.length; i++)
|
nicholas@2224
|
2989 {
|
nicholas@2224
|
2990 var checkNode = this.parent.document.createElement('response');
|
nicholas@2224
|
2991 checkNode.setAttribute('name',node.response[i].name);
|
nicholas@2224
|
2992 checkNode.setAttribute('checked',node.response[i].checked);
|
nicholas@2224
|
2993 surveyresult.appendChild(checkNode);
|
nicholas@2224
|
2994 }
|
nicholas@2224
|
2995 break;
|
nicholas@2224
|
2996 }
|
nicholas@2224
|
2997 };
|
nicholas@2224
|
2998 this.complete = function() {
|
nicholas@2224
|
2999 this.state = "complete";
|
nicholas@2224
|
3000 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3001 }
|
nicholas@2224
|
3002 };
|
nicholas@2224
|
3003
|
nicholas@2224
|
3004 this.pageNode = function(parent,specification)
|
nicholas@2224
|
3005 {
|
nicholas@2224
|
3006 // Create one store per test page
|
nicholas@2224
|
3007 this.specification = specification;
|
nicholas@2224
|
3008 this.parent = parent;
|
nicholas@2224
|
3009 this.state = "empty";
|
nicholas@2224
|
3010 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2224
|
3011 this.XMLDOM.setAttribute('ref',specification.id);
|
nicholas@2224
|
3012 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
nicholas@2224
|
3013 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3014 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
nicholas@2224
|
3015 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
nicholas@2224
|
3016
|
nicholas@2224
|
3017 // Add any page metrics
|
nicholas@2224
|
3018 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3019 this.XMLDOM.appendChild(page_metric);
|
nicholas@2224
|
3020
|
nicholas@2224
|
3021 // Add the audioelement
|
nicholas@2224
|
3022 for (var element of this.specification.audioElements)
|
nicholas@2224
|
3023 {
|
nicholas@2224
|
3024 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2224
|
3025 aeNode.setAttribute('ref',element.id);
|
nicholas@2224
|
3026 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
|
nicholas@2224
|
3027 aeNode.setAttribute('type',element.type);
|
nicholas@2224
|
3028 aeNode.setAttribute('url', element.url);
|
nicholas@2224
|
3029 aeNode.setAttribute('fqurl',qualifyURL(element.url));
|
nicholas@2224
|
3030 aeNode.setAttribute('gain', element.gain);
|
nicholas@2224
|
3031 if (element.type == 'anchor' || element.type == 'reference')
|
nicholas@2224
|
3032 {
|
nicholas@2224
|
3033 if (element.marker > 0)
|
nicholas@2224
|
3034 {
|
nicholas@2224
|
3035 aeNode.setAttribute('marker',element.marker);
|
nicholas@2224
|
3036 }
|
nicholas@2224
|
3037 }
|
nicholas@2224
|
3038 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3039 aeNode.appendChild(ae_metric);
|
nicholas@2224
|
3040 this.XMLDOM.appendChild(aeNode);
|
nicholas@2224
|
3041 }
|
nicholas@2224
|
3042
|
nicholas@2224
|
3043 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2224
|
3044
|
nicholas@2224
|
3045 this.complete = function() {
|
nicholas@2224
|
3046 this.state = "complete";
|
nicholas@2224
|
3047 this.XMLDOM.setAttribute("state","complete");
|
nicholas@2224
|
3048 }
|
nicholas@2224
|
3049 };
|
nicholas@2224
|
3050 this.update = function() {
|
nicholas@2224
|
3051 this.SessionKey.update();
|
nicholas@2224
|
3052 }
|
nicholas@2224
|
3053 this.finish = function()
|
nicholas@2224
|
3054 {
|
nicholas@2224
|
3055 if (this.state == 0)
|
nicholas@2224
|
3056 {
|
nicholas@2224
|
3057 this.update();
|
nicholas@2224
|
3058 }
|
nicholas@2224
|
3059 this.state = 1;
|
nicholas@2224
|
3060 return this.root;
|
nicholas@2224
|
3061 };
|
nicholas@2224
|
3062 }
|