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