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