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