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