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