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