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