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@2312
|
1106 this.getCurrentTestPageStore = function() {
|
nicholas@2312
|
1107 if (this.stateIndex >= 0 && this.stateIndex< this.stateMap.length) {
|
nicholas@2312
|
1108 return this.currentStore;
|
nicholas@2312
|
1109 } else {
|
nicholas@2312
|
1110 return null;
|
nicholas@2312
|
1111 }
|
nicholas@2312
|
1112 }
|
nicholas@2224
|
1113 }
|
nicholas@2224
|
1114
|
nicholas@2224
|
1115 function AudioEngine(specification) {
|
nicholas@2224
|
1116
|
nicholas@2224
|
1117 // Create two output paths, the main outputGain and fooGain.
|
nicholas@2224
|
1118 // Output gain is default to 1 and any items for playback route here
|
nicholas@2224
|
1119 // Foo gain is used for analysis to ensure paths get processed, but are not heard
|
nicholas@2224
|
1120 // because web audio will optimise and any route which does not go to the destination gets ignored.
|
nicholas@2224
|
1121 this.outputGain = audioContext.createGain();
|
nicholas@2224
|
1122 this.fooGain = audioContext.createGain();
|
nicholas@2224
|
1123 this.fooGain.gain = 0;
|
nicholas@2224
|
1124
|
nicholas@2224
|
1125 // Use this to detect playback state: 0 - stopped, 1 - playing
|
nicholas@2224
|
1126 this.status = 0;
|
nicholas@2224
|
1127
|
nicholas@2224
|
1128 // Connect both gains to output
|
nicholas@2224
|
1129 this.outputGain.connect(audioContext.destination);
|
nicholas@2224
|
1130 this.fooGain.connect(audioContext.destination);
|
nicholas@2224
|
1131
|
nicholas@2224
|
1132 // Create the timer Object
|
nicholas@2224
|
1133 this.timer = new timer();
|
nicholas@2224
|
1134 // Create session metrics
|
nicholas@2224
|
1135 this.metric = new sessionMetrics(this,specification);
|
nicholas@2224
|
1136
|
nicholas@2224
|
1137 this.loopPlayback = false;
|
nicholas@2224
|
1138
|
nicholas@2224
|
1139 this.pageStore = null;
|
nicholas@2224
|
1140
|
nicholas@2224
|
1141 // Create store for new audioObjects
|
nicholas@2224
|
1142 this.audioObjects = [];
|
nicholas@2224
|
1143
|
nicholas@2224
|
1144 this.buffers = [];
|
nicholas@2224
|
1145 this.bufferObj = function()
|
nicholas@2224
|
1146 {
|
nicholas@2224
|
1147 this.url = null;
|
nicholas@2224
|
1148 this.buffer = null;
|
nicholas@2224
|
1149 this.xmlRequest = new XMLHttpRequest();
|
nicholas@2224
|
1150 this.xmlRequest.parent = this;
|
nicholas@2224
|
1151 this.users = [];
|
nicholas@2224
|
1152 this.progress = 0;
|
nicholas@2224
|
1153 this.status = 0;
|
nicholas@2224
|
1154 this.ready = function()
|
nicholas@2224
|
1155 {
|
nicholas@2224
|
1156 if (this.status >= 2)
|
nicholas@2224
|
1157 {
|
nicholas@2224
|
1158 this.status = 3;
|
nicholas@2224
|
1159 }
|
nicholas@2224
|
1160 for (var i=0; i<this.users.length; i++)
|
nicholas@2224
|
1161 {
|
nicholas@2224
|
1162 this.users[i].state = 1;
|
nicholas@2224
|
1163 if (this.users[i].interfaceDOM != null)
|
nicholas@2224
|
1164 {
|
nicholas@2224
|
1165 this.users[i].bufferLoaded(this);
|
nicholas@2224
|
1166 }
|
nicholas@2224
|
1167 }
|
nicholas@2224
|
1168 };
|
nicholas@2224
|
1169 this.getMedia = function(url) {
|
nicholas@2224
|
1170 this.url = url;
|
nicholas@2224
|
1171 this.xmlRequest.open('GET',this.url,true);
|
nicholas@2224
|
1172 this.xmlRequest.responseType = 'arraybuffer';
|
nicholas@2224
|
1173
|
nicholas@2224
|
1174 var bufferObj = this;
|
nicholas@2224
|
1175
|
nicholas@2224
|
1176 // Create callback to decode the data asynchronously
|
nicholas@2224
|
1177 this.xmlRequest.onloadend = function() {
|
nicholas@2224
|
1178 // Use inbuilt WAVE decoder first
|
nicholas@2224
|
1179 if (this.status == -1) {return;}
|
nicholas@2224
|
1180 var waveObj = new WAVE();
|
nicholas@2224
|
1181 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
|
nicholas@2224
|
1182 {
|
nicholas@2224
|
1183 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
|
nicholas@2224
|
1184 for (var c=0; c<waveObj.num_channels; c++)
|
nicholas@2224
|
1185 {
|
nicholas@2224
|
1186 var buffer_ptr = bufferObj.buffer.getChannelData(c);
|
nicholas@2224
|
1187 for (var n=0; n<waveObj.num_samples; n++)
|
nicholas@2224
|
1188 {
|
nicholas@2224
|
1189 buffer_ptr[n] = waveObj.decoded_data[c][n];
|
nicholas@2224
|
1190 }
|
nicholas@2224
|
1191 }
|
nicholas@2224
|
1192
|
nicholas@2224
|
1193 delete waveObj;
|
nicholas@2224
|
1194 } else {
|
nicholas@2224
|
1195 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
|
nicholas@2224
|
1196 bufferObj.buffer = decodedData;
|
nicholas@2244
|
1197 bufferObj.status = 2;
|
nicholas@2244
|
1198 calculateLoudness(bufferObj,"I");
|
nicholas@2224
|
1199 }, function(e){
|
nicholas@2224
|
1200 // Should only be called if there was an error, but sometimes gets called continuously
|
nicholas@2224
|
1201 // Check here if the error is genuine
|
nicholas@2224
|
1202 if (bufferObj.xmlRequest.response == undefined) {
|
nicholas@2224
|
1203 // Genuine error
|
nicholas@2224
|
1204 console.log('FATAL - Error loading buffer on '+audioObj.id);
|
nicholas@2224
|
1205 if (request.status == 404)
|
nicholas@2224
|
1206 {
|
nicholas@2224
|
1207 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
|
nicholas@2224
|
1208 console.log('URL: '+audioObj.url);
|
nicholas@2224
|
1209 errorSessionDump('Fragment '+audioObj.id+' 404 error');
|
nicholas@2224
|
1210 }
|
nicholas@2224
|
1211 this.parent.status = -1;
|
nicholas@2224
|
1212 }
|
nicholas@2224
|
1213 });
|
nicholas@2224
|
1214 }
|
nicholas@2224
|
1215 if (bufferObj.buffer != undefined)
|
nicholas@2224
|
1216 {
|
nicholas@2224
|
1217 bufferObj.status = 2;
|
nicholas@2224
|
1218 calculateLoudness(bufferObj,"I");
|
nicholas@2224
|
1219 }
|
nicholas@2224
|
1220 };
|
nicholas@2224
|
1221
|
nicholas@2224
|
1222 // Create callback for any error in loading
|
nicholas@2224
|
1223 this.xmlRequest.onerror = function() {
|
nicholas@2224
|
1224 this.parent.status = -1;
|
nicholas@2224
|
1225 for (var i=0; i<this.parent.users.length; i++)
|
nicholas@2224
|
1226 {
|
nicholas@2224
|
1227 this.parent.users[i].state = -1;
|
nicholas@2224
|
1228 if (this.parent.users[i].interfaceDOM != null)
|
nicholas@2224
|
1229 {
|
nicholas@2224
|
1230 this.parent.users[i].bufferLoaded(this);
|
nicholas@2224
|
1231 }
|
nicholas@2224
|
1232 }
|
nicholas@2224
|
1233 }
|
nicholas@2224
|
1234
|
nicholas@2224
|
1235 this.progress = 0;
|
nicholas@2224
|
1236 this.progressCallback = function(event){
|
nicholas@2224
|
1237 if (event.lengthComputable)
|
nicholas@2224
|
1238 {
|
nicholas@2224
|
1239 this.parent.progress = event.loaded / event.total;
|
nicholas@2224
|
1240 for (var i=0; i<this.parent.users.length; i++)
|
nicholas@2224
|
1241 {
|
nicholas@2224
|
1242 if(this.parent.users[i].interfaceDOM != null)
|
nicholas@2224
|
1243 {
|
nicholas@2224
|
1244 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
|
nicholas@2224
|
1245 {
|
nicholas@2224
|
1246 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
|
nicholas@2224
|
1247 }
|
nicholas@2224
|
1248 }
|
nicholas@2224
|
1249 }
|
nicholas@2224
|
1250 }
|
nicholas@2224
|
1251 };
|
nicholas@2224
|
1252 this.xmlRequest.addEventListener("progress", this.progressCallback);
|
nicholas@2224
|
1253 this.status = 1;
|
nicholas@2224
|
1254 this.xmlRequest.send();
|
nicholas@2224
|
1255 };
|
nicholas@2224
|
1256
|
nicholas@2224
|
1257 this.registerAudioObject = function(audioObject)
|
nicholas@2224
|
1258 {
|
nicholas@2224
|
1259 // Called by an audioObject to register to the buffer for use
|
nicholas@2224
|
1260 // First check if already in the register pool
|
nicholas@2224
|
1261 for (var objects of this.users)
|
nicholas@2224
|
1262 {
|
nicholas@2224
|
1263 if (audioObject.id == objects.id){return 0;}
|
nicholas@2224
|
1264 }
|
nicholas@2224
|
1265 this.users.push(audioObject);
|
nicholas@2224
|
1266 if (this.status == 3 || this.status == -1)
|
nicholas@2224
|
1267 {
|
nicholas@2224
|
1268 // The buffer is already ready, trigger bufferLoaded
|
nicholas@2224
|
1269 audioObject.bufferLoaded(this);
|
nicholas@2224
|
1270 }
|
nicholas@2224
|
1271 };
|
nicholas@2224
|
1272
|
nicholas@2224
|
1273 this.copyBuffer = function(preSilenceTime,postSilenceTime) {
|
nicholas@2224
|
1274 // Copies the entire bufferObj.
|
nicholas@2224
|
1275 if (preSilenceTime == undefined) {preSilenceTime = 0;}
|
nicholas@2224
|
1276 if (postSilenceTime == undefined) {postSilenceTime = 0;}
|
nicholas@2224
|
1277 var copy = new this.constructor();
|
nicholas@2224
|
1278 copy.url = this.url;
|
nicholas@2224
|
1279 var preSilenceSamples = secondsToSamples(preSilenceTime,this.buffer.sampleRate);
|
nicholas@2224
|
1280 var postSilenceSamples = secondsToSamples(postSilenceTime,this.buffer.sampleRate);
|
nicholas@2224
|
1281 var newLength = this.buffer.length+preSilenceSamples+postSilenceSamples;
|
nicholas@2224
|
1282 copy.buffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
|
nicholas@2224
|
1283 // Now we can use some efficient background copy schemes if we are just padding the end
|
nicholas@2224
|
1284 if (preSilenceSamples == 0 && typeof copy.buffer.copyToChannel == "function") {
|
nicholas@2224
|
1285 for (var c=0; c<this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1286 copy.buffer.copyToChannel(this.buffer.getChannelData(c),c);
|
nicholas@2224
|
1287 }
|
nicholas@2224
|
1288 } else {
|
nicholas@2224
|
1289 for (var c=0; c<this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1290 var src = this.buffer.getChannelData(c);
|
nicholas@2224
|
1291 var dst = copy.buffer.getChannelData(c);
|
nicholas@2224
|
1292 for (var n=0; n<src.length; n++)
|
nicholas@2224
|
1293 dst[n+preSilenceSamples] = src[n];
|
nicholas@2224
|
1294 }
|
nicholas@2224
|
1295 }
|
nicholas@2224
|
1296 // Copy in the rest of the buffer information
|
nicholas@2224
|
1297 copy.buffer.lufs = this.buffer.lufs;
|
nicholas@2224
|
1298 copy.buffer.playbackGain = this.buffer.playbackGain;
|
nicholas@2224
|
1299 return copy;
|
nicholas@2224
|
1300 }
|
nicholas@2224
|
1301 };
|
nicholas@2224
|
1302
|
nicholas@2224
|
1303 this.loadPageData = function(page) {
|
nicholas@2224
|
1304 // Load the URL from pages
|
nicholas@2224
|
1305 for (var element of page.audioElements) {
|
nicholas@2224
|
1306 var URL = page.hostURL + element.url;
|
nicholas@2224
|
1307 var buffer = null;
|
nicholas@2224
|
1308 for (var buffObj of this.buffers) {
|
nicholas@2224
|
1309 if (URL == buffObj.url) {
|
nicholas@2224
|
1310 buffer = buffObj;
|
nicholas@2224
|
1311 break;
|
nicholas@2224
|
1312 }
|
nicholas@2224
|
1313 }
|
nicholas@2224
|
1314 if (buffer == null) {
|
nicholas@2224
|
1315 buffer = new this.bufferObj();
|
nicholas@2224
|
1316 buffer.getMedia(URL);
|
nicholas@2224
|
1317 this.buffers.push(buffer);
|
nicholas@2224
|
1318 }
|
nicholas@2224
|
1319 }
|
nicholas@2224
|
1320 };
|
nicholas@2224
|
1321
|
nicholas@2224
|
1322 this.play = function(id) {
|
nicholas@2224
|
1323 // Start the timer and set the audioEngine state to playing (1)
|
nicholas@2224
|
1324 if (this.status == 0 && this.loopPlayback) {
|
nicholas@2224
|
1325 // Check if all audioObjects are ready
|
nicholas@2224
|
1326 if(this.checkAllReady())
|
nicholas@2224
|
1327 {
|
nicholas@2224
|
1328 this.status = 1;
|
nicholas@2224
|
1329 this.setSynchronousLoop();
|
nicholas@2224
|
1330 }
|
nicholas@2224
|
1331 }
|
nicholas@2224
|
1332 else
|
nicholas@2224
|
1333 {
|
nicholas@2224
|
1334 this.status = 1;
|
nicholas@2224
|
1335 }
|
nicholas@2224
|
1336 if (this.status== 1) {
|
nicholas@2224
|
1337 this.timer.startTest();
|
nicholas@2224
|
1338 if (id == undefined) {
|
nicholas@2224
|
1339 id = -1;
|
nicholas@2224
|
1340 console.error('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
|
nicholas@2224
|
1341 return;
|
nicholas@2224
|
1342 } else {
|
nicholas@2224
|
1343 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
|
nicholas@2224
|
1344 }
|
nicholas@2224
|
1345 if (this.loopPlayback) {
|
nicholas@2224
|
1346 var setTime = audioContext.currentTime+specification.crossFade;
|
nicholas@2224
|
1347 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1348 {
|
nicholas@2224
|
1349 this.audioObjects[i].play(audioContext.currentTime);
|
nicholas@2224
|
1350 if (id == i) {
|
nicholas@2224
|
1351 this.audioObjects[i].loopStart(setTime);
|
nicholas@2224
|
1352 } else {
|
nicholas@2224
|
1353 this.audioObjects[i].loopStop(setTime);
|
nicholas@2224
|
1354 }
|
nicholas@2224
|
1355 }
|
nicholas@2224
|
1356 } else {
|
nicholas@2224
|
1357 var setTime = audioContext.currentTime+specification.crossFade;
|
nicholas@2224
|
1358 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1359 {
|
nicholas@2224
|
1360 if (i != id) {
|
nicholas@2224
|
1361 this.audioObjects[i].stop(setTime);
|
nicholas@2224
|
1362 } else if (i == id) {
|
nicholas@2224
|
1363 this.audioObjects[id].play(setTime);
|
nicholas@2224
|
1364 }
|
nicholas@2224
|
1365 }
|
nicholas@2224
|
1366 }
|
nicholas@2224
|
1367 interfaceContext.playhead.start();
|
nicholas@2224
|
1368 }
|
nicholas@2224
|
1369 };
|
nicholas@2224
|
1370
|
nicholas@2224
|
1371 this.stop = function() {
|
nicholas@2224
|
1372 // Send stop and reset command to all playback buffers
|
nicholas@2224
|
1373 if (this.status == 1) {
|
nicholas@2224
|
1374 var setTime = audioContext.currentTime+0.1;
|
nicholas@2224
|
1375 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1376 {
|
nicholas@2224
|
1377 this.audioObjects[i].stop(setTime);
|
nicholas@2224
|
1378 }
|
nicholas@2224
|
1379 interfaceContext.playhead.stop();
|
nicholas@2224
|
1380 }
|
nicholas@2224
|
1381 };
|
nicholas@2224
|
1382
|
nicholas@2224
|
1383 this.newTrack = function(element) {
|
nicholas@2224
|
1384 // Pull data from given URL into new audio buffer
|
nicholas@2224
|
1385 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
|
nicholas@2224
|
1386
|
nicholas@2224
|
1387 // Create the audioObject with ID of the new track length;
|
nicholas@2224
|
1388 audioObjectId = this.audioObjects.length;
|
nicholas@2224
|
1389 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
|
nicholas@2224
|
1390
|
nicholas@2224
|
1391 // Check if audioObject buffer is currently stored by full URL
|
nicholas@2224
|
1392 var URL = testState.currentStateMap.hostURL + element.url;
|
nicholas@2224
|
1393 var buffer = null;
|
nicholas@2224
|
1394 for (var i=0; i<this.buffers.length; i++)
|
nicholas@2224
|
1395 {
|
nicholas@2224
|
1396 if (URL == this.buffers[i].url)
|
nicholas@2224
|
1397 {
|
nicholas@2224
|
1398 buffer = this.buffers[i];
|
nicholas@2224
|
1399 break;
|
nicholas@2224
|
1400 }
|
nicholas@2224
|
1401 }
|
nicholas@2224
|
1402 if (buffer == null)
|
nicholas@2224
|
1403 {
|
nicholas@2224
|
1404 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
|
nicholas@2224
|
1405 buffer = new this.bufferObj();
|
nicholas@2224
|
1406 this.buffers.push(buffer);
|
nicholas@2224
|
1407 buffer.getMedia(URL);
|
nicholas@2224
|
1408 }
|
nicholas@2224
|
1409 this.audioObjects[audioObjectId].specification = element;
|
nicholas@2224
|
1410 this.audioObjects[audioObjectId].url = URL;
|
nicholas@2224
|
1411 // Obtain store node
|
nicholas@2224
|
1412 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
|
nicholas@2224
|
1413 for (var i=0; i<aeNodes.length; i++)
|
nicholas@2224
|
1414 {
|
nicholas@2224
|
1415 if(aeNodes[i].getAttribute("ref") == element.id)
|
nicholas@2224
|
1416 {
|
nicholas@2224
|
1417 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
|
nicholas@2224
|
1418 break;
|
nicholas@2224
|
1419 }
|
nicholas@2224
|
1420 }
|
nicholas@2224
|
1421 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
|
nicholas@2224
|
1422 return this.audioObjects[audioObjectId];
|
nicholas@2224
|
1423 };
|
nicholas@2224
|
1424
|
nicholas@2224
|
1425 this.newTestPage = function(audioHolderObject,store) {
|
nicholas@2224
|
1426 this.pageStore = store;
|
nicholas@2224
|
1427 this.status = 0;
|
nicholas@2224
|
1428 this.audioObjectsReady = false;
|
nicholas@2224
|
1429 this.metric.reset();
|
nicholas@2224
|
1430 for (var i=0; i < this.buffers.length; i++)
|
nicholas@2224
|
1431 {
|
nicholas@2224
|
1432 this.buffers[i].users = [];
|
nicholas@2224
|
1433 }
|
nicholas@2224
|
1434 this.audioObjects = [];
|
nicholas@2224
|
1435 this.timer = new timer();
|
nicholas@2224
|
1436 this.loopPlayback = audioHolderObject.loop;
|
nicholas@2224
|
1437 };
|
nicholas@2224
|
1438
|
nicholas@2224
|
1439 this.checkAllPlayed = function() {
|
nicholas@2224
|
1440 arr = [];
|
nicholas@2224
|
1441 for (var id=0; id<this.audioObjects.length; id++) {
|
nicholas@2224
|
1442 if (this.audioObjects[id].metric.wasListenedTo == false) {
|
nicholas@2224
|
1443 arr.push(this.audioObjects[id].id);
|
nicholas@2224
|
1444 }
|
nicholas@2224
|
1445 }
|
nicholas@2224
|
1446 return arr;
|
nicholas@2224
|
1447 };
|
nicholas@2224
|
1448
|
nicholas@2224
|
1449 this.checkAllReady = function() {
|
nicholas@2224
|
1450 var ready = true;
|
nicholas@2224
|
1451 for (var i=0; i<this.audioObjects.length; i++) {
|
nicholas@2224
|
1452 if (this.audioObjects[i].state == 0) {
|
nicholas@2224
|
1453 // Track not ready
|
nicholas@2224
|
1454 console.log('WAIT -- audioObject '+i+' not ready yet!');
|
nicholas@2224
|
1455 ready = false;
|
nicholas@2224
|
1456 };
|
nicholas@2224
|
1457 }
|
nicholas@2224
|
1458 return ready;
|
nicholas@2224
|
1459 };
|
nicholas@2224
|
1460
|
nicholas@2224
|
1461 this.setSynchronousLoop = function() {
|
nicholas@2224
|
1462 // Pads the signals so they are all exactly the same length
|
nicholas@2224
|
1463 // Get the length of the longest signal.
|
nicholas@2224
|
1464 var length = 0;
|
nicholas@2224
|
1465 var maxId;
|
nicholas@2224
|
1466 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1467 {
|
nicholas@2224
|
1468 if (length < this.audioObjects[i].buffer.buffer.length)
|
nicholas@2224
|
1469 {
|
nicholas@2224
|
1470 length = this.audioObjects[i].buffer.buffer.length;
|
nicholas@2224
|
1471 maxId = i;
|
nicholas@2224
|
1472 }
|
nicholas@2224
|
1473 }
|
nicholas@2224
|
1474 // Extract the audio and zero-pad
|
nicholas@2224
|
1475 for (var ao of this.audioObjects)
|
nicholas@2224
|
1476 {
|
nicholas@2224
|
1477 var lengthDiff = length - ao.buffer.buffer.length;
|
nicholas@2224
|
1478 ao.buffer = ao.buffer.copyBuffer(0,samplesToSeconds(lengthDiff,ao.buffer.buffer.sampleRate));
|
nicholas@2224
|
1479 }
|
nicholas@2224
|
1480 };
|
nicholas@2224
|
1481
|
nicholas@2224
|
1482 this.exportXML = function()
|
nicholas@2224
|
1483 {
|
nicholas@2224
|
1484
|
nicholas@2224
|
1485 };
|
nicholas@2224
|
1486
|
nicholas@2224
|
1487 }
|
nicholas@2224
|
1488
|
nicholas@2224
|
1489 function audioObject(id) {
|
nicholas@2224
|
1490 // The main buffer object with common control nodes to the AudioEngine
|
nicholas@2224
|
1491
|
nicholas@2224
|
1492 this.specification;
|
nicholas@2224
|
1493 this.id = id;
|
nicholas@2224
|
1494 this.state = 0; // 0 - no data, 1 - ready
|
nicholas@2224
|
1495 this.url = null; // Hold the URL given for the output back to the results.
|
nicholas@2224
|
1496 this.metric = new metricTracker(this);
|
nicholas@2224
|
1497 this.storeDOM = null;
|
nicholas@2224
|
1498
|
nicholas@2224
|
1499 // Bindings for GUI
|
nicholas@2224
|
1500 this.interfaceDOM = null;
|
nicholas@2224
|
1501 this.commentDOM = null;
|
nicholas@2224
|
1502
|
nicholas@2224
|
1503 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
|
nicholas@2224
|
1504 this.bufferNode = undefined;
|
nicholas@2224
|
1505 this.outputGain = audioContext.createGain();
|
nicholas@2224
|
1506
|
nicholas@2224
|
1507 this.onplayGain = 1.0;
|
nicholas@2224
|
1508
|
nicholas@2224
|
1509 // Connect buffer to the audio graph
|
nicholas@2224
|
1510 this.outputGain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
1511
|
nicholas@2224
|
1512 // the audiobuffer is not designed for multi-start playback
|
nicholas@2224
|
1513 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
|
nicholas@2224
|
1514 this.buffer;
|
nicholas@2224
|
1515
|
nicholas@2224
|
1516 this.bufferLoaded = function(callee)
|
nicholas@2224
|
1517 {
|
nicholas@2224
|
1518 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
|
nicholas@2224
|
1519 // audioObject and trigger the interfaceDOM.enable() function for user feedback
|
nicholas@2224
|
1520 if (callee.status == -1) {
|
nicholas@2224
|
1521 // ERROR
|
nicholas@2224
|
1522 this.state = -1;
|
nicholas@2224
|
1523 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
|
nicholas@2224
|
1524 this.buffer = callee;
|
nicholas@2224
|
1525 return;
|
nicholas@2224
|
1526 }
|
nicholas@2224
|
1527 this.buffer = callee;
|
nicholas@2224
|
1528 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
|
nicholas@2224
|
1529 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
|
nicholas@2224
|
1530 if (preSilenceTime != 0 || postSilenceTime != 0) {
|
nicholas@2224
|
1531 this.buffer = callee.copyBuffer(preSilenceTime,postSilenceTime);
|
nicholas@2224
|
1532 }
|
nicholas@2224
|
1533 this.state = 1;
|
nicholas@2224
|
1534 var targetLUFS = this.specification.parent.loudness || specification.loudness;
|
nicholas@2224
|
1535 if (typeof targetLUFS === "number")
|
nicholas@2224
|
1536 {
|
nicholas@2224
|
1537 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
|
nicholas@2224
|
1538 } else {
|
nicholas@2224
|
1539 this.buffer.buffer.playbackGain = 1.0;
|
nicholas@2224
|
1540 }
|
nicholas@2224
|
1541 if (this.interfaceDOM != null) {
|
nicholas@2224
|
1542 this.interfaceDOM.enable();
|
nicholas@2224
|
1543 }
|
nicholas@2224
|
1544 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
|
nicholas@2224
|
1545 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
|
nicholas@2224
|
1546 };
|
nicholas@2224
|
1547
|
nicholas@2224
|
1548 this.bindInterface = function(interfaceObject)
|
nicholas@2224
|
1549 {
|
nicholas@2224
|
1550 this.interfaceDOM = interfaceObject;
|
nicholas@2224
|
1551 this.metric.initialise(interfaceObject.getValue());
|
nicholas@2224
|
1552 if (this.state == 1)
|
nicholas@2224
|
1553 {
|
nicholas@2224
|
1554 this.interfaceDOM.enable();
|
nicholas@2224
|
1555 } else if (this.state == -1) {
|
nicholas@2224
|
1556 // ERROR
|
nicholas@2224
|
1557 this.interfaceDOM.error();
|
nicholas@2224
|
1558 return;
|
nicholas@2224
|
1559 }
|
nicholas@2224
|
1560 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
|
nicholas@2224
|
1561 };
|
nicholas@2224
|
1562
|
nicholas@2224
|
1563 this.loopStart = function(setTime) {
|
nicholas@2224
|
1564 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
|
nicholas@2224
|
1565 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1566 this.interfaceDOM.startPlayback();
|
nicholas@2224
|
1567 };
|
nicholas@2224
|
1568
|
nicholas@2224
|
1569 this.loopStop = function(setTime) {
|
nicholas@2224
|
1570 if (this.outputGain.gain.value != 0.0) {
|
nicholas@2224
|
1571 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
|
nicholas@2224
|
1572 this.metric.stopListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1573 }
|
nicholas@2224
|
1574 this.interfaceDOM.stopPlayback();
|
nicholas@2224
|
1575 };
|
nicholas@2224
|
1576
|
nicholas@2224
|
1577 this.play = function(startTime) {
|
nicholas@2224
|
1578 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
|
nicholas@2224
|
1579 this.bufferNode = audioContext.createBufferSource();
|
nicholas@2224
|
1580 this.bufferNode.owner = this;
|
nicholas@2224
|
1581 this.bufferNode.connect(this.outputGain);
|
nicholas@2224
|
1582 this.bufferNode.buffer = this.buffer.buffer;
|
nicholas@2224
|
1583 this.bufferNode.loop = audioEngineContext.loopPlayback;
|
nicholas@2224
|
1584 this.bufferNode.onended = function(event) {
|
nicholas@2224
|
1585 // Safari does not like using 'this' to reference the calling object!
|
nicholas@2224
|
1586 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
|
nicholas@2224
|
1587 if (event.currentTarget != null) {
|
nicholas@2224
|
1588 event.currentTarget.owner.stop(audioContext.currentTime+1);
|
nicholas@2224
|
1589 }
|
nicholas@2224
|
1590 };
|
nicholas@2224
|
1591 if (this.bufferNode.loop == false) {
|
nicholas@2224
|
1592 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1593 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
|
nicholas@2224
|
1594 this.interfaceDOM.startPlayback();
|
nicholas@2224
|
1595 } else {
|
nicholas@2224
|
1596 this.outputGain.gain.setValueAtTime(0.0,startTime);
|
nicholas@2224
|
1597 }
|
nicholas@2224
|
1598 this.bufferNode.start(startTime);
|
nicholas@2224
|
1599 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
1600 }
|
nicholas@2224
|
1601 };
|
nicholas@2224
|
1602
|
nicholas@2224
|
1603 this.stop = function(stopTime) {
|
nicholas@2224
|
1604 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
nicholas@2224
|
1605 if (this.bufferNode != undefined)
|
nicholas@2224
|
1606 {
|
nicholas@2224
|
1607 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
|
nicholas@2224
|
1608 this.bufferNode.stop(stopTime);
|
nicholas@2224
|
1609 this.bufferNode = undefined;
|
nicholas@2224
|
1610 }
|
nicholas@2224
|
1611 this.outputGain.gain.value = 0.0;
|
nicholas@2224
|
1612 this.interfaceDOM.stopPlayback();
|
nicholas@2224
|
1613 };
|
nicholas@2224
|
1614
|
nicholas@2224
|
1615 this.getCurrentPosition = function() {
|
nicholas@2224
|
1616 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
1617 if (this.bufferNode != undefined) {
|
nicholas@2224
|
1618 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
|
nicholas@2224
|
1619 if (isNaN(position)){return 0;}
|
nicholas@2224
|
1620 return position;
|
nicholas@2224
|
1621 } else {
|
nicholas@2224
|
1622 return 0;
|
nicholas@2224
|
1623 }
|
nicholas@2224
|
1624 };
|
nicholas@2224
|
1625
|
nicholas@2224
|
1626 this.exportXMLDOM = function() {
|
nicholas@2224
|
1627 var file = storage.document.createElement('file');
|
nicholas@2224
|
1628 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
|
nicholas@2224
|
1629 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
|
nicholas@2224
|
1630 file.setAttribute('sampleCount',this.buffer.buffer.length);
|
nicholas@2224
|
1631 file.setAttribute('duration',this.buffer.buffer.duration);
|
nicholas@2224
|
1632 this.storeDOM.appendChild(file);
|
nicholas@2224
|
1633 if (this.specification.type != 'outside-reference') {
|
nicholas@2224
|
1634 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
nicholas@2224
|
1635 if (interfaceXML != null)
|
nicholas@2224
|
1636 {
|
nicholas@2224
|
1637 if (interfaceXML.length == undefined) {
|
nicholas@2224
|
1638 this.storeDOM.appendChild(interfaceXML);
|
nicholas@2224
|
1639 } else {
|
nicholas@2224
|
1640 for (var i=0; i<interfaceXML.length; i++)
|
nicholas@2224
|
1641 {
|
nicholas@2224
|
1642 this.storeDOM.appendChild(interfaceXML[i]);
|
nicholas@2224
|
1643 }
|
nicholas@2224
|
1644 }
|
nicholas@2224
|
1645 }
|
nicholas@2224
|
1646 if (this.commentDOM != null) {
|
nicholas@2224
|
1647 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
nicholas@2224
|
1648 }
|
nicholas@2224
|
1649 }
|
nicholas@2224
|
1650 var nodes = this.metric.exportXMLDOM();
|
nicholas@2224
|
1651 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
|
nicholas@2224
|
1652 for (var i=0; i<nodes.length; i++)
|
nicholas@2224
|
1653 {
|
nicholas@2224
|
1654 mroot.appendChild(nodes[i]);
|
nicholas@2224
|
1655 }
|
nicholas@2224
|
1656 };
|
nicholas@2224
|
1657 }
|
nicholas@2224
|
1658
|
nicholas@2224
|
1659 function timer()
|
nicholas@2224
|
1660 {
|
nicholas@2224
|
1661 /* Timer object used in audioEngine to keep track of session timings
|
nicholas@2224
|
1662 * Uses the timer of the web audio API, so sample resolution
|
nicholas@2224
|
1663 */
|
nicholas@2224
|
1664 this.testStarted = false;
|
nicholas@2224
|
1665 this.testStartTime = 0;
|
nicholas@2224
|
1666 this.testDuration = 0;
|
nicholas@2224
|
1667 this.minimumTestTime = 0; // No minimum test time
|
nicholas@2224
|
1668 this.startTest = function()
|
nicholas@2224
|
1669 {
|
nicholas@2224
|
1670 if (this.testStarted == false)
|
nicholas@2224
|
1671 {
|
nicholas@2224
|
1672 this.testStartTime = audioContext.currentTime;
|
nicholas@2224
|
1673 this.testStarted = true;
|
nicholas@2224
|
1674 this.updateTestTime();
|
nicholas@2224
|
1675 audioEngineContext.metric.initialiseTest();
|
nicholas@2224
|
1676 }
|
nicholas@2224
|
1677 };
|
nicholas@2224
|
1678 this.stopTest = function()
|
nicholas@2224
|
1679 {
|
nicholas@2224
|
1680 if (this.testStarted)
|
nicholas@2224
|
1681 {
|
nicholas@2224
|
1682 this.testDuration = this.getTestTime();
|
nicholas@2224
|
1683 this.testStarted = false;
|
nicholas@2224
|
1684 } else {
|
nicholas@2224
|
1685 console.log('ERR: Test tried to end before beginning');
|
nicholas@2224
|
1686 }
|
nicholas@2224
|
1687 };
|
nicholas@2224
|
1688 this.updateTestTime = function()
|
nicholas@2224
|
1689 {
|
nicholas@2224
|
1690 if (this.testStarted)
|
nicholas@2224
|
1691 {
|
nicholas@2224
|
1692 this.testDuration = audioContext.currentTime - this.testStartTime;
|
nicholas@2224
|
1693 }
|
nicholas@2224
|
1694 };
|
nicholas@2224
|
1695 this.getTestTime = function()
|
nicholas@2224
|
1696 {
|
nicholas@2224
|
1697 this.updateTestTime();
|
nicholas@2224
|
1698 return this.testDuration;
|
nicholas@2224
|
1699 };
|
nicholas@2224
|
1700 }
|
nicholas@2224
|
1701
|
nicholas@2224
|
1702 function sessionMetrics(engine,specification)
|
nicholas@2224
|
1703 {
|
nicholas@2224
|
1704 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
nicholas@2224
|
1705 */
|
nicholas@2224
|
1706 this.engine = engine;
|
nicholas@2224
|
1707 this.lastClicked = -1;
|
nicholas@2224
|
1708 this.data = -1;
|
nicholas@2224
|
1709 this.reset = function() {
|
nicholas@2224
|
1710 this.lastClicked = -1;
|
nicholas@2224
|
1711 this.data = -1;
|
nicholas@2224
|
1712 };
|
nicholas@2224
|
1713
|
nicholas@2224
|
1714 this.enableElementInitialPosition = false;
|
nicholas@2224
|
1715 this.enableElementListenTracker = false;
|
nicholas@2224
|
1716 this.enableElementTimer = false;
|
nicholas@2224
|
1717 this.enableElementTracker = false;
|
nicholas@2224
|
1718 this.enableFlagListenedTo = false;
|
nicholas@2224
|
1719 this.enableFlagMoved = false;
|
nicholas@2224
|
1720 this.enableTestTimer = false;
|
nicholas@2224
|
1721 // Obtain the metrics enabled
|
nicholas@2224
|
1722 for (var i=0; i<specification.metrics.enabled.length; i++)
|
nicholas@2224
|
1723 {
|
nicholas@2224
|
1724 var node = specification.metrics.enabled[i];
|
nicholas@2224
|
1725 switch(node)
|
nicholas@2224
|
1726 {
|
nicholas@2224
|
1727 case 'testTimer':
|
nicholas@2224
|
1728 this.enableTestTimer = true;
|
nicholas@2224
|
1729 break;
|
nicholas@2224
|
1730 case 'elementTimer':
|
nicholas@2224
|
1731 this.enableElementTimer = true;
|
nicholas@2224
|
1732 break;
|
nicholas@2224
|
1733 case 'elementTracker':
|
nicholas@2224
|
1734 this.enableElementTracker = true;
|
nicholas@2224
|
1735 break;
|
nicholas@2224
|
1736 case 'elementListenTracker':
|
nicholas@2224
|
1737 this.enableElementListenTracker = true;
|
nicholas@2224
|
1738 break;
|
nicholas@2224
|
1739 case 'elementInitialPosition':
|
nicholas@2224
|
1740 this.enableElementInitialPosition = true;
|
nicholas@2224
|
1741 break;
|
nicholas@2224
|
1742 case 'elementFlagListenedTo':
|
nicholas@2224
|
1743 this.enableFlagListenedTo = true;
|
nicholas@2224
|
1744 break;
|
nicholas@2224
|
1745 case 'elementFlagMoved':
|
nicholas@2224
|
1746 this.enableFlagMoved = true;
|
nicholas@2224
|
1747 break;
|
nicholas@2224
|
1748 case 'elementFlagComments':
|
nicholas@2224
|
1749 this.enableFlagComments = true;
|
nicholas@2224
|
1750 break;
|
nicholas@2224
|
1751 }
|
nicholas@2224
|
1752 }
|
nicholas@2224
|
1753 this.initialiseTest = function(){};
|
nicholas@2224
|
1754 }
|
nicholas@2224
|
1755
|
nicholas@2224
|
1756 function metricTracker(caller)
|
nicholas@2224
|
1757 {
|
nicholas@2224
|
1758 /* Custom object to track and collect metric data
|
nicholas@2224
|
1759 * Used only inside the audioObjects object.
|
nicholas@2224
|
1760 */
|
nicholas@2224
|
1761
|
nicholas@2224
|
1762 this.listenedTimer = 0;
|
nicholas@2224
|
1763 this.listenStart = 0;
|
nicholas@2224
|
1764 this.listenHold = false;
|
nicholas@2224
|
1765 this.initialPosition = -1;
|
nicholas@2224
|
1766 this.movementTracker = [];
|
nicholas@2224
|
1767 this.listenTracker =[];
|
nicholas@2224
|
1768 this.wasListenedTo = false;
|
nicholas@2224
|
1769 this.wasMoved = false;
|
nicholas@2224
|
1770 this.hasComments = false;
|
nicholas@2224
|
1771 this.parent = caller;
|
nicholas@2224
|
1772
|
nicholas@2224
|
1773 this.initialise = function(position)
|
nicholas@2224
|
1774 {
|
nicholas@2224
|
1775 if (this.initialPosition == -1) {
|
nicholas@2224
|
1776 this.initialPosition = position;
|
nicholas@2224
|
1777 this.moved(0,position);
|
nicholas@2224
|
1778 }
|
nicholas@2224
|
1779 };
|
nicholas@2224
|
1780
|
nicholas@2224
|
1781 this.moved = function(time,position)
|
nicholas@2224
|
1782 {
|
nicholas@2224
|
1783 if (time > 0) {this.wasMoved = true;}
|
nicholas@2224
|
1784 this.movementTracker[this.movementTracker.length] = [time, position];
|
nicholas@2224
|
1785 };
|
nicholas@2224
|
1786
|
nicholas@2224
|
1787 this.startListening = function(time)
|
nicholas@2224
|
1788 {
|
nicholas@2224
|
1789 if (this.listenHold == false)
|
nicholas@2224
|
1790 {
|
nicholas@2224
|
1791 this.wasListenedTo = true;
|
nicholas@2224
|
1792 this.listenStart = time;
|
nicholas@2224
|
1793 this.listenHold = true;
|
nicholas@2224
|
1794
|
nicholas@2224
|
1795 var evnt = document.createElement('event');
|
nicholas@2224
|
1796 var testTime = document.createElement('testTime');
|
nicholas@2224
|
1797 testTime.setAttribute('start',time);
|
nicholas@2224
|
1798 var bufferTime = document.createElement('bufferTime');
|
nicholas@2224
|
1799 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
|
nicholas@2224
|
1800 evnt.appendChild(testTime);
|
nicholas@2224
|
1801 evnt.appendChild(bufferTime);
|
nicholas@2224
|
1802 this.listenTracker.push(evnt);
|
nicholas@2224
|
1803
|
nicholas@2224
|
1804 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2224
|
1805 }
|
nicholas@2224
|
1806 };
|
nicholas@2224
|
1807
|
nicholas@2224
|
1808 this.stopListening = function(time,bufferStopTime)
|
nicholas@2224
|
1809 {
|
nicholas@2224
|
1810 if (this.listenHold == true)
|
nicholas@2224
|
1811 {
|
nicholas@2224
|
1812 var diff = time - this.listenStart;
|
nicholas@2224
|
1813 this.listenedTimer += (diff);
|
nicholas@2224
|
1814 this.listenStart = 0;
|
nicholas@2224
|
1815 this.listenHold = false;
|
nicholas@2224
|
1816
|
nicholas@2224
|
1817 var evnt = this.listenTracker[this.listenTracker.length-1];
|
nicholas@2224
|
1818 var testTime = evnt.getElementsByTagName('testTime')[0];
|
nicholas@2224
|
1819 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
nicholas@2224
|
1820 testTime.setAttribute('stop',time);
|
nicholas@2224
|
1821 if (bufferStopTime == undefined) {
|
nicholas@2224
|
1822 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
|
nicholas@2224
|
1823 } else {
|
nicholas@2224
|
1824 bufferTime.setAttribute('stop',bufferStopTime);
|
nicholas@2224
|
1825 }
|
nicholas@2224
|
1826 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2224
|
1827 }
|
nicholas@2224
|
1828 };
|
nicholas@2224
|
1829
|
nicholas@2224
|
1830 this.exportXMLDOM = function() {
|
nicholas@2224
|
1831 var storeDOM = [];
|
nicholas@2224
|
1832 if (audioEngineContext.metric.enableElementTimer) {
|
nicholas@2224
|
1833 var mElementTimer = storage.document.createElement('metricresult');
|
nicholas@2224
|
1834 mElementTimer.setAttribute('name','enableElementTimer');
|
nicholas@2224
|
1835 mElementTimer.textContent = this.listenedTimer;
|
nicholas@2224
|
1836 storeDOM.push(mElementTimer);
|
nicholas@2224
|
1837 }
|
nicholas@2224
|
1838 if (audioEngineContext.metric.enableElementTracker) {
|
b@2281
|
1839 var elementTrackerFull = storage.document.createElement('metricresult');
|
nicholas@2224
|
1840 elementTrackerFull.setAttribute('name','elementTrackerFull');
|
nicholas@2224
|
1841 for (var k=0; k<this.movementTracker.length; k++)
|
nicholas@2224
|
1842 {
|
nicholas@2224
|
1843 var timePos = storage.document.createElement('movement');
|
nicholas@2224
|
1844 timePos.setAttribute("time",this.movementTracker[k][0]);
|
nicholas@2224
|
1845 timePos.setAttribute("value",this.movementTracker[k][1]);
|
nicholas@2224
|
1846 elementTrackerFull.appendChild(timePos);
|
nicholas@2224
|
1847 }
|
nicholas@2224
|
1848 storeDOM.push(elementTrackerFull);
|
nicholas@2224
|
1849 }
|
nicholas@2224
|
1850 if (audioEngineContext.metric.enableElementListenTracker) {
|
b@2281
|
1851 var elementListenTracker = storage.document.createElement('metricresult');
|
nicholas@2224
|
1852 elementListenTracker.setAttribute('name','elementListenTracker');
|
nicholas@2224
|
1853 for (var k=0; k<this.listenTracker.length; k++) {
|
nicholas@2224
|
1854 elementListenTracker.appendChild(this.listenTracker[k]);
|
nicholas@2224
|
1855 }
|
nicholas@2224
|
1856 storeDOM.push(elementListenTracker);
|
nicholas@2224
|
1857 }
|
nicholas@2224
|
1858 if (audioEngineContext.metric.enableElementInitialPosition) {
|
b@2281
|
1859 var elementInitial = storage.document.createElement('metricresult');
|
nicholas@2224
|
1860 elementInitial.setAttribute('name','elementInitialPosition');
|
nicholas@2224
|
1861 elementInitial.textContent = this.initialPosition;
|
nicholas@2224
|
1862 storeDOM.push(elementInitial);
|
nicholas@2224
|
1863 }
|
nicholas@2224
|
1864 if (audioEngineContext.metric.enableFlagListenedTo) {
|
b@2281
|
1865 var flagListenedTo = storage.document.createElement('metricresult');
|
nicholas@2224
|
1866 flagListenedTo.setAttribute('name','elementFlagListenedTo');
|
nicholas@2224
|
1867 flagListenedTo.textContent = this.wasListenedTo;
|
nicholas@2224
|
1868 storeDOM.push(flagListenedTo);
|
nicholas@2224
|
1869 }
|
nicholas@2224
|
1870 if (audioEngineContext.metric.enableFlagMoved) {
|
b@2281
|
1871 var flagMoved = storage.document.createElement('metricresult');
|
nicholas@2224
|
1872 flagMoved.setAttribute('name','elementFlagMoved');
|
nicholas@2224
|
1873 flagMoved.textContent = this.wasMoved;
|
nicholas@2224
|
1874 storeDOM.push(flagMoved);
|
nicholas@2224
|
1875 }
|
nicholas@2224
|
1876 if (audioEngineContext.metric.enableFlagComments) {
|
b@2281
|
1877 var flagComments = storage.document.createElement('metricresult');
|
nicholas@2224
|
1878 flagComments.setAttribute('name','elementFlagComments');
|
nicholas@2224
|
1879 if (this.parent.commentDOM == null)
|
nicholas@2224
|
1880 {flag.textContent = 'false';}
|
nicholas@2224
|
1881 else if (this.parent.commentDOM.textContent.length == 0)
|
nicholas@2224
|
1882 {flag.textContent = 'false';}
|
nicholas@2224
|
1883 else
|
nicholas@2224
|
1884 {flag.textContet = 'true';}
|
nicholas@2224
|
1885 storeDOM.push(flagComments);
|
nicholas@2224
|
1886 }
|
nicholas@2224
|
1887 return storeDOM;
|
nicholas@2224
|
1888 };
|
nicholas@2224
|
1889 }
|
nicholas@2224
|
1890
|
nicholas@2224
|
1891 function Interface(specificationObject) {
|
nicholas@2224
|
1892 // This handles the bindings between the interface and the audioEngineContext;
|
nicholas@2224
|
1893 this.specification = specificationObject;
|
nicholas@2224
|
1894 this.insertPoint = document.getElementById("topLevelBody");
|
nicholas@2224
|
1895
|
nicholas@2224
|
1896 this.newPage = function(audioHolderObject,store)
|
nicholas@2224
|
1897 {
|
nicholas@2224
|
1898 audioEngineContext.newTestPage(audioHolderObject,store);
|
nicholas@2224
|
1899 interfaceContext.commentBoxes.deleteCommentBoxes();
|
nicholas@2224
|
1900 interfaceContext.deleteCommentQuestions();
|
nicholas@2224
|
1901 loadTest(audioHolderObject,store);
|
nicholas@2224
|
1902 };
|
nicholas@2224
|
1903
|
nicholas@2224
|
1904 // Bounded by interface!!
|
nicholas@2224
|
1905 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
nicholas@2224
|
1906 // For example, APE returns the slider position normalised in a <value> tag.
|
nicholas@2224
|
1907 this.interfaceObjects = [];
|
nicholas@2224
|
1908 this.interfaceObject = function(){};
|
nicholas@2224
|
1909
|
nicholas@2224
|
1910 this.resizeWindow = function(event)
|
nicholas@2224
|
1911 {
|
nicholas@2224
|
1912 popup.resize(event);
|
nicholas@2224
|
1913 for(var i=0; i<this.commentBoxes.length; i++)
|
nicholas@2224
|
1914 {this.commentBoxes[i].resize();}
|
nicholas@2224
|
1915 for(var i=0; i<this.commentQuestions.length; i++)
|
nicholas@2224
|
1916 {this.commentQuestions[i].resize();}
|
nicholas@2224
|
1917 try
|
nicholas@2224
|
1918 {
|
nicholas@2224
|
1919 resizeWindow(event);
|
nicholas@2224
|
1920 }
|
nicholas@2224
|
1921 catch(err)
|
nicholas@2224
|
1922 {
|
nicholas@2224
|
1923 console.log("Warning - Interface does not have Resize option");
|
nicholas@2224
|
1924 console.log(err);
|
nicholas@2224
|
1925 }
|
nicholas@2224
|
1926 };
|
nicholas@2224
|
1927
|
nicholas@2224
|
1928 this.returnNavigator = function()
|
nicholas@2224
|
1929 {
|
nicholas@2224
|
1930 var node = storage.document.createElement("navigator");
|
nicholas@2224
|
1931 var platform = storage.document.createElement("platform");
|
nicholas@2224
|
1932 platform.textContent = navigator.platform;
|
nicholas@2224
|
1933 var vendor = storage.document.createElement("vendor");
|
nicholas@2224
|
1934 vendor.textContent = navigator.vendor;
|
nicholas@2224
|
1935 var userAgent = storage.document.createElement("uagent");
|
nicholas@2224
|
1936 userAgent.textContent = navigator.userAgent;
|
nicholas@2224
|
1937 var screen = storage.document.createElement("window");
|
nicholas@2224
|
1938 screen.setAttribute('innerWidth',window.innerWidth);
|
nicholas@2224
|
1939 screen.setAttribute('innerHeight',window.innerHeight);
|
nicholas@2224
|
1940 node.appendChild(platform);
|
nicholas@2224
|
1941 node.appendChild(vendor);
|
nicholas@2224
|
1942 node.appendChild(userAgent);
|
nicholas@2224
|
1943 node.appendChild(screen);
|
nicholas@2224
|
1944 return node;
|
nicholas@2224
|
1945 };
|
nicholas@2224
|
1946
|
nicholas@2224
|
1947 this.returnDateNode = function()
|
nicholas@2224
|
1948 {
|
nicholas@2224
|
1949 // Create an XML Node for the Date and Time a test was conducted
|
nicholas@2224
|
1950 // Structure is
|
nicholas@2224
|
1951 // <datetime>
|
nicholas@2224
|
1952 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
nicholas@2224
|
1953 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
nicholas@2224
|
1954 // </datetime>
|
nicholas@2224
|
1955 var dateTime = new Date();
|
nicholas@2224
|
1956 var hold = storage.document.createElement("datetime");
|
nicholas@2224
|
1957 var date = storage.document.createElement("date");
|
nicholas@2224
|
1958 var time = storage.document.createElement("time");
|
nicholas@2224
|
1959 date.setAttribute('year',dateTime.getFullYear());
|
nicholas@2224
|
1960 date.setAttribute('month',dateTime.getMonth()+1);
|
nicholas@2224
|
1961 date.setAttribute('day',dateTime.getDate());
|
nicholas@2224
|
1962 time.setAttribute('hour',dateTime.getHours());
|
nicholas@2224
|
1963 time.setAttribute('minute',dateTime.getMinutes());
|
nicholas@2224
|
1964 time.setAttribute('secs',dateTime.getSeconds());
|
nicholas@2224
|
1965
|
nicholas@2224
|
1966 hold.appendChild(date);
|
nicholas@2224
|
1967 hold.appendChild(time);
|
nicholas@2224
|
1968 return hold;
|
nicholas@2224
|
1969
|
nicholas@2224
|
1970 }
|
nicholas@2224
|
1971
|
nicholas@2224
|
1972 this.commentBoxes = new function() {
|
nicholas@2224
|
1973 this.boxes = [];
|
nicholas@2224
|
1974 this.injectPoint = null;
|
nicholas@2224
|
1975 this.elementCommentBox = function(audioObject) {
|
nicholas@2224
|
1976 var element = audioObject.specification;
|
nicholas@2224
|
1977 this.audioObject = audioObject;
|
nicholas@2224
|
1978 this.id = audioObject.id;
|
nicholas@2224
|
1979 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
1980 // Create document objects to hold the comment boxes
|
nicholas@2224
|
1981 this.trackComment = document.createElement('div');
|
nicholas@2224
|
1982 this.trackComment.className = 'comment-div';
|
nicholas@2224
|
1983 this.trackComment.id = 'comment-div-'+audioObject.id;
|
nicholas@2224
|
1984 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
1985 this.trackString = document.createElement('span');
|
nicholas@2224
|
1986 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
1987 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
1988 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
1989 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
1990 this.trackCommentBox.cols = '100';
|
nicholas@2224
|
1991 this.trackCommentBox.name = 'trackComment'+audioObject.id;
|
nicholas@2224
|
1992 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
1993 var br = document.createElement('br');
|
nicholas@2224
|
1994 // Add to the holder.
|
nicholas@2224
|
1995 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
1996 this.trackComment.appendChild(br);
|
nicholas@2224
|
1997 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
1998
|
nicholas@2224
|
1999 this.exportXMLDOM = function() {
|
nicholas@2224
|
2000 var root = document.createElement('comment');
|
nicholas@2224
|
2001 var question = document.createElement('question');
|
nicholas@2224
|
2002 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2003 var response = document.createElement('response');
|
nicholas@2224
|
2004 response.textContent = this.trackCommentBox.value;
|
nicholas@2224
|
2005 console.log("Comment frag-"+this.id+": "+response.textContent);
|
nicholas@2224
|
2006 root.appendChild(question);
|
nicholas@2224
|
2007 root.appendChild(response);
|
nicholas@2224
|
2008 return root;
|
nicholas@2224
|
2009 };
|
nicholas@2224
|
2010 this.resize = function()
|
nicholas@2224
|
2011 {
|
nicholas@2224
|
2012 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2013 if (boxwidth >= 600)
|
nicholas@2224
|
2014 {
|
nicholas@2224
|
2015 boxwidth = 600;
|
nicholas@2224
|
2016 }
|
nicholas@2224
|
2017 else if (boxwidth < 400)
|
nicholas@2224
|
2018 {
|
nicholas@2224
|
2019 boxwidth = 400;
|
nicholas@2224
|
2020 }
|
nicholas@2224
|
2021 this.trackComment.style.width = boxwidth+"px";
|
nicholas@2224
|
2022 this.trackCommentBox.style.width = boxwidth-6+"px";
|
nicholas@2224
|
2023 };
|
nicholas@2224
|
2024 this.resize();
|
nicholas@2224
|
2025 };
|
nicholas@2224
|
2026 this.createCommentBox = function(audioObject) {
|
nicholas@2224
|
2027 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2028 this.boxes.push(node);
|
nicholas@2224
|
2029 audioObject.commentDOM = node;
|
nicholas@2224
|
2030 return node;
|
nicholas@2224
|
2031 };
|
nicholas@2224
|
2032 this.sortCommentBoxes = function() {
|
nicholas@2224
|
2033 this.boxes.sort(function(a,b){return a.id - b.id;});
|
nicholas@2224
|
2034 };
|
nicholas@2224
|
2035
|
nicholas@2224
|
2036 this.showCommentBoxes = function(inject, sort) {
|
nicholas@2224
|
2037 this.injectPoint = inject;
|
nicholas@2224
|
2038 if (sort) {this.sortCommentBoxes();}
|
nicholas@2224
|
2039 for (var box of this.boxes) {
|
nicholas@2224
|
2040 inject.appendChild(box.trackComment);
|
nicholas@2224
|
2041 }
|
nicholas@2224
|
2042 };
|
nicholas@2224
|
2043
|
nicholas@2224
|
2044 this.deleteCommentBoxes = function() {
|
nicholas@2224
|
2045 if (this.injectPoint != null) {
|
nicholas@2224
|
2046 for (var box of this.boxes) {
|
nicholas@2224
|
2047 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2224
|
2048 }
|
nicholas@2224
|
2049 this.injectPoint = null;
|
nicholas@2224
|
2050 }
|
nicholas@2224
|
2051 this.boxes = [];
|
nicholas@2224
|
2052 };
|
nicholas@2224
|
2053 }
|
nicholas@2224
|
2054
|
nicholas@2224
|
2055 this.commentQuestions = [];
|
nicholas@2224
|
2056
|
nicholas@2224
|
2057 this.commentBox = function(commentQuestion) {
|
nicholas@2224
|
2058 this.specification = commentQuestion;
|
nicholas@2224
|
2059 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2060 this.holder = document.createElement('div');
|
nicholas@2224
|
2061 this.holder.className = 'comment-div';
|
nicholas@2224
|
2062 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2063 this.string = document.createElement('span');
|
nicholas@2224
|
2064 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2065 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2066 this.textArea = document.createElement('textarea');
|
nicholas@2224
|
2067 this.textArea.rows = '4';
|
nicholas@2224
|
2068 this.textArea.cols = '100';
|
nicholas@2224
|
2069 this.textArea.className = 'trackComment';
|
nicholas@2224
|
2070 var br = document.createElement('br');
|
nicholas@2224
|
2071 // Add to the holder.
|
nicholas@2224
|
2072 this.holder.appendChild(this.string);
|
nicholas@2224
|
2073 this.holder.appendChild(br);
|
nicholas@2224
|
2074 this.holder.appendChild(this.textArea);
|
nicholas@2224
|
2075
|
nicholas@2224
|
2076 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2077 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2078 root.id = this.specification.id;
|
nicholas@2224
|
2079 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2080 console.log("Question: "+this.string.textContent);
|
nicholas@2224
|
2081 console.log("Response: "+root.textContent);
|
nicholas@2224
|
2082 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2083 question.textContent = this.string.textContent;
|
nicholas@2224
|
2084 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2085 response.textContent = this.textArea.value;
|
nicholas@2224
|
2086 root.appendChild(question);
|
nicholas@2224
|
2087 root.appendChild(response);
|
nicholas@2224
|
2088 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2089 return root;
|
nicholas@2224
|
2090 };
|
nicholas@2224
|
2091 this.resize = function()
|
nicholas@2224
|
2092 {
|
nicholas@2224
|
2093 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2094 if (boxwidth >= 600)
|
nicholas@2224
|
2095 {
|
nicholas@2224
|
2096 boxwidth = 600;
|
nicholas@2224
|
2097 }
|
nicholas@2224
|
2098 else if (boxwidth < 400)
|
nicholas@2224
|
2099 {
|
nicholas@2224
|
2100 boxwidth = 400;
|
nicholas@2224
|
2101 }
|
nicholas@2224
|
2102 this.holder.style.width = boxwidth+"px";
|
nicholas@2224
|
2103 this.textArea.style.width = boxwidth-6+"px";
|
nicholas@2224
|
2104 };
|
nicholas@2224
|
2105 this.resize();
|
nicholas@2224
|
2106 };
|
nicholas@2224
|
2107
|
nicholas@2224
|
2108 this.radioBox = function(commentQuestion) {
|
nicholas@2224
|
2109 this.specification = commentQuestion;
|
nicholas@2224
|
2110 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2111 this.holder = document.createElement('div');
|
nicholas@2224
|
2112 this.holder.className = 'comment-div';
|
nicholas@2224
|
2113 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2114 this.string = document.createElement('span');
|
nicholas@2224
|
2115 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2116 var br = document.createElement('br');
|
nicholas@2224
|
2117 // Add to the holder.
|
nicholas@2224
|
2118 this.holder.appendChild(this.string);
|
nicholas@2224
|
2119 this.holder.appendChild(br);
|
nicholas@2224
|
2120 this.options = [];
|
nicholas@2224
|
2121 this.inputs = document.createElement('div');
|
nicholas@2224
|
2122 this.span = document.createElement('div');
|
nicholas@2224
|
2123 this.inputs.align = 'center';
|
nicholas@2224
|
2124 this.inputs.style.marginLeft = '12px';
|
nicholas@2294
|
2125 this.inputs.className = "comment-radio-inputs-holder";
|
nicholas@2224
|
2126 this.span.style.marginLeft = '12px';
|
nicholas@2224
|
2127 this.span.align = 'center';
|
nicholas@2224
|
2128 this.span.style.marginTop = '15px';
|
nicholas@2294
|
2129 this.span.className = "comment-radio-span-holder";
|
nicholas@2224
|
2130
|
nicholas@2224
|
2131 var optCount = commentQuestion.options.length;
|
nicholas@2224
|
2132 for (var optNode of commentQuestion.options)
|
nicholas@2224
|
2133 {
|
nicholas@2224
|
2134 var div = document.createElement('div');
|
nicholas@2224
|
2135 div.style.width = '80px';
|
nicholas@2224
|
2136 div.style.float = 'left';
|
nicholas@2224
|
2137 var input = document.createElement('input');
|
nicholas@2224
|
2138 input.type = 'radio';
|
nicholas@2224
|
2139 input.name = commentQuestion.id;
|
nicholas@2224
|
2140 input.setAttribute('setvalue',optNode.name);
|
nicholas@2224
|
2141 input.className = 'comment-radio';
|
nicholas@2224
|
2142 div.appendChild(input);
|
nicholas@2224
|
2143 this.inputs.appendChild(div);
|
nicholas@2224
|
2144
|
nicholas@2224
|
2145
|
nicholas@2224
|
2146 div = document.createElement('div');
|
nicholas@2224
|
2147 div.style.width = '80px';
|
nicholas@2224
|
2148 div.style.float = 'left';
|
nicholas@2224
|
2149 div.align = 'center';
|
nicholas@2224
|
2150 var span = document.createElement('span');
|
nicholas@2224
|
2151 span.textContent = optNode.text;
|
nicholas@2224
|
2152 span.className = 'comment-radio-span';
|
nicholas@2224
|
2153 div.appendChild(span);
|
nicholas@2224
|
2154 this.span.appendChild(div);
|
nicholas@2224
|
2155 this.options.push(input);
|
nicholas@2224
|
2156 }
|
nicholas@2224
|
2157 this.holder.appendChild(this.span);
|
nicholas@2224
|
2158 this.holder.appendChild(this.inputs);
|
nicholas@2224
|
2159
|
nicholas@2224
|
2160 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2161 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2162 root.id = this.specification.id;
|
nicholas@2224
|
2163 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2164 var question = document.createElement('question');
|
nicholas@2224
|
2165 question.textContent = this.string.textContent;
|
nicholas@2224
|
2166 var response = document.createElement('response');
|
nicholas@2224
|
2167 var i=0;
|
nicholas@2224
|
2168 while(this.options[i].checked == false) {
|
nicholas@2224
|
2169 i++;
|
nicholas@2224
|
2170 if (i >= this.options.length) {
|
nicholas@2224
|
2171 break;
|
nicholas@2224
|
2172 }
|
nicholas@2224
|
2173 }
|
nicholas@2224
|
2174 if (i >= this.options.length) {
|
nicholas@2224
|
2175 response.textContent = 'null';
|
nicholas@2224
|
2176 } else {
|
nicholas@2224
|
2177 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2224
|
2178 response.setAttribute('number',i);
|
nicholas@2224
|
2179 }
|
nicholas@2224
|
2180 console.log('Comment: '+question.textContent);
|
nicholas@2224
|
2181 console.log('Response: '+response.textContent);
|
nicholas@2224
|
2182 root.appendChild(question);
|
nicholas@2224
|
2183 root.appendChild(response);
|
nicholas@2224
|
2184 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2185 return root;
|
nicholas@2224
|
2186 };
|
nicholas@2224
|
2187 this.resize = function()
|
nicholas@2224
|
2188 {
|
nicholas@2224
|
2189 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2190 if (boxwidth >= 600)
|
nicholas@2224
|
2191 {
|
nicholas@2224
|
2192 boxwidth = 600;
|
nicholas@2224
|
2193 }
|
nicholas@2224
|
2194 else if (boxwidth < 400)
|
nicholas@2224
|
2195 {
|
nicholas@2224
|
2196 boxwidth = 400;
|
nicholas@2224
|
2197 }
|
nicholas@2224
|
2198 this.holder.style.width = boxwidth+"px";
|
nicholas@2294
|
2199 var text = this.holder.getElementsByClassName("comment-radio-span-holder")[0];
|
nicholas@2294
|
2200 var options = this.holder.getElementsByClassName("comment-radio-inputs-holder")[0];
|
nicholas@2294
|
2201 var optCount = options.childElementCount;
|
nicholas@2224
|
2202 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
nicholas@2224
|
2203 var options = options.firstChild;
|
nicholas@2224
|
2204 var text = text.firstChild;
|
nicholas@2224
|
2205 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2206 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2207 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2208 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2209 while(options.nextSibling != undefined)
|
nicholas@2224
|
2210 {
|
nicholas@2224
|
2211 options = options.nextSibling;
|
nicholas@2224
|
2212 text = text.nextSibling;
|
nicholas@2224
|
2213 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2214 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2215 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2216 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2217 }
|
nicholas@2224
|
2218 };
|
nicholas@2224
|
2219 this.resize();
|
nicholas@2224
|
2220 };
|
nicholas@2224
|
2221
|
nicholas@2224
|
2222 this.checkboxBox = function(commentQuestion) {
|
nicholas@2224
|
2223 this.specification = commentQuestion;
|
nicholas@2224
|
2224 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2225 this.holder = document.createElement('div');
|
nicholas@2224
|
2226 this.holder.className = 'comment-div';
|
nicholas@2224
|
2227 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2228 this.string = document.createElement('span');
|
nicholas@2224
|
2229 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2230 var br = document.createElement('br');
|
nicholas@2224
|
2231 // Add to the holder.
|
nicholas@2224
|
2232 this.holder.appendChild(this.string);
|
nicholas@2224
|
2233 this.holder.appendChild(br);
|
nicholas@2224
|
2234 this.options = [];
|
nicholas@2224
|
2235 this.inputs = document.createElement('div');
|
nicholas@2224
|
2236 this.span = document.createElement('div');
|
nicholas@2224
|
2237 this.inputs.align = 'center';
|
nicholas@2224
|
2238 this.inputs.style.marginLeft = '12px';
|
nicholas@2294
|
2239 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2224
|
2240 this.span.style.marginLeft = '12px';
|
nicholas@2224
|
2241 this.span.align = 'center';
|
nicholas@2224
|
2242 this.span.style.marginTop = '15px';
|
nicholas@2294
|
2243 this.span.className = "comment-checkbox-span-holder";
|
nicholas@2224
|
2244
|
nicholas@2224
|
2245 var optCount = commentQuestion.options.length;
|
nicholas@2224
|
2246 for (var i=0; i<optCount; i++)
|
nicholas@2224
|
2247 {
|
nicholas@2224
|
2248 var div = document.createElement('div');
|
nicholas@2224
|
2249 div.style.width = '80px';
|
nicholas@2224
|
2250 div.style.float = 'left';
|
nicholas@2224
|
2251 var input = document.createElement('input');
|
nicholas@2224
|
2252 input.type = 'checkbox';
|
nicholas@2224
|
2253 input.name = commentQuestion.id;
|
nicholas@2224
|
2254 input.setAttribute('setvalue',commentQuestion.options[i].name);
|
nicholas@2224
|
2255 input.className = 'comment-radio';
|
nicholas@2224
|
2256 div.appendChild(input);
|
nicholas@2224
|
2257 this.inputs.appendChild(div);
|
nicholas@2224
|
2258
|
nicholas@2224
|
2259
|
nicholas@2224
|
2260 div = document.createElement('div');
|
nicholas@2224
|
2261 div.style.width = '80px';
|
nicholas@2224
|
2262 div.style.float = 'left';
|
nicholas@2224
|
2263 div.align = 'center';
|
nicholas@2224
|
2264 var span = document.createElement('span');
|
nicholas@2224
|
2265 span.textContent = commentQuestion.options[i].text;
|
nicholas@2224
|
2266 span.className = 'comment-radio-span';
|
nicholas@2224
|
2267 div.appendChild(span);
|
nicholas@2224
|
2268 this.span.appendChild(div);
|
nicholas@2224
|
2269 this.options.push(input);
|
nicholas@2224
|
2270 }
|
nicholas@2224
|
2271 this.holder.appendChild(this.span);
|
nicholas@2224
|
2272 this.holder.appendChild(this.inputs);
|
nicholas@2224
|
2273
|
nicholas@2224
|
2274 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2275 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2276 root.id = this.specification.id;
|
nicholas@2224
|
2277 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2278 var question = document.createElement('question');
|
nicholas@2224
|
2279 question.textContent = this.string.textContent;
|
nicholas@2224
|
2280 root.appendChild(question);
|
nicholas@2224
|
2281 console.log('Comment: '+question.textContent);
|
nicholas@2224
|
2282 for (var i=0; i<this.options.length; i++) {
|
nicholas@2224
|
2283 var response = document.createElement('response');
|
nicholas@2224
|
2284 response.textContent = this.options[i].checked;
|
nicholas@2224
|
2285 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
|
nicholas@2224
|
2286 root.appendChild(response);
|
nicholas@2224
|
2287 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
|
nicholas@2224
|
2288 }
|
nicholas@2224
|
2289 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2290 return root;
|
nicholas@2224
|
2291 };
|
nicholas@2224
|
2292 this.resize = function()
|
nicholas@2224
|
2293 {
|
nicholas@2224
|
2294 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2295 if (boxwidth >= 600)
|
nicholas@2224
|
2296 {
|
nicholas@2224
|
2297 boxwidth = 600;
|
nicholas@2224
|
2298 }
|
nicholas@2224
|
2299 else if (boxwidth < 400)
|
nicholas@2224
|
2300 {
|
nicholas@2224
|
2301 boxwidth = 400;
|
nicholas@2224
|
2302 }
|
nicholas@2224
|
2303 this.holder.style.width = boxwidth+"px";
|
nicholas@2294
|
2304 var text = this.holder.getElementsByClassName("comment-checkbox-span-holder")[0];
|
nicholas@2294
|
2305 var options = this.holder.getElementsByClassName("comment-checkbox-inputs-holder")[0];
|
nicholas@2294
|
2306 var optCount = options.childElementCount;
|
nicholas@2224
|
2307 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
nicholas@2224
|
2308 var options = options.firstChild;
|
nicholas@2224
|
2309 var text = text.firstChild;
|
nicholas@2224
|
2310 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2311 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2312 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2313 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2314 while(options.nextSibling != undefined)
|
nicholas@2224
|
2315 {
|
nicholas@2224
|
2316 options = options.nextSibling;
|
nicholas@2224
|
2317 text = text.nextSibling;
|
nicholas@2224
|
2318 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2319 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2320 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2321 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2322 }
|
nicholas@2224
|
2323 };
|
nicholas@2224
|
2324 this.resize();
|
nicholas@2224
|
2325 };
|
nicholas@2224
|
2326
|
nicholas@2224
|
2327 this.createCommentQuestion = function(element) {
|
nicholas@2224
|
2328 var node;
|
nicholas@2224
|
2329 if (element.type == 'question') {
|
nicholas@2224
|
2330 node = new this.commentBox(element);
|
nicholas@2224
|
2331 } else if (element.type == 'radio') {
|
nicholas@2224
|
2332 node = new this.radioBox(element);
|
nicholas@2224
|
2333 } else if (element.type == 'checkbox') {
|
nicholas@2224
|
2334 node = new this.checkboxBox(element);
|
nicholas@2224
|
2335 }
|
nicholas@2224
|
2336 this.commentQuestions.push(node);
|
nicholas@2224
|
2337 return node;
|
nicholas@2224
|
2338 };
|
nicholas@2224
|
2339
|
nicholas@2224
|
2340 this.deleteCommentQuestions = function()
|
nicholas@2224
|
2341 {
|
nicholas@2224
|
2342 this.commentQuestions = [];
|
nicholas@2224
|
2343 };
|
nicholas@2224
|
2344
|
nicholas@2224
|
2345 this.outsideReferenceDOM = function(audioObject,index,inject)
|
nicholas@2224
|
2346 {
|
nicholas@2224
|
2347 this.parent = audioObject;
|
nicholas@2224
|
2348 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2349 this.outsideReferenceHolder.id = 'outside-reference';
|
nicholas@2224
|
2350 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2224
|
2351 this.outsideReferenceHolder.setAttribute('track-id',index);
|
nicholas@2224
|
2352 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2353 this.outsideReferenceHolder.disabled = true;
|
nicholas@2224
|
2354
|
nicholas@2224
|
2355 this.outsideReferenceHolder.onclick = function(event)
|
nicholas@2224
|
2356 {
|
nicholas@2224
|
2357 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
|
nicholas@2224
|
2358 };
|
nicholas@2224
|
2359 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2224
|
2360 this.enable = function()
|
nicholas@2224
|
2361 {
|
nicholas@2224
|
2362 if (this.parent.state == 1)
|
nicholas@2224
|
2363 {
|
nicholas@2224
|
2364 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2365 }
|
nicholas@2224
|
2366 };
|
nicholas@2224
|
2367 this.updateLoading = function(progress)
|
nicholas@2224
|
2368 {
|
nicholas@2224
|
2369 if (progress != 100)
|
nicholas@2224
|
2370 {
|
nicholas@2224
|
2371 progress = String(progress);
|
nicholas@2224
|
2372 progress = progress.split('.')[0];
|
nicholas@2224
|
2373 this.outsideReferenceHolder.textContent = progress+'%';
|
nicholas@2224
|
2374 } else {
|
nicholas@2224
|
2375 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2376 }
|
nicholas@2224
|
2377 };
|
nicholas@2224
|
2378 this.startPlayback = function()
|
nicholas@2224
|
2379 {
|
nicholas@2224
|
2380 // Called when playback has begun
|
nicholas@2224
|
2381 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2382 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2383 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2384 };
|
nicholas@2224
|
2385 this.stopPlayback = function()
|
nicholas@2224
|
2386 {
|
nicholas@2224
|
2387 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2388 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2389 };
|
nicholas@2224
|
2390 this.exportXMLDOM = function(audioObject)
|
nicholas@2224
|
2391 {
|
nicholas@2224
|
2392 return null;
|
nicholas@2224
|
2393 };
|
nicholas@2224
|
2394 this.getValue = function()
|
nicholas@2224
|
2395 {
|
nicholas@2224
|
2396 return 0;
|
nicholas@2224
|
2397 };
|
nicholas@2224
|
2398 this.getPresentedId = function()
|
nicholas@2224
|
2399 {
|
nicholas@2224
|
2400 return 'Reference';
|
nicholas@2224
|
2401 };
|
nicholas@2224
|
2402 this.canMove = function()
|
nicholas@2224
|
2403 {
|
nicholas@2224
|
2404 return false;
|
nicholas@2224
|
2405 };
|
nicholas@2224
|
2406 this.error = function() {
|
nicholas@2224
|
2407 // audioObject has an error!!
|
nicholas@2224
|
2408 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2409 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2224
|
2410 }
|
nicholas@2224
|
2411 }
|
nicholas@2224
|
2412
|
nicholas@2224
|
2413 this.playhead = new function()
|
nicholas@2224
|
2414 {
|
nicholas@2224
|
2415 this.object = document.createElement('div');
|
nicholas@2224
|
2416 this.object.className = 'playhead';
|
nicholas@2224
|
2417 this.object.align = 'left';
|
nicholas@2224
|
2418 var curTime = document.createElement('div');
|
nicholas@2224
|
2419 curTime.style.width = '50px';
|
nicholas@2224
|
2420 this.curTimeSpan = document.createElement('span');
|
nicholas@2224
|
2421 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2422 curTime.appendChild(this.curTimeSpan);
|
nicholas@2224
|
2423 this.object.appendChild(curTime);
|
nicholas@2224
|
2424 this.scrubberTrack = document.createElement('div');
|
nicholas@2224
|
2425 this.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2224
|
2426
|
nicholas@2224
|
2427 this.scrubberHead = document.createElement('div');
|
nicholas@2224
|
2428 this.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2224
|
2429 this.scrubberTrack.appendChild(this.scrubberHead);
|
nicholas@2224
|
2430 this.object.appendChild(this.scrubberTrack);
|
nicholas@2224
|
2431
|
nicholas@2224
|
2432 this.timePerPixel = 0;
|
nicholas@2224
|
2433 this.maxTime = 0;
|
nicholas@2224
|
2434
|
nicholas@2224
|
2435 this.playbackObject;
|
nicholas@2224
|
2436
|
nicholas@2224
|
2437 this.setTimePerPixel = function(audioObject) {
|
nicholas@2224
|
2438 //maxTime must be in seconds
|
nicholas@2224
|
2439 this.playbackObject = audioObject;
|
nicholas@2224
|
2440 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2224
|
2441 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2224
|
2442 this.timePerPixel = this.maxTime/490;
|
nicholas@2224
|
2443 if (this.maxTime < 60) {
|
nicholas@2224
|
2444 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2445 } else {
|
nicholas@2224
|
2446 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2447 }
|
nicholas@2224
|
2448 };
|
nicholas@2224
|
2449
|
nicholas@2224
|
2450 this.update = function() {
|
nicholas@2224
|
2451 // Update the playhead position, startPlay must be called
|
nicholas@2224
|
2452 if (this.timePerPixel > 0) {
|
nicholas@2224
|
2453 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2224
|
2454 if (time > 0 && time < this.maxTime) {
|
nicholas@2224
|
2455 var width = 490;
|
nicholas@2224
|
2456 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@2224
|
2457 this.scrubberHead.style.left = pix+'px';
|
nicholas@2224
|
2458 if (this.maxTime > 60.0) {
|
nicholas@2224
|
2459 var secs = time%60;
|
nicholas@2224
|
2460 var mins = Math.floor((time-secs)/60);
|
nicholas@2224
|
2461 secs = secs.toString();
|
nicholas@2224
|
2462 secs = secs.substr(0,2);
|
nicholas@2224
|
2463 mins = mins.toString();
|
nicholas@2224
|
2464 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@2224
|
2465 } else {
|
nicholas@2224
|
2466 time = time.toString();
|
nicholas@2224
|
2467 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@2224
|
2468 }
|
nicholas@2224
|
2469 } else {
|
nicholas@2224
|
2470 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2471 if (this.maxTime < 60) {
|
nicholas@2224
|
2472 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2473 } else {
|
nicholas@2224
|
2474 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2475 }
|
nicholas@2224
|
2476 }
|
nicholas@2224
|
2477 }
|
nicholas@2224
|
2478 };
|
nicholas@2224
|
2479
|
nicholas@2224
|
2480 this.interval = undefined;
|
nicholas@2224
|
2481
|
nicholas@2224
|
2482 this.start = function() {
|
nicholas@2224
|
2483 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@2224
|
2484 if (this.maxTime < 60) {
|
nicholas@2224
|
2485 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@2224
|
2486 } else {
|
nicholas@2224
|
2487 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@2224
|
2488 }
|
nicholas@2224
|
2489 }
|
nicholas@2224
|
2490 };
|
nicholas@2224
|
2491 this.stop = function() {
|
nicholas@2224
|
2492 clearInterval(this.interval);
|
nicholas@2224
|
2493 this.interval = undefined;
|
nicholas@2224
|
2494 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2495 if (this.maxTime < 60) {
|
nicholas@2224
|
2496 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2497 } else {
|
nicholas@2224
|
2498 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2499 }
|
nicholas@2224
|
2500 };
|
nicholas@2224
|
2501 };
|
nicholas@2224
|
2502
|
nicholas@2224
|
2503 this.volume = new function()
|
nicholas@2224
|
2504 {
|
nicholas@2224
|
2505 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
2506 // Includes trackers on page-by-page data
|
nicholas@2224
|
2507 // Volume does NOT reset to 0dB on each page load
|
nicholas@2224
|
2508 this.valueLin = 1.0;
|
nicholas@2224
|
2509 this.valueDB = 0.0;
|
nicholas@2224
|
2510 this.object = document.createElement('div');
|
nicholas@2224
|
2511 this.object.id = 'master-volume-holder';
|
nicholas@2224
|
2512 this.slider = document.createElement('input');
|
nicholas@2224
|
2513 this.slider.id = 'master-volume-control';
|
nicholas@2224
|
2514 this.slider.type = 'range';
|
nicholas@2224
|
2515 this.valueText = document.createElement('span');
|
nicholas@2224
|
2516 this.valueText.id = 'master-volume-feedback';
|
nicholas@2224
|
2517 this.valueText.textContent = '0dB';
|
nicholas@2224
|
2518
|
nicholas@2224
|
2519 this.slider.min = -60;
|
nicholas@2224
|
2520 this.slider.max = 12;
|
nicholas@2224
|
2521 this.slider.value = 0;
|
nicholas@2224
|
2522 this.slider.step = 1;
|
nicholas@2224
|
2523 this.slider.onmousemove = function(event)
|
nicholas@2224
|
2524 {
|
nicholas@2224
|
2525 interfaceContext.volume.valueDB = event.currentTarget.value;
|
nicholas@2224
|
2526 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
nicholas@2224
|
2527 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
nicholas@2224
|
2528 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
nicholas@2224
|
2529 }
|
nicholas@2224
|
2530 this.slider.onmouseup = function(event)
|
nicholas@2224
|
2531 {
|
nicholas@2224
|
2532 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2224
|
2533 if (storePoint.length == 0)
|
nicholas@2224
|
2534 {
|
nicholas@2224
|
2535 storePoint = storage.document.createElement('metricresult');
|
nicholas@2224
|
2536 storePoint.setAttribute('name','volumeTracker');
|
nicholas@2224
|
2537 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2224
|
2538 }
|
nicholas@2224
|
2539 else {
|
nicholas@2224
|
2540 storePoint = storePoint[0];
|
nicholas@2224
|
2541 }
|
nicholas@2224
|
2542 var node = storage.document.createElement('movement');
|
nicholas@2224
|
2543 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
2544 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
nicholas@2224
|
2545 node.setAttribute('format','dBFS');
|
nicholas@2224
|
2546 storePoint.appendChild(node);
|
nicholas@2224
|
2547 }
|
nicholas@2224
|
2548
|
nicholas@2224
|
2549 var title = document.createElement('div');
|
nicholas@2224
|
2550 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
2551 title.style.fontSize = '0.75em';
|
nicholas@2224
|
2552 title.style.width = "100%";
|
nicholas@2224
|
2553 title.align = 'center';
|
nicholas@2224
|
2554 this.object.appendChild(title);
|
nicholas@2224
|
2555
|
nicholas@2224
|
2556 this.object.appendChild(this.slider);
|
nicholas@2224
|
2557 this.object.appendChild(this.valueText);
|
nicholas@2224
|
2558 }
|
nicholas@2224
|
2559
|
nicholas@2224
|
2560 this.calibrationModuleObject = null;
|
nicholas@2224
|
2561 this.calibrationModule = function() {
|
nicholas@2224
|
2562 // This creates an on-page calibration module
|
nicholas@2224
|
2563 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
2564 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
2565 // The calibration is a fixed state module
|
nicholas@2224
|
2566 this.calibrationNodes = [];
|
nicholas@2224
|
2567 this.holder = null;
|
nicholas@2224
|
2568 this.build = function(inject) {
|
nicholas@2224
|
2569 var f0 = 62.5;
|
nicholas@2224
|
2570 this.holder = document.createElement("div");
|
nicholas@2224
|
2571 this.holder.className = "calibration-holder";
|
nicholas@2224
|
2572 this.calibrationNodes = [];
|
nicholas@2224
|
2573 while(f0 < 20000) {
|
nicholas@2224
|
2574 var obj = {
|
nicholas@2224
|
2575 root: document.createElement("div"),
|
nicholas@2224
|
2576 input: document.createElement("input"),
|
nicholas@2224
|
2577 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
2578 gain: audioContext.createGain(),
|
nicholas@2224
|
2579 f: f0,
|
nicholas@2224
|
2580 parent: this,
|
nicholas@2224
|
2581 handleEvent: function(event) {
|
nicholas@2224
|
2582 switch(event.type) {
|
nicholas@2224
|
2583 case "mouseenter":
|
nicholas@2224
|
2584 this.oscillator.start(0);
|
nicholas@2224
|
2585 break;
|
nicholas@2224
|
2586 case "mouseleave":
|
nicholas@2224
|
2587 this.oscillator.stop(0);
|
nicholas@2224
|
2588 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
2589 this.oscillator.connect(this.gain);
|
nicholas@2224
|
2590 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
2591 break;
|
nicholas@2224
|
2592 case "mousemove":
|
nicholas@2224
|
2593 var value = Math.pow(10,this.input.value/20);
|
nicholas@2224
|
2594 if (this.f == 1000) {
|
nicholas@2224
|
2595 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
2596 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
2597 } else {
|
nicholas@2224
|
2598 this.gain.gain.value = value
|
nicholas@2224
|
2599 }
|
nicholas@2224
|
2600 break;
|
nicholas@2224
|
2601 }
|
nicholas@2224
|
2602 },
|
nicholas@2224
|
2603 disconnect: function() {
|
nicholas@2224
|
2604 this.gain.disconnect();
|
nicholas@2224
|
2605 }
|
nicholas@2224
|
2606 }
|
nicholas@2224
|
2607 obj.root.className = "calibration-slider";
|
nicholas@2224
|
2608 obj.root.appendChild(obj.input);
|
nicholas@2224
|
2609 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
2610 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
2611 obj.gain.gain.value = Math.random()*2;
|
nicholas@2224
|
2612 obj.input.value = obj.gain.gain.value;
|
nicholas@2224
|
2613 obj.input.setAttribute('orient','vertical');
|
nicholas@2224
|
2614 obj.input.type = "range";
|
nicholas@2224
|
2615 obj.input.min = -6;
|
nicholas@2224
|
2616 obj.input.max = 6;
|
nicholas@2224
|
2617 obj.input.step = 0.25;
|
nicholas@2224
|
2618 if (f0 != 1000) {
|
nicholas@2224
|
2619 obj.input.value = (Math.random()*12)-6;
|
nicholas@2224
|
2620 } else {
|
nicholas@2224
|
2621 obj.input.value = 0;
|
nicholas@2224
|
2622 obj.root.style.backgroundColor="rgb(255,125,125)";
|
nicholas@2224
|
2623 }
|
nicholas@2224
|
2624 obj.input.addEventListener("mousemove",obj);
|
nicholas@2224
|
2625 obj.input.addEventListener("mouseenter",obj);
|
nicholas@2224
|
2626 obj.input.addEventListener("mouseleave",obj);
|
nicholas@2224
|
2627 obj.gain.gain.value = Math.pow(10,obj.input.value/20);
|
nicholas@2224
|
2628 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
2629 this.calibrationNodes.push(obj);
|
nicholas@2224
|
2630 this.holder.appendChild(obj.root);
|
nicholas@2224
|
2631 f0 *= 2;
|
nicholas@2224
|
2632 }
|
nicholas@2224
|
2633 inject.appendChild(this.holder);
|
nicholas@2224
|
2634 }
|
nicholas@2224
|
2635 this.collect = function() {
|
nicholas@2224
|
2636 for (var obj of this.calibrationNodes) {
|
nicholas@2224
|
2637 var node = storage.document.createElement("calibrationresult");
|
nicholas@2224
|
2638 node.setAttribute("frequency",obj.f);
|
nicholas@2224
|
2639 node.setAttribute("range-min",obj.input.min);
|
nicholas@2224
|
2640 node.setAttribute("range-max",obj.input.max);
|
nicholas@2224
|
2641 node.setAttribute("gain-lin",obj.gain.gain.value);
|
nicholas@2224
|
2642 this.storeDOM.appendChild(node);
|
nicholas@2224
|
2643 }
|
nicholas@2224
|
2644 }
|
nicholas@2224
|
2645 }
|
nicholas@2224
|
2646
|
nicholas@2224
|
2647
|
nicholas@2224
|
2648 // Global Checkers
|
nicholas@2224
|
2649 // These functions will help enforce the checkers
|
nicholas@2224
|
2650 this.checkHiddenAnchor = function()
|
nicholas@2224
|
2651 {
|
nicholas@2224
|
2652 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2653 {
|
nicholas@2224
|
2654 if (ao.specification.type == "anchor")
|
nicholas@2224
|
2655 {
|
nicholas@2224
|
2656 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2657 // Anchor is not set below
|
nicholas@2224
|
2658 console.log('Anchor node not below marker value');
|
nicholas@2224
|
2659 alert('Please keep listening');
|
nicholas@2224
|
2660 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2224
|
2661 return false;
|
nicholas@2224
|
2662 }
|
nicholas@2224
|
2663 }
|
nicholas@2224
|
2664 }
|
nicholas@2224
|
2665 return true;
|
nicholas@2224
|
2666 };
|
nicholas@2224
|
2667
|
nicholas@2224
|
2668 this.checkHiddenReference = function()
|
nicholas@2224
|
2669 {
|
nicholas@2224
|
2670 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2671 {
|
nicholas@2224
|
2672 if (ao.specification.type == "reference")
|
nicholas@2224
|
2673 {
|
nicholas@2224
|
2674 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2675 // Anchor is not set below
|
nicholas@2224
|
2676 console.log('Reference node not above marker value');
|
nicholas@2224
|
2677 this.storeErrorNode('Reference node not above marker value');
|
nicholas@2224
|
2678 alert('Please keep listening');
|
nicholas@2224
|
2679 return false;
|
nicholas@2224
|
2680 }
|
nicholas@2224
|
2681 }
|
nicholas@2224
|
2682 }
|
nicholas@2224
|
2683 return true;
|
nicholas@2224
|
2684 };
|
nicholas@2224
|
2685
|
nicholas@2224
|
2686 this.checkFragmentsFullyPlayed = function ()
|
nicholas@2224
|
2687 {
|
nicholas@2224
|
2688 // Checks the entire file has been played back
|
nicholas@2224
|
2689 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2224
|
2690 if (audioEngineContext.loopPlayback)
|
nicholas@2224
|
2691 {
|
nicholas@2224
|
2692 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2224
|
2693 return true;
|
nicholas@2224
|
2694 }
|
nicholas@2224
|
2695 var check_pass = true;
|
nicholas@2224
|
2696 var error_obj = [];
|
nicholas@2224
|
2697 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
nicholas@2224
|
2698 {
|
nicholas@2224
|
2699 var object = audioEngineContext.audioObjects[i];
|
nicholas@2224
|
2700 var time = object.buffer.buffer.duration;
|
nicholas@2224
|
2701 var metric = object.metric;
|
nicholas@2224
|
2702 var passed = false;
|
nicholas@2224
|
2703 for (var j=0; j<metric.listenTracker.length; j++)
|
nicholas@2224
|
2704 {
|
nicholas@2224
|
2705 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
nicholas@2224
|
2706 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2224
|
2707 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2224
|
2708 var delta = stop_time - start_time;
|
nicholas@2224
|
2709 if (delta >= time)
|
nicholas@2224
|
2710 {
|
nicholas@2224
|
2711 passed = true;
|
nicholas@2224
|
2712 break;
|
nicholas@2224
|
2713 }
|
nicholas@2224
|
2714 }
|
nicholas@2224
|
2715 if (passed == false)
|
nicholas@2224
|
2716 {
|
nicholas@2224
|
2717 check_pass = false;
|
b@2258
|
2718 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2719 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2720 }
|
nicholas@2224
|
2721 }
|
nicholas@2224
|
2722 if (check_pass == false)
|
nicholas@2224
|
2723 {
|
nicholas@2224
|
2724 var str_start = "You have not completely listened to fragments ";
|
nicholas@2224
|
2725 for (var i=0; i<error_obj.length; i++)
|
nicholas@2224
|
2726 {
|
nicholas@2224
|
2727 str_start += error_obj[i];
|
nicholas@2224
|
2728 if (i != error_obj.length-1)
|
nicholas@2224
|
2729 {
|
nicholas@2224
|
2730 str_start += ', ';
|
nicholas@2224
|
2731 }
|
nicholas@2224
|
2732 }
|
nicholas@2224
|
2733 str_start += ". Please keep listening";
|
nicholas@2224
|
2734 console.log("[ALERT]: "+str_start);
|
nicholas@2224
|
2735 this.storeErrorNode("[ALERT]: "+str_start);
|
nicholas@2224
|
2736 alert(str_start);
|
nicholas@2224
|
2737 }
|
nicholas@2224
|
2738 };
|
nicholas@2224
|
2739 this.checkAllMoved = function()
|
nicholas@2224
|
2740 {
|
nicholas@2224
|
2741 var str = "You have not moved ";
|
nicholas@2224
|
2742 var failed = [];
|
nicholas@2224
|
2743 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2744 {
|
nicholas@2224
|
2745 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@2224
|
2746 {
|
b@2258
|
2747 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2748 }
|
nicholas@2224
|
2749 }
|
nicholas@2224
|
2750 if (failed.length == 0)
|
nicholas@2224
|
2751 {
|
nicholas@2224
|
2752 return true;
|
nicholas@2224
|
2753 } else if (failed.length == 1)
|
nicholas@2224
|
2754 {
|
nicholas@2224
|
2755 str += 'track '+failed[0];
|
nicholas@2224
|
2756 } else {
|
nicholas@2224
|
2757 str += 'tracks ';
|
nicholas@2224
|
2758 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2759 {
|
nicholas@2224
|
2760 str += failed[i]+', ';
|
nicholas@2224
|
2761 }
|
nicholas@2224
|
2762 str += 'and '+failed[i];
|
nicholas@2224
|
2763 }
|
nicholas@2224
|
2764 str +='.';
|
nicholas@2224
|
2765 alert(str);
|
nicholas@2224
|
2766 console.log(str);
|
nicholas@2224
|
2767 this.storeErrorNode(str);
|
nicholas@2224
|
2768 return false;
|
nicholas@2224
|
2769 };
|
nicholas@2224
|
2770 this.checkAllPlayed = function()
|
nicholas@2224
|
2771 {
|
nicholas@2224
|
2772 var str = "You have not played ";
|
nicholas@2224
|
2773 var failed = [];
|
nicholas@2224
|
2774 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2775 {
|
nicholas@2224
|
2776 if(ao.metric.wasListenedTo == false)
|
nicholas@2224
|
2777 {
|
b@2258
|
2778 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2779 }
|
nicholas@2224
|
2780 }
|
nicholas@2224
|
2781 if (failed.length == 0)
|
nicholas@2224
|
2782 {
|
nicholas@2224
|
2783 return true;
|
nicholas@2224
|
2784 } else if (failed.length == 1)
|
nicholas@2224
|
2785 {
|
nicholas@2224
|
2786 str += 'track '+failed[0];
|
nicholas@2224
|
2787 } else {
|
nicholas@2224
|
2788 str += 'tracks ';
|
nicholas@2224
|
2789 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2790 {
|
nicholas@2224
|
2791 str += failed[i]+', ';
|
nicholas@2224
|
2792 }
|
nicholas@2224
|
2793 str += 'and '+failed[i];
|
nicholas@2224
|
2794 }
|
nicholas@2224
|
2795 str +='.';
|
nicholas@2224
|
2796 alert(str);
|
nicholas@2224
|
2797 console.log(str);
|
nicholas@2224
|
2798 this.storeErrorNode(str);
|
nicholas@2224
|
2799 return false;
|
nicholas@2224
|
2800 };
|
nicholas@2310
|
2801 this.checkScaleRange = function(min, max) {
|
nicholas@2310
|
2802 var page = testState.getCurrentTestPage();
|
nicholas@2310
|
2803 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2310
|
2804 var state = true;
|
nicholas@2310
|
2805 var str = "Please keep listening. ";
|
nicholas@2310
|
2806 var minRanking = Infinity;
|
nicholas@2310
|
2807 var maxRanking = -Infinity;
|
nicholas@2310
|
2808 for (var ao of audioObjects) {
|
nicholas@2310
|
2809 var rank = ao.interfaceDOM.getValue();
|
nicholas@2310
|
2810 if (rank < minRanking) {minRanking = rank;}
|
nicholas@2310
|
2811 if (rank > maxRanking) {maxRanking = rank;}
|
nicholas@2310
|
2812 }
|
nicholas@2310
|
2813 if (minRanking*100 > min) {
|
nicholas@2310
|
2814 str += "At least one fragment must be below the "+min+" mark.";
|
nicholas@2310
|
2815 state = false;
|
nicholas@2310
|
2816 }
|
nicholas@2310
|
2817 if (maxRanking*100 < max) {
|
nicholas@2310
|
2818 str += "At least one fragment must be above the "+max+" mark."
|
nicholas@2310
|
2819 state = false;
|
nicholas@2310
|
2820 }
|
nicholas@2310
|
2821 if (!state) {
|
nicholas@2310
|
2822 console.log(str);
|
nicholas@2310
|
2823 this.storeErrorNode(str);
|
nicholas@2310
|
2824 alert(str);
|
nicholas@2310
|
2825 }
|
nicholas@2310
|
2826 return state;
|
nicholas@2310
|
2827 }
|
nicholas@2224
|
2828
|
nicholas@2224
|
2829 this.storeErrorNode = function(errorMessage)
|
nicholas@2224
|
2830 {
|
nicholas@2224
|
2831 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
2832 var node = storage.document.createElement('error');
|
nicholas@2224
|
2833 node.setAttribute('time',time);
|
nicholas@2224
|
2834 node.textContent = errorMessage;
|
nicholas@2224
|
2835 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
2836 };
|
nicholas@2224
|
2837 }
|
nicholas@2224
|
2838
|
nicholas@2224
|
2839 function Storage()
|
nicholas@2224
|
2840 {
|
nicholas@2224
|
2841 // Holds results in XML format until ready for collection
|
nicholas@2224
|
2842 this.globalPreTest = null;
|
nicholas@2224
|
2843 this.globalPostTest = null;
|
nicholas@2224
|
2844 this.testPages = [];
|
nicholas@2224
|
2845 this.document = null;
|
nicholas@2224
|
2846 this.root = null;
|
nicholas@2224
|
2847 this.state = 0;
|
nicholas@2224
|
2848
|
nicholas@2224
|
2849 this.initialise = function(existingStore)
|
nicholas@2224
|
2850 {
|
nicholas@2224
|
2851 if (existingStore == undefined) {
|
nicholas@2224
|
2852 // We need to get the sessionKey
|
nicholas@2224
|
2853 this.SessionKey.generateKey();
|
nicholas@2224
|
2854 this.document = document.implementation.createDocument(null,"waetresult");
|
nicholas@2224
|
2855 this.root = this.document.childNodes[0];
|
nicholas@2224
|
2856 var projectDocument = specification.projectXML;
|
nicholas@2224
|
2857 projectDocument.setAttribute('file-name',url);
|
nicholas@2224
|
2858 projectDocument.setAttribute('url',qualifyURL(url));
|
nicholas@2224
|
2859 this.root.appendChild(projectDocument);
|
nicholas@2224
|
2860 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
2861 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
2862 } else {
|
nicholas@2224
|
2863 this.document = existingStore;
|
nicholas@2294
|
2864 this.root = existingStore.firstChild;
|
nicholas@2224
|
2865 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
2866 }
|
nicholas@2224
|
2867 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
nicholas@2224
|
2868 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
nicholas@2224
|
2869 };
|
nicholas@2224
|
2870
|
nicholas@2224
|
2871 this.SessionKey = {
|
nicholas@2224
|
2872 key: null,
|
nicholas@2224
|
2873 request: new XMLHttpRequest(),
|
nicholas@2224
|
2874 parent: this,
|
nicholas@2224
|
2875 handleEvent: function() {
|
nicholas@2224
|
2876 var parse = new DOMParser();
|
nicholas@2224
|
2877 var xml = parse.parseFromString(this.request.response,"text/xml");
|
giuliomoro@2301
|
2878 var shouldGenerateKey = true;
|
giuliomoro@2301
|
2879 if(xml.getAllElementsByTagName("state").length > 0){
|
giuliomoro@2301
|
2880 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
|
giuliomoro@2301
|
2881 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
giuliomoro@2301
|
2882 this.parent.root.setAttribute("key",this.key);
|
giuliomoro@2301
|
2883 this.parent.root.setAttribute("state","empty");
|
giuliomoro@2301
|
2884 shouldGenerateKey = false;
|
giuliomoro@2301
|
2885 }
|
giuliomoro@2301
|
2886 }
|
giuliomoro@2301
|
2887 if(shouldGenerateKey === true){
|
giuliomoro@2301
|
2888 this.generateKey();
|
giuliomoro@2301
|
2889 }
|
nicholas@2224
|
2890 },
|
nicholas@2224
|
2891 generateKey: function() {
|
nicholas@2224
|
2892 var temp_key = randomString(32);
|
nicholas@2302
|
2893 var returnURL = "";
|
nicholas@2302
|
2894 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
2895 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
2896 returnURL = specification.projectReturn;
|
nicholas@2302
|
2897 }
|
nicholas@2302
|
2898 }
|
nicholas@2302
|
2899 this.request.open("GET",returnURL+"php/keygen.php?key="+temp_key,true);
|
nicholas@2224
|
2900 this.request.addEventListener("load",this);
|
nicholas@2224
|
2901 this.request.send();
|
nicholas@2224
|
2902 },
|
nicholas@2224
|
2903 update: function() {
|
nicholas@2224
|
2904 this.parent.root.setAttribute("state","update");
|
nicholas@2224
|
2905 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
2906 var returnURL = "";
|
nicholas@2302
|
2907 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
2908 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
2909 returnURL = specification.projectReturn;
|
nicholas@2302
|
2910 }
|
nicholas@2302
|
2911 }
|
nicholas@2302
|
2912 xmlhttp.open("POST",returnURL+"php/save.php?key="+this.key);
|
nicholas@2224
|
2913 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2224
|
2914 xmlhttp.onerror = function(){
|
nicholas@2224
|
2915 console.log('Error updating file to server!');
|
nicholas@2224
|
2916 };
|
nicholas@2224
|
2917 var hold = document.createElement("div");
|
nicholas@2224
|
2918 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
2919 hold.appendChild(clone);
|
nicholas@2224
|
2920 xmlhttp.onload = function() {
|
nicholas@2224
|
2921 if (this.status >= 300) {
|
nicholas@2224
|
2922 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
2923 } else {
|
nicholas@2224
|
2924 var parser = new DOMParser();
|
nicholas@2224
|
2925 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
2926 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
2927 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
2928 var file = response.getElementsByTagName("file")[0];
|
nicholas@2224
|
2929 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
|
nicholas@2224
|
2930 } else {
|
nicholas@2224
|
2931 var message = response.getElementsByTagName("message");
|
nicholas@2224
|
2932 console.log("Intermediate save: Error! "+message.textContent);
|
nicholas@2224
|
2933 }
|
nicholas@2224
|
2934 }
|
nicholas@2224
|
2935 }
|
nicholas@2224
|
2936 xmlhttp.send([hold.innerHTML]);
|
nicholas@2224
|
2937 }
|
nicholas@2224
|
2938 }
|
nicholas@2224
|
2939
|
nicholas@2224
|
2940 this.createTestPageStore = function(specification)
|
nicholas@2224
|
2941 {
|
nicholas@2224
|
2942 var store = new this.pageNode(this,specification);
|
nicholas@2224
|
2943 this.testPages.push(store);
|
nicholas@2224
|
2944 return this.testPages[this.testPages.length-1];
|
nicholas@2224
|
2945 };
|
nicholas@2224
|
2946
|
nicholas@2224
|
2947 this.surveyNode = function(parent,root,specification)
|
nicholas@2224
|
2948 {
|
nicholas@2224
|
2949 this.specification = specification;
|
nicholas@2224
|
2950 this.parent = parent;
|
nicholas@2224
|
2951 this.state = "empty";
|
nicholas@2224
|
2952 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2224
|
2953 this.XMLDOM.setAttribute('location',this.specification.location);
|
nicholas@2224
|
2954 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
2955 for (var optNode of this.specification.options)
|
nicholas@2224
|
2956 {
|
nicholas@2224
|
2957 if (optNode.type != 'statement')
|
nicholas@2224
|
2958 {
|
nicholas@2224
|
2959 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2224
|
2960 node.setAttribute("ref",optNode.id);
|
nicholas@2224
|
2961 node.setAttribute('type',optNode.type);
|
nicholas@2224
|
2962 this.XMLDOM.appendChild(node);
|
nicholas@2224
|
2963 }
|
nicholas@2224
|
2964 }
|
nicholas@2224
|
2965 root.appendChild(this.XMLDOM);
|
nicholas@2224
|
2966
|
nicholas@2224
|
2967 this.postResult = function(node)
|
nicholas@2224
|
2968 {
|
nicholas@2224
|
2969 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2224
|
2970 // ID is the position
|
nicholas@2224
|
2971 if (node.specification.type == 'statement'){return;}
|
nicholas@2294
|
2972 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2224
|
2973 while(surveyresult != null) {
|
nicholas@2224
|
2974 if (surveyresult.getAttribute("ref") == node.specification.id)
|
nicholas@2224
|
2975 {
|
nicholas@2224
|
2976 break;
|
nicholas@2224
|
2977 }
|
nicholas@2224
|
2978 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
2979 }
|
nicholas@2224
|
2980 switch(node.specification.type)
|
nicholas@2224
|
2981 {
|
nicholas@2224
|
2982 case "number":
|
nicholas@2224
|
2983 case "question":
|
nicholas@2224
|
2984 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
2985 child.textContent = node.response;
|
nicholas@2224
|
2986 surveyresult.appendChild(child);
|
nicholas@2224
|
2987 break;
|
nicholas@2224
|
2988 case "radio":
|
nicholas@2224
|
2989 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
2990 child.setAttribute('name',node.response.name);
|
nicholas@2224
|
2991 child.textContent = node.response.text;
|
nicholas@2224
|
2992 surveyresult.appendChild(child);
|
nicholas@2224
|
2993 break;
|
nicholas@2224
|
2994 case "checkbox":
|
nicholas@2224
|
2995 for (var i=0; i<node.response.length; i++)
|
nicholas@2224
|
2996 {
|
nicholas@2224
|
2997 var checkNode = this.parent.document.createElement('response');
|
nicholas@2224
|
2998 checkNode.setAttribute('name',node.response[i].name);
|
nicholas@2224
|
2999 checkNode.setAttribute('checked',node.response[i].checked);
|
nicholas@2224
|
3000 surveyresult.appendChild(checkNode);
|
nicholas@2224
|
3001 }
|
nicholas@2224
|
3002 break;
|
nicholas@2224
|
3003 }
|
nicholas@2224
|
3004 };
|
nicholas@2224
|
3005 this.complete = function() {
|
nicholas@2224
|
3006 this.state = "complete";
|
nicholas@2224
|
3007 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3008 }
|
nicholas@2224
|
3009 };
|
nicholas@2224
|
3010
|
nicholas@2224
|
3011 this.pageNode = function(parent,specification)
|
nicholas@2224
|
3012 {
|
nicholas@2224
|
3013 // Create one store per test page
|
nicholas@2224
|
3014 this.specification = specification;
|
nicholas@2224
|
3015 this.parent = parent;
|
nicholas@2224
|
3016 this.state = "empty";
|
nicholas@2224
|
3017 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2224
|
3018 this.XMLDOM.setAttribute('ref',specification.id);
|
nicholas@2224
|
3019 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
nicholas@2224
|
3020 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3021 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
nicholas@2224
|
3022 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
nicholas@2224
|
3023
|
nicholas@2224
|
3024 // Add any page metrics
|
nicholas@2224
|
3025 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3026 this.XMLDOM.appendChild(page_metric);
|
nicholas@2224
|
3027
|
nicholas@2224
|
3028 // Add the audioelement
|
nicholas@2224
|
3029 for (var element of this.specification.audioElements)
|
nicholas@2224
|
3030 {
|
nicholas@2224
|
3031 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2224
|
3032 aeNode.setAttribute('ref',element.id);
|
nicholas@2224
|
3033 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
|
nicholas@2224
|
3034 aeNode.setAttribute('type',element.type);
|
nicholas@2224
|
3035 aeNode.setAttribute('url', element.url);
|
nicholas@2224
|
3036 aeNode.setAttribute('fqurl',qualifyURL(element.url));
|
nicholas@2224
|
3037 aeNode.setAttribute('gain', element.gain);
|
nicholas@2224
|
3038 if (element.type == 'anchor' || element.type == 'reference')
|
nicholas@2224
|
3039 {
|
nicholas@2224
|
3040 if (element.marker > 0)
|
nicholas@2224
|
3041 {
|
nicholas@2224
|
3042 aeNode.setAttribute('marker',element.marker);
|
nicholas@2224
|
3043 }
|
nicholas@2224
|
3044 }
|
nicholas@2224
|
3045 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3046 aeNode.appendChild(ae_metric);
|
nicholas@2224
|
3047 this.XMLDOM.appendChild(aeNode);
|
nicholas@2224
|
3048 }
|
nicholas@2224
|
3049
|
nicholas@2224
|
3050 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2224
|
3051
|
nicholas@2224
|
3052 this.complete = function() {
|
nicholas@2224
|
3053 this.state = "complete";
|
nicholas@2224
|
3054 this.XMLDOM.setAttribute("state","complete");
|
nicholas@2224
|
3055 }
|
nicholas@2224
|
3056 };
|
nicholas@2224
|
3057 this.update = function() {
|
nicholas@2224
|
3058 this.SessionKey.update();
|
nicholas@2224
|
3059 }
|
nicholas@2224
|
3060 this.finish = function()
|
nicholas@2224
|
3061 {
|
nicholas@2224
|
3062 if (this.state == 0)
|
nicholas@2224
|
3063 {
|
nicholas@2224
|
3064 this.update();
|
nicholas@2224
|
3065 }
|
nicholas@2224
|
3066 this.state = 1;
|
nicholas@2224
|
3067 return this.root;
|
nicholas@2224
|
3068 };
|
nicholas@2224
|
3069 }
|