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