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@2224
|
1273 if (waveObj.open(bufferObj.xmlRequest.response) == 0)
|
nicholas@2224
|
1274 {
|
nicholas@2224
|
1275 bufferObj.buffer = audioContext.createBuffer(waveObj.num_channels,waveObj.num_samples,waveObj.sample_rate);
|
nicholas@2224
|
1276 for (var c=0; c<waveObj.num_channels; c++)
|
nicholas@2224
|
1277 {
|
nicholas@2224
|
1278 var buffer_ptr = bufferObj.buffer.getChannelData(c);
|
nicholas@2224
|
1279 for (var n=0; n<waveObj.num_samples; n++)
|
nicholas@2224
|
1280 {
|
nicholas@2224
|
1281 buffer_ptr[n] = waveObj.decoded_data[c][n];
|
nicholas@2224
|
1282 }
|
nicholas@2224
|
1283 }
|
nicholas@2224
|
1284
|
nicholas@2224
|
1285 delete waveObj;
|
nicholas@2224
|
1286 } else {
|
nicholas@2224
|
1287 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
|
nicholas@2224
|
1288 bufferObj.buffer = decodedData;
|
nicholas@2244
|
1289 bufferObj.status = 2;
|
nicholas@2244
|
1290 calculateLoudness(bufferObj,"I");
|
nicholas@2224
|
1291 }, function(e){
|
nicholas@2224
|
1292 // Should only be called if there was an error, but sometimes gets called continuously
|
nicholas@2224
|
1293 // Check here if the error is genuine
|
nicholas@2224
|
1294 if (bufferObj.xmlRequest.response == undefined) {
|
nicholas@2224
|
1295 // Genuine error
|
nicholas@2224
|
1296 console.log('FATAL - Error loading buffer on '+audioObj.id);
|
nicholas@2224
|
1297 if (request.status == 404)
|
nicholas@2224
|
1298 {
|
nicholas@2224
|
1299 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
|
nicholas@2224
|
1300 console.log('URL: '+audioObj.url);
|
nicholas@2224
|
1301 errorSessionDump('Fragment '+audioObj.id+' 404 error');
|
nicholas@2224
|
1302 }
|
nicholas@2224
|
1303 this.parent.status = -1;
|
nicholas@2224
|
1304 }
|
nicholas@2224
|
1305 });
|
nicholas@2224
|
1306 }
|
nicholas@2224
|
1307 if (bufferObj.buffer != undefined)
|
nicholas@2224
|
1308 {
|
nicholas@2224
|
1309 bufferObj.status = 2;
|
nicholas@2224
|
1310 calculateLoudness(bufferObj,"I");
|
nicholas@2224
|
1311 }
|
nicholas@2224
|
1312 };
|
nicholas@2224
|
1313
|
nicholas@2224
|
1314 // Create callback for any error in loading
|
nicholas@2224
|
1315 this.xmlRequest.onerror = function() {
|
nicholas@2224
|
1316 this.parent.status = -1;
|
nicholas@2224
|
1317 for (var i=0; i<this.parent.users.length; i++)
|
nicholas@2224
|
1318 {
|
nicholas@2224
|
1319 this.parent.users[i].state = -1;
|
nicholas@2224
|
1320 if (this.parent.users[i].interfaceDOM != null)
|
nicholas@2224
|
1321 {
|
nicholas@2224
|
1322 this.parent.users[i].bufferLoaded(this);
|
nicholas@2224
|
1323 }
|
nicholas@2224
|
1324 }
|
nicholas@2224
|
1325 }
|
nicholas@2224
|
1326
|
nicholas@2224
|
1327 this.progress = 0;
|
nicholas@2224
|
1328 this.progressCallback = function(event){
|
nicholas@2224
|
1329 if (event.lengthComputable)
|
nicholas@2224
|
1330 {
|
nicholas@2224
|
1331 this.parent.progress = event.loaded / event.total;
|
nicholas@2224
|
1332 for (var i=0; i<this.parent.users.length; i++)
|
nicholas@2224
|
1333 {
|
nicholas@2224
|
1334 if(this.parent.users[i].interfaceDOM != null)
|
nicholas@2224
|
1335 {
|
nicholas@2224
|
1336 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
|
nicholas@2224
|
1337 {
|
nicholas@2224
|
1338 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
|
nicholas@2224
|
1339 }
|
nicholas@2224
|
1340 }
|
nicholas@2224
|
1341 }
|
nicholas@2224
|
1342 }
|
nicholas@2224
|
1343 };
|
nicholas@2224
|
1344 this.xmlRequest.addEventListener("progress", this.progressCallback);
|
nicholas@2224
|
1345 this.status = 1;
|
nicholas@2224
|
1346 this.xmlRequest.send();
|
nicholas@2224
|
1347 };
|
nicholas@2224
|
1348
|
nicholas@2224
|
1349 this.registerAudioObject = function(audioObject)
|
nicholas@2224
|
1350 {
|
nicholas@2224
|
1351 // Called by an audioObject to register to the buffer for use
|
nicholas@2224
|
1352 // First check if already in the register pool
|
nicholas@2224
|
1353 for (var objects of this.users)
|
nicholas@2224
|
1354 {
|
nicholas@2224
|
1355 if (audioObject.id == objects.id){return 0;}
|
nicholas@2224
|
1356 }
|
nicholas@2224
|
1357 this.users.push(audioObject);
|
nicholas@2224
|
1358 if (this.status == 3 || this.status == -1)
|
nicholas@2224
|
1359 {
|
nicholas@2224
|
1360 // The buffer is already ready, trigger bufferLoaded
|
nicholas@2224
|
1361 audioObject.bufferLoaded(this);
|
nicholas@2224
|
1362 }
|
nicholas@2224
|
1363 };
|
nicholas@2224
|
1364
|
nicholas@2224
|
1365 this.copyBuffer = function(preSilenceTime,postSilenceTime) {
|
nicholas@2224
|
1366 // Copies the entire bufferObj.
|
nicholas@2224
|
1367 if (preSilenceTime == undefined) {preSilenceTime = 0;}
|
nicholas@2224
|
1368 if (postSilenceTime == undefined) {postSilenceTime = 0;}
|
nicholas@2224
|
1369 var copy = new this.constructor();
|
nicholas@2224
|
1370 copy.url = this.url;
|
nicholas@2224
|
1371 var preSilenceSamples = secondsToSamples(preSilenceTime,this.buffer.sampleRate);
|
nicholas@2224
|
1372 var postSilenceSamples = secondsToSamples(postSilenceTime,this.buffer.sampleRate);
|
nicholas@2224
|
1373 var newLength = this.buffer.length+preSilenceSamples+postSilenceSamples;
|
nicholas@2224
|
1374 copy.buffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
|
nicholas@2224
|
1375 // Now we can use some efficient background copy schemes if we are just padding the end
|
nicholas@2224
|
1376 if (preSilenceSamples == 0 && typeof copy.buffer.copyToChannel == "function") {
|
nicholas@2224
|
1377 for (var c=0; c<this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1378 copy.buffer.copyToChannel(this.buffer.getChannelData(c),c);
|
nicholas@2224
|
1379 }
|
nicholas@2224
|
1380 } else {
|
nicholas@2224
|
1381 for (var c=0; c<this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1382 var src = this.buffer.getChannelData(c);
|
nicholas@2224
|
1383 var dst = copy.buffer.getChannelData(c);
|
nicholas@2224
|
1384 for (var n=0; n<src.length; n++)
|
nicholas@2224
|
1385 dst[n+preSilenceSamples] = src[n];
|
nicholas@2224
|
1386 }
|
nicholas@2224
|
1387 }
|
nicholas@2224
|
1388 // Copy in the rest of the buffer information
|
nicholas@2224
|
1389 copy.buffer.lufs = this.buffer.lufs;
|
nicholas@2224
|
1390 copy.buffer.playbackGain = this.buffer.playbackGain;
|
nicholas@2224
|
1391 return copy;
|
nicholas@2224
|
1392 }
|
nicholas@2224
|
1393 };
|
nicholas@2224
|
1394
|
nicholas@2224
|
1395 this.loadPageData = function(page) {
|
nicholas@2224
|
1396 // Load the URL from pages
|
nicholas@2224
|
1397 for (var element of page.audioElements) {
|
nicholas@2224
|
1398 var URL = page.hostURL + element.url;
|
nicholas@2224
|
1399 var buffer = null;
|
nicholas@2224
|
1400 for (var buffObj of this.buffers) {
|
nicholas@2224
|
1401 if (URL == buffObj.url) {
|
nicholas@2224
|
1402 buffer = buffObj;
|
nicholas@2224
|
1403 break;
|
nicholas@2224
|
1404 }
|
nicholas@2224
|
1405 }
|
nicholas@2224
|
1406 if (buffer == null) {
|
nicholas@2224
|
1407 buffer = new this.bufferObj();
|
nicholas@2224
|
1408 buffer.getMedia(URL);
|
nicholas@2224
|
1409 this.buffers.push(buffer);
|
nicholas@2224
|
1410 }
|
nicholas@2224
|
1411 }
|
nicholas@2224
|
1412 };
|
nicholas@2224
|
1413
|
nicholas@2224
|
1414 this.play = function(id) {
|
nicholas@2224
|
1415 // Start the timer and set the audioEngine state to playing (1)
|
nicholas@2224
|
1416 if (this.status == 0 && this.loopPlayback) {
|
nicholas@2224
|
1417 // Check if all audioObjects are ready
|
nicholas@2224
|
1418 if(this.checkAllReady())
|
nicholas@2224
|
1419 {
|
nicholas@2224
|
1420 this.status = 1;
|
nicholas@2224
|
1421 this.setSynchronousLoop();
|
nicholas@2224
|
1422 }
|
nicholas@2224
|
1423 }
|
nicholas@2224
|
1424 else
|
nicholas@2224
|
1425 {
|
nicholas@2224
|
1426 this.status = 1;
|
nicholas@2224
|
1427 }
|
nicholas@2224
|
1428 if (this.status== 1) {
|
nicholas@2224
|
1429 this.timer.startTest();
|
nicholas@2224
|
1430 if (id == undefined) {
|
nicholas@2224
|
1431 id = -1;
|
nicholas@2224
|
1432 console.error('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
|
nicholas@2224
|
1433 return;
|
nicholas@2224
|
1434 } else {
|
nicholas@2224
|
1435 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
|
nicholas@2224
|
1436 }
|
nicholas@2351
|
1437 if (this.synchPlayback && this.loopPlayback) {
|
nicholas@2351
|
1438 // Traditional looped playback
|
nicholas@2224
|
1439 var setTime = audioContext.currentTime+specification.crossFade;
|
nicholas@2224
|
1440 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1441 {
|
nicholas@2224
|
1442 this.audioObjects[i].play(audioContext.currentTime);
|
nicholas@2224
|
1443 if (id == i) {
|
nicholas@2224
|
1444 this.audioObjects[i].loopStart(setTime);
|
nicholas@2224
|
1445 } else {
|
nicholas@2224
|
1446 this.audioObjects[i].loopStop(setTime);
|
nicholas@2224
|
1447 }
|
nicholas@2224
|
1448 }
|
nicholas@2351
|
1449 } else {
|
nicholas@2224
|
1450 var setTime = audioContext.currentTime+specification.crossFade;
|
nicholas@2224
|
1451 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1452 {
|
nicholas@2224
|
1453 if (i != id) {
|
nicholas@2224
|
1454 this.audioObjects[i].stop(setTime);
|
nicholas@2224
|
1455 } else if (i == id) {
|
nicholas@2224
|
1456 this.audioObjects[id].play(setTime);
|
nicholas@2224
|
1457 }
|
nicholas@2224
|
1458 }
|
nicholas@2224
|
1459 }
|
nicholas@2224
|
1460 interfaceContext.playhead.start();
|
nicholas@2224
|
1461 }
|
nicholas@2224
|
1462 };
|
nicholas@2224
|
1463
|
nicholas@2224
|
1464 this.stop = function() {
|
nicholas@2224
|
1465 // Send stop and reset command to all playback buffers
|
nicholas@2224
|
1466 if (this.status == 1) {
|
nicholas@2224
|
1467 var setTime = audioContext.currentTime+0.1;
|
nicholas@2224
|
1468 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1469 {
|
nicholas@2224
|
1470 this.audioObjects[i].stop(setTime);
|
nicholas@2224
|
1471 }
|
nicholas@2224
|
1472 interfaceContext.playhead.stop();
|
nicholas@2224
|
1473 }
|
nicholas@2224
|
1474 };
|
nicholas@2224
|
1475
|
nicholas@2224
|
1476 this.newTrack = function(element) {
|
nicholas@2224
|
1477 // Pull data from given URL into new audio buffer
|
nicholas@2224
|
1478 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
|
nicholas@2224
|
1479
|
nicholas@2224
|
1480 // Create the audioObject with ID of the new track length;
|
nicholas@2224
|
1481 audioObjectId = this.audioObjects.length;
|
nicholas@2224
|
1482 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
|
nicholas@2224
|
1483
|
nicholas@2224
|
1484 // Check if audioObject buffer is currently stored by full URL
|
nicholas@2224
|
1485 var URL = testState.currentStateMap.hostURL + element.url;
|
nicholas@2224
|
1486 var buffer = null;
|
nicholas@2224
|
1487 for (var i=0; i<this.buffers.length; i++)
|
nicholas@2224
|
1488 {
|
nicholas@2224
|
1489 if (URL == this.buffers[i].url)
|
nicholas@2224
|
1490 {
|
nicholas@2224
|
1491 buffer = this.buffers[i];
|
nicholas@2224
|
1492 break;
|
nicholas@2224
|
1493 }
|
nicholas@2224
|
1494 }
|
nicholas@2224
|
1495 if (buffer == null)
|
nicholas@2224
|
1496 {
|
nicholas@2224
|
1497 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
|
nicholas@2224
|
1498 buffer = new this.bufferObj();
|
nicholas@2224
|
1499 this.buffers.push(buffer);
|
nicholas@2224
|
1500 buffer.getMedia(URL);
|
nicholas@2224
|
1501 }
|
nicholas@2224
|
1502 this.audioObjects[audioObjectId].specification = element;
|
nicholas@2224
|
1503 this.audioObjects[audioObjectId].url = URL;
|
nicholas@2224
|
1504 // Obtain store node
|
nicholas@2224
|
1505 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
|
nicholas@2224
|
1506 for (var i=0; i<aeNodes.length; i++)
|
nicholas@2224
|
1507 {
|
nicholas@2224
|
1508 if(aeNodes[i].getAttribute("ref") == element.id)
|
nicholas@2224
|
1509 {
|
nicholas@2224
|
1510 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
|
nicholas@2224
|
1511 break;
|
nicholas@2224
|
1512 }
|
nicholas@2224
|
1513 }
|
nicholas@2224
|
1514 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
|
nicholas@2224
|
1515 return this.audioObjects[audioObjectId];
|
nicholas@2224
|
1516 };
|
nicholas@2224
|
1517
|
nicholas@2224
|
1518 this.newTestPage = function(audioHolderObject,store) {
|
nicholas@2224
|
1519 this.pageStore = store;
|
nicholas@2351
|
1520 this.pageSpecification = audioHolderObject;
|
nicholas@2224
|
1521 this.status = 0;
|
nicholas@2224
|
1522 this.audioObjectsReady = false;
|
nicholas@2224
|
1523 this.metric.reset();
|
nicholas@2224
|
1524 for (var i=0; i < this.buffers.length; i++)
|
nicholas@2224
|
1525 {
|
nicholas@2224
|
1526 this.buffers[i].users = [];
|
nicholas@2224
|
1527 }
|
nicholas@2224
|
1528 this.audioObjects = [];
|
nicholas@2224
|
1529 this.timer = new timer();
|
nicholas@2224
|
1530 this.loopPlayback = audioHolderObject.loop;
|
nicholas@2351
|
1531 this.synchPlayback = audioHolderObject.synchronous;
|
nicholas@2224
|
1532 };
|
nicholas@2224
|
1533
|
nicholas@2224
|
1534 this.checkAllPlayed = function() {
|
nicholas@2224
|
1535 arr = [];
|
nicholas@2224
|
1536 for (var id=0; id<this.audioObjects.length; id++) {
|
nicholas@2224
|
1537 if (this.audioObjects[id].metric.wasListenedTo == false) {
|
nicholas@2224
|
1538 arr.push(this.audioObjects[id].id);
|
nicholas@2224
|
1539 }
|
nicholas@2224
|
1540 }
|
nicholas@2224
|
1541 return arr;
|
nicholas@2224
|
1542 };
|
nicholas@2224
|
1543
|
nicholas@2224
|
1544 this.checkAllReady = function() {
|
nicholas@2224
|
1545 var ready = true;
|
nicholas@2224
|
1546 for (var i=0; i<this.audioObjects.length; i++) {
|
nicholas@2224
|
1547 if (this.audioObjects[i].state == 0) {
|
nicholas@2224
|
1548 // Track not ready
|
nicholas@2224
|
1549 console.log('WAIT -- audioObject '+i+' not ready yet!');
|
nicholas@2224
|
1550 ready = false;
|
nicholas@2224
|
1551 };
|
nicholas@2224
|
1552 }
|
nicholas@2224
|
1553 return ready;
|
nicholas@2224
|
1554 };
|
nicholas@2224
|
1555
|
nicholas@2224
|
1556 this.setSynchronousLoop = function() {
|
nicholas@2224
|
1557 // Pads the signals so they are all exactly the same length
|
nicholas@2224
|
1558 // Get the length of the longest signal.
|
nicholas@2224
|
1559 var length = 0;
|
nicholas@2224
|
1560 var maxId;
|
nicholas@2224
|
1561 for (var i=0; i<this.audioObjects.length; i++)
|
nicholas@2224
|
1562 {
|
nicholas@2224
|
1563 if (length < this.audioObjects[i].buffer.buffer.length)
|
nicholas@2224
|
1564 {
|
nicholas@2224
|
1565 length = this.audioObjects[i].buffer.buffer.length;
|
nicholas@2224
|
1566 maxId = i;
|
nicholas@2224
|
1567 }
|
nicholas@2224
|
1568 }
|
nicholas@2224
|
1569 // Extract the audio and zero-pad
|
nicholas@2224
|
1570 for (var ao of this.audioObjects)
|
nicholas@2224
|
1571 {
|
nicholas@2224
|
1572 var lengthDiff = length - ao.buffer.buffer.length;
|
nicholas@2224
|
1573 ao.buffer = ao.buffer.copyBuffer(0,samplesToSeconds(lengthDiff,ao.buffer.buffer.sampleRate));
|
nicholas@2224
|
1574 }
|
nicholas@2224
|
1575 };
|
nicholas@2224
|
1576
|
nicholas@2224
|
1577 this.exportXML = function()
|
nicholas@2224
|
1578 {
|
nicholas@2224
|
1579
|
nicholas@2224
|
1580 };
|
nicholas@2224
|
1581
|
nicholas@2224
|
1582 }
|
nicholas@2224
|
1583
|
nicholas@2224
|
1584 function audioObject(id) {
|
nicholas@2224
|
1585 // The main buffer object with common control nodes to the AudioEngine
|
nicholas@2224
|
1586
|
nicholas@2224
|
1587 this.specification;
|
nicholas@2224
|
1588 this.id = id;
|
nicholas@2224
|
1589 this.state = 0; // 0 - no data, 1 - ready
|
nicholas@2224
|
1590 this.url = null; // Hold the URL given for the output back to the results.
|
nicholas@2224
|
1591 this.metric = new metricTracker(this);
|
nicholas@2224
|
1592 this.storeDOM = null;
|
nicholas@2224
|
1593
|
nicholas@2224
|
1594 // Bindings for GUI
|
nicholas@2224
|
1595 this.interfaceDOM = null;
|
nicholas@2224
|
1596 this.commentDOM = null;
|
nicholas@2224
|
1597
|
nicholas@2224
|
1598 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
|
nicholas@2224
|
1599 this.bufferNode = undefined;
|
nicholas@2224
|
1600 this.outputGain = audioContext.createGain();
|
nicholas@2224
|
1601
|
nicholas@2224
|
1602 this.onplayGain = 1.0;
|
nicholas@2224
|
1603
|
nicholas@2224
|
1604 // Connect buffer to the audio graph
|
nicholas@2224
|
1605 this.outputGain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
1606
|
nicholas@2224
|
1607 // the audiobuffer is not designed for multi-start playback
|
nicholas@2224
|
1608 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
|
nicholas@2224
|
1609 this.buffer;
|
nicholas@2224
|
1610
|
nicholas@2224
|
1611 this.bufferLoaded = function(callee)
|
nicholas@2224
|
1612 {
|
nicholas@2224
|
1613 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
|
nicholas@2224
|
1614 // audioObject and trigger the interfaceDOM.enable() function for user feedback
|
nicholas@2224
|
1615 if (callee.status == -1) {
|
nicholas@2224
|
1616 // ERROR
|
nicholas@2224
|
1617 this.state = -1;
|
nicholas@2224
|
1618 if (this.interfaceDOM != null) {this.interfaceDOM.error();}
|
nicholas@2224
|
1619 this.buffer = callee;
|
nicholas@2224
|
1620 return;
|
nicholas@2224
|
1621 }
|
nicholas@2224
|
1622 this.buffer = callee;
|
nicholas@2224
|
1623 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
|
nicholas@2224
|
1624 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
|
nicholas@2224
|
1625 if (preSilenceTime != 0 || postSilenceTime != 0) {
|
nicholas@2224
|
1626 this.buffer = callee.copyBuffer(preSilenceTime,postSilenceTime);
|
nicholas@2224
|
1627 }
|
nicholas@2224
|
1628 this.state = 1;
|
nicholas@2224
|
1629 var targetLUFS = this.specification.parent.loudness || specification.loudness;
|
nicholas@2224
|
1630 if (typeof targetLUFS === "number")
|
nicholas@2224
|
1631 {
|
nicholas@2224
|
1632 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
|
nicholas@2224
|
1633 } else {
|
nicholas@2224
|
1634 this.buffer.buffer.playbackGain = 1.0;
|
nicholas@2224
|
1635 }
|
nicholas@2224
|
1636 if (this.interfaceDOM != null) {
|
nicholas@2224
|
1637 this.interfaceDOM.enable();
|
nicholas@2224
|
1638 }
|
nicholas@2224
|
1639 this.onplayGain = decibelToLinear(this.specification.gain)*this.buffer.buffer.playbackGain;
|
nicholas@2224
|
1640 this.storeDOM.setAttribute('playGain',linearToDecibel(this.onplayGain));
|
nicholas@2224
|
1641 };
|
nicholas@2224
|
1642
|
nicholas@2224
|
1643 this.bindInterface = function(interfaceObject)
|
nicholas@2224
|
1644 {
|
nicholas@2224
|
1645 this.interfaceDOM = interfaceObject;
|
nicholas@2224
|
1646 this.metric.initialise(interfaceObject.getValue());
|
nicholas@2224
|
1647 if (this.state == 1)
|
nicholas@2224
|
1648 {
|
nicholas@2224
|
1649 this.interfaceDOM.enable();
|
nicholas@2224
|
1650 } else if (this.state == -1) {
|
nicholas@2224
|
1651 // ERROR
|
nicholas@2224
|
1652 this.interfaceDOM.error();
|
nicholas@2224
|
1653 return;
|
nicholas@2224
|
1654 }
|
nicholas@2224
|
1655 this.storeDOM.setAttribute('presentedId',interfaceObject.getPresentedId());
|
nicholas@2224
|
1656 };
|
nicholas@2224
|
1657
|
nicholas@2224
|
1658 this.loopStart = function(setTime) {
|
nicholas@2224
|
1659 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain,setTime);
|
nicholas@2224
|
1660 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1661 this.interfaceDOM.startPlayback();
|
nicholas@2224
|
1662 };
|
nicholas@2224
|
1663
|
nicholas@2224
|
1664 this.loopStop = function(setTime) {
|
nicholas@2224
|
1665 if (this.outputGain.gain.value != 0.0) {
|
nicholas@2224
|
1666 this.outputGain.gain.linearRampToValueAtTime(0.0,setTime);
|
nicholas@2224
|
1667 this.metric.stopListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1668 }
|
nicholas@2224
|
1669 this.interfaceDOM.stopPlayback();
|
nicholas@2224
|
1670 };
|
nicholas@2224
|
1671
|
nicholas@2224
|
1672 this.play = function(startTime) {
|
nicholas@2224
|
1673 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
|
nicholas@2224
|
1674 this.bufferNode = audioContext.createBufferSource();
|
nicholas@2224
|
1675 this.bufferNode.owner = this;
|
nicholas@2224
|
1676 this.bufferNode.connect(this.outputGain);
|
nicholas@2224
|
1677 this.bufferNode.buffer = this.buffer.buffer;
|
nicholas@2224
|
1678 this.bufferNode.loop = audioEngineContext.loopPlayback;
|
nicholas@2224
|
1679 this.bufferNode.onended = function(event) {
|
nicholas@2224
|
1680 // Safari does not like using 'this' to reference the calling object!
|
nicholas@2224
|
1681 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
|
nicholas@2224
|
1682 if (event.currentTarget != null) {
|
nicholas@2224
|
1683 event.currentTarget.owner.stop(audioContext.currentTime+1);
|
nicholas@2224
|
1684 }
|
nicholas@2224
|
1685 };
|
nicholas@2351
|
1686 if (!audioEngineContext.loopPlayback || !audioEngineContext.synchPlayback) {
|
nicholas@2224
|
1687 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
1688 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
|
nicholas@2224
|
1689 this.interfaceDOM.startPlayback();
|
nicholas@2224
|
1690 } else {
|
nicholas@2224
|
1691 this.outputGain.gain.setValueAtTime(0.0,startTime);
|
nicholas@2224
|
1692 }
|
nicholas@2224
|
1693 this.bufferNode.start(startTime);
|
nicholas@2224
|
1694 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
1695 }
|
nicholas@2224
|
1696 };
|
nicholas@2224
|
1697
|
nicholas@2224
|
1698 this.stop = function(stopTime) {
|
nicholas@2224
|
1699 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
nicholas@2224
|
1700 if (this.bufferNode != undefined)
|
nicholas@2224
|
1701 {
|
nicholas@2224
|
1702 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
|
nicholas@2224
|
1703 this.bufferNode.stop(stopTime);
|
nicholas@2224
|
1704 this.bufferNode = undefined;
|
nicholas@2224
|
1705 }
|
nicholas@2224
|
1706 this.outputGain.gain.value = 0.0;
|
nicholas@2224
|
1707 this.interfaceDOM.stopPlayback();
|
nicholas@2224
|
1708 };
|
nicholas@2224
|
1709
|
nicholas@2224
|
1710 this.getCurrentPosition = function() {
|
nicholas@2224
|
1711 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
1712 if (this.bufferNode != undefined) {
|
nicholas@2224
|
1713 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
|
nicholas@2224
|
1714 if (isNaN(position)){return 0;}
|
nicholas@2224
|
1715 return position;
|
nicholas@2224
|
1716 } else {
|
nicholas@2224
|
1717 return 0;
|
nicholas@2224
|
1718 }
|
nicholas@2224
|
1719 };
|
nicholas@2224
|
1720
|
nicholas@2224
|
1721 this.exportXMLDOM = function() {
|
nicholas@2224
|
1722 var file = storage.document.createElement('file');
|
nicholas@2224
|
1723 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
|
nicholas@2224
|
1724 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
|
nicholas@2224
|
1725 file.setAttribute('sampleCount',this.buffer.buffer.length);
|
nicholas@2224
|
1726 file.setAttribute('duration',this.buffer.buffer.duration);
|
nicholas@2224
|
1727 this.storeDOM.appendChild(file);
|
nicholas@2224
|
1728 if (this.specification.type != 'outside-reference') {
|
nicholas@2224
|
1729 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
nicholas@2224
|
1730 if (interfaceXML != null)
|
nicholas@2224
|
1731 {
|
nicholas@2224
|
1732 if (interfaceXML.length == undefined) {
|
nicholas@2224
|
1733 this.storeDOM.appendChild(interfaceXML);
|
nicholas@2224
|
1734 } else {
|
nicholas@2224
|
1735 for (var i=0; i<interfaceXML.length; i++)
|
nicholas@2224
|
1736 {
|
nicholas@2224
|
1737 this.storeDOM.appendChild(interfaceXML[i]);
|
nicholas@2224
|
1738 }
|
nicholas@2224
|
1739 }
|
nicholas@2224
|
1740 }
|
nicholas@2224
|
1741 if (this.commentDOM != null) {
|
nicholas@2224
|
1742 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
nicholas@2224
|
1743 }
|
nicholas@2224
|
1744 }
|
nicholas@2224
|
1745 var nodes = this.metric.exportXMLDOM();
|
nicholas@2224
|
1746 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
|
nicholas@2224
|
1747 for (var i=0; i<nodes.length; i++)
|
nicholas@2224
|
1748 {
|
nicholas@2224
|
1749 mroot.appendChild(nodes[i]);
|
nicholas@2224
|
1750 }
|
nicholas@2224
|
1751 };
|
nicholas@2224
|
1752 }
|
nicholas@2224
|
1753
|
nicholas@2224
|
1754 function timer()
|
nicholas@2224
|
1755 {
|
nicholas@2224
|
1756 /* Timer object used in audioEngine to keep track of session timings
|
nicholas@2224
|
1757 * Uses the timer of the web audio API, so sample resolution
|
nicholas@2224
|
1758 */
|
nicholas@2224
|
1759 this.testStarted = false;
|
nicholas@2224
|
1760 this.testStartTime = 0;
|
nicholas@2224
|
1761 this.testDuration = 0;
|
nicholas@2224
|
1762 this.minimumTestTime = 0; // No minimum test time
|
nicholas@2224
|
1763 this.startTest = function()
|
nicholas@2224
|
1764 {
|
nicholas@2224
|
1765 if (this.testStarted == false)
|
nicholas@2224
|
1766 {
|
nicholas@2224
|
1767 this.testStartTime = audioContext.currentTime;
|
nicholas@2224
|
1768 this.testStarted = true;
|
nicholas@2224
|
1769 this.updateTestTime();
|
nicholas@2224
|
1770 audioEngineContext.metric.initialiseTest();
|
nicholas@2224
|
1771 }
|
nicholas@2224
|
1772 };
|
nicholas@2224
|
1773 this.stopTest = function()
|
nicholas@2224
|
1774 {
|
nicholas@2224
|
1775 if (this.testStarted)
|
nicholas@2224
|
1776 {
|
nicholas@2224
|
1777 this.testDuration = this.getTestTime();
|
nicholas@2224
|
1778 this.testStarted = false;
|
nicholas@2224
|
1779 } else {
|
nicholas@2224
|
1780 console.log('ERR: Test tried to end before beginning');
|
nicholas@2224
|
1781 }
|
nicholas@2224
|
1782 };
|
nicholas@2224
|
1783 this.updateTestTime = function()
|
nicholas@2224
|
1784 {
|
nicholas@2224
|
1785 if (this.testStarted)
|
nicholas@2224
|
1786 {
|
nicholas@2224
|
1787 this.testDuration = audioContext.currentTime - this.testStartTime;
|
nicholas@2224
|
1788 }
|
nicholas@2224
|
1789 };
|
nicholas@2224
|
1790 this.getTestTime = function()
|
nicholas@2224
|
1791 {
|
nicholas@2224
|
1792 this.updateTestTime();
|
nicholas@2224
|
1793 return this.testDuration;
|
nicholas@2224
|
1794 };
|
nicholas@2224
|
1795 }
|
nicholas@2224
|
1796
|
nicholas@2224
|
1797 function sessionMetrics(engine,specification)
|
nicholas@2224
|
1798 {
|
nicholas@2224
|
1799 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
nicholas@2224
|
1800 */
|
nicholas@2224
|
1801 this.engine = engine;
|
nicholas@2224
|
1802 this.lastClicked = -1;
|
nicholas@2224
|
1803 this.data = -1;
|
nicholas@2224
|
1804 this.reset = function() {
|
nicholas@2224
|
1805 this.lastClicked = -1;
|
nicholas@2224
|
1806 this.data = -1;
|
nicholas@2224
|
1807 };
|
nicholas@2224
|
1808
|
nicholas@2224
|
1809 this.enableElementInitialPosition = false;
|
nicholas@2224
|
1810 this.enableElementListenTracker = false;
|
nicholas@2224
|
1811 this.enableElementTimer = false;
|
nicholas@2224
|
1812 this.enableElementTracker = false;
|
nicholas@2224
|
1813 this.enableFlagListenedTo = false;
|
nicholas@2224
|
1814 this.enableFlagMoved = false;
|
nicholas@2224
|
1815 this.enableTestTimer = false;
|
nicholas@2224
|
1816 // Obtain the metrics enabled
|
nicholas@2224
|
1817 for (var i=0; i<specification.metrics.enabled.length; i++)
|
nicholas@2224
|
1818 {
|
nicholas@2224
|
1819 var node = specification.metrics.enabled[i];
|
nicholas@2224
|
1820 switch(node)
|
nicholas@2224
|
1821 {
|
nicholas@2224
|
1822 case 'testTimer':
|
nicholas@2224
|
1823 this.enableTestTimer = true;
|
nicholas@2224
|
1824 break;
|
nicholas@2224
|
1825 case 'elementTimer':
|
nicholas@2224
|
1826 this.enableElementTimer = true;
|
nicholas@2224
|
1827 break;
|
nicholas@2224
|
1828 case 'elementTracker':
|
nicholas@2224
|
1829 this.enableElementTracker = true;
|
nicholas@2224
|
1830 break;
|
nicholas@2224
|
1831 case 'elementListenTracker':
|
nicholas@2224
|
1832 this.enableElementListenTracker = true;
|
nicholas@2224
|
1833 break;
|
nicholas@2224
|
1834 case 'elementInitialPosition':
|
nicholas@2224
|
1835 this.enableElementInitialPosition = true;
|
nicholas@2224
|
1836 break;
|
nicholas@2224
|
1837 case 'elementFlagListenedTo':
|
nicholas@2224
|
1838 this.enableFlagListenedTo = true;
|
nicholas@2224
|
1839 break;
|
nicholas@2224
|
1840 case 'elementFlagMoved':
|
nicholas@2224
|
1841 this.enableFlagMoved = true;
|
nicholas@2224
|
1842 break;
|
nicholas@2224
|
1843 case 'elementFlagComments':
|
nicholas@2224
|
1844 this.enableFlagComments = true;
|
nicholas@2224
|
1845 break;
|
nicholas@2224
|
1846 }
|
nicholas@2224
|
1847 }
|
nicholas@2224
|
1848 this.initialiseTest = function(){};
|
nicholas@2224
|
1849 }
|
nicholas@2224
|
1850
|
nicholas@2224
|
1851 function metricTracker(caller)
|
nicholas@2224
|
1852 {
|
nicholas@2224
|
1853 /* Custom object to track and collect metric data
|
nicholas@2224
|
1854 * Used only inside the audioObjects object.
|
nicholas@2224
|
1855 */
|
nicholas@2224
|
1856
|
nicholas@2224
|
1857 this.listenedTimer = 0;
|
nicholas@2224
|
1858 this.listenStart = 0;
|
nicholas@2224
|
1859 this.listenHold = false;
|
nicholas@2224
|
1860 this.initialPosition = -1;
|
nicholas@2224
|
1861 this.movementTracker = [];
|
nicholas@2224
|
1862 this.listenTracker =[];
|
nicholas@2224
|
1863 this.wasListenedTo = false;
|
nicholas@2224
|
1864 this.wasMoved = false;
|
nicholas@2224
|
1865 this.hasComments = false;
|
nicholas@2224
|
1866 this.parent = caller;
|
nicholas@2224
|
1867
|
nicholas@2224
|
1868 this.initialise = function(position)
|
nicholas@2224
|
1869 {
|
nicholas@2224
|
1870 if (this.initialPosition == -1) {
|
nicholas@2224
|
1871 this.initialPosition = position;
|
nicholas@2224
|
1872 this.moved(0,position);
|
nicholas@2224
|
1873 }
|
nicholas@2224
|
1874 };
|
nicholas@2224
|
1875
|
nicholas@2224
|
1876 this.moved = function(time,position)
|
nicholas@2224
|
1877 {
|
nicholas@2224
|
1878 if (time > 0) {this.wasMoved = true;}
|
nicholas@2224
|
1879 this.movementTracker[this.movementTracker.length] = [time, position];
|
nicholas@2224
|
1880 };
|
nicholas@2224
|
1881
|
nicholas@2224
|
1882 this.startListening = function(time)
|
nicholas@2224
|
1883 {
|
nicholas@2224
|
1884 if (this.listenHold == false)
|
nicholas@2224
|
1885 {
|
nicholas@2224
|
1886 this.wasListenedTo = true;
|
nicholas@2224
|
1887 this.listenStart = time;
|
nicholas@2224
|
1888 this.listenHold = true;
|
nicholas@2224
|
1889
|
nicholas@2224
|
1890 var evnt = document.createElement('event');
|
nicholas@2224
|
1891 var testTime = document.createElement('testTime');
|
nicholas@2224
|
1892 testTime.setAttribute('start',time);
|
nicholas@2224
|
1893 var bufferTime = document.createElement('bufferTime');
|
nicholas@2224
|
1894 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
|
nicholas@2224
|
1895 evnt.appendChild(testTime);
|
nicholas@2224
|
1896 evnt.appendChild(bufferTime);
|
nicholas@2224
|
1897 this.listenTracker.push(evnt);
|
nicholas@2224
|
1898
|
nicholas@2224
|
1899 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2224
|
1900 }
|
nicholas@2224
|
1901 };
|
nicholas@2224
|
1902
|
nicholas@2224
|
1903 this.stopListening = function(time,bufferStopTime)
|
nicholas@2224
|
1904 {
|
nicholas@2224
|
1905 if (this.listenHold == true)
|
nicholas@2224
|
1906 {
|
nicholas@2224
|
1907 var diff = time - this.listenStart;
|
nicholas@2224
|
1908 this.listenedTimer += (diff);
|
nicholas@2224
|
1909 this.listenStart = 0;
|
nicholas@2224
|
1910 this.listenHold = false;
|
nicholas@2224
|
1911
|
nicholas@2224
|
1912 var evnt = this.listenTracker[this.listenTracker.length-1];
|
nicholas@2224
|
1913 var testTime = evnt.getElementsByTagName('testTime')[0];
|
nicholas@2224
|
1914 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
nicholas@2224
|
1915 testTime.setAttribute('stop',time);
|
nicholas@2224
|
1916 if (bufferStopTime == undefined) {
|
nicholas@2224
|
1917 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
|
nicholas@2224
|
1918 } else {
|
nicholas@2224
|
1919 bufferTime.setAttribute('stop',bufferStopTime);
|
nicholas@2224
|
1920 }
|
nicholas@2224
|
1921 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2224
|
1922 }
|
nicholas@2224
|
1923 };
|
nicholas@2224
|
1924
|
nicholas@2224
|
1925 this.exportXMLDOM = function() {
|
nicholas@2224
|
1926 var storeDOM = [];
|
nicholas@2224
|
1927 if (audioEngineContext.metric.enableElementTimer) {
|
nicholas@2224
|
1928 var mElementTimer = storage.document.createElement('metricresult');
|
nicholas@2224
|
1929 mElementTimer.setAttribute('name','enableElementTimer');
|
nicholas@2224
|
1930 mElementTimer.textContent = this.listenedTimer;
|
nicholas@2224
|
1931 storeDOM.push(mElementTimer);
|
nicholas@2224
|
1932 }
|
nicholas@2224
|
1933 if (audioEngineContext.metric.enableElementTracker) {
|
b@2281
|
1934 var elementTrackerFull = storage.document.createElement('metricresult');
|
nicholas@2224
|
1935 elementTrackerFull.setAttribute('name','elementTrackerFull');
|
nicholas@2224
|
1936 for (var k=0; k<this.movementTracker.length; k++)
|
nicholas@2224
|
1937 {
|
nicholas@2224
|
1938 var timePos = storage.document.createElement('movement');
|
nicholas@2224
|
1939 timePos.setAttribute("time",this.movementTracker[k][0]);
|
nicholas@2224
|
1940 timePos.setAttribute("value",this.movementTracker[k][1]);
|
nicholas@2224
|
1941 elementTrackerFull.appendChild(timePos);
|
nicholas@2224
|
1942 }
|
nicholas@2224
|
1943 storeDOM.push(elementTrackerFull);
|
nicholas@2224
|
1944 }
|
nicholas@2224
|
1945 if (audioEngineContext.metric.enableElementListenTracker) {
|
b@2281
|
1946 var elementListenTracker = storage.document.createElement('metricresult');
|
nicholas@2224
|
1947 elementListenTracker.setAttribute('name','elementListenTracker');
|
nicholas@2224
|
1948 for (var k=0; k<this.listenTracker.length; k++) {
|
nicholas@2224
|
1949 elementListenTracker.appendChild(this.listenTracker[k]);
|
nicholas@2224
|
1950 }
|
nicholas@2224
|
1951 storeDOM.push(elementListenTracker);
|
nicholas@2224
|
1952 }
|
nicholas@2224
|
1953 if (audioEngineContext.metric.enableElementInitialPosition) {
|
b@2281
|
1954 var elementInitial = storage.document.createElement('metricresult');
|
nicholas@2224
|
1955 elementInitial.setAttribute('name','elementInitialPosition');
|
nicholas@2224
|
1956 elementInitial.textContent = this.initialPosition;
|
nicholas@2224
|
1957 storeDOM.push(elementInitial);
|
nicholas@2224
|
1958 }
|
nicholas@2224
|
1959 if (audioEngineContext.metric.enableFlagListenedTo) {
|
b@2281
|
1960 var flagListenedTo = storage.document.createElement('metricresult');
|
nicholas@2224
|
1961 flagListenedTo.setAttribute('name','elementFlagListenedTo');
|
nicholas@2224
|
1962 flagListenedTo.textContent = this.wasListenedTo;
|
nicholas@2224
|
1963 storeDOM.push(flagListenedTo);
|
nicholas@2224
|
1964 }
|
nicholas@2224
|
1965 if (audioEngineContext.metric.enableFlagMoved) {
|
b@2281
|
1966 var flagMoved = storage.document.createElement('metricresult');
|
nicholas@2224
|
1967 flagMoved.setAttribute('name','elementFlagMoved');
|
nicholas@2224
|
1968 flagMoved.textContent = this.wasMoved;
|
nicholas@2224
|
1969 storeDOM.push(flagMoved);
|
nicholas@2224
|
1970 }
|
nicholas@2224
|
1971 if (audioEngineContext.metric.enableFlagComments) {
|
b@2281
|
1972 var flagComments = storage.document.createElement('metricresult');
|
nicholas@2224
|
1973 flagComments.setAttribute('name','elementFlagComments');
|
nicholas@2224
|
1974 if (this.parent.commentDOM == null)
|
nicholas@2224
|
1975 {flag.textContent = 'false';}
|
nicholas@2224
|
1976 else if (this.parent.commentDOM.textContent.length == 0)
|
nicholas@2224
|
1977 {flag.textContent = 'false';}
|
nicholas@2224
|
1978 else
|
nicholas@2224
|
1979 {flag.textContet = 'true';}
|
nicholas@2224
|
1980 storeDOM.push(flagComments);
|
nicholas@2224
|
1981 }
|
nicholas@2224
|
1982 return storeDOM;
|
nicholas@2224
|
1983 };
|
nicholas@2224
|
1984 }
|
nicholas@2224
|
1985
|
nicholas@2224
|
1986 function Interface(specificationObject) {
|
nicholas@2224
|
1987 // This handles the bindings between the interface and the audioEngineContext;
|
nicholas@2224
|
1988 this.specification = specificationObject;
|
nicholas@2224
|
1989 this.insertPoint = document.getElementById("topLevelBody");
|
nicholas@2224
|
1990
|
nicholas@2224
|
1991 this.newPage = function(audioHolderObject,store)
|
nicholas@2224
|
1992 {
|
nicholas@2224
|
1993 audioEngineContext.newTestPage(audioHolderObject,store);
|
nicholas@2224
|
1994 interfaceContext.commentBoxes.deleteCommentBoxes();
|
nicholas@2224
|
1995 interfaceContext.deleteCommentQuestions();
|
nicholas@2224
|
1996 loadTest(audioHolderObject,store);
|
nicholas@2224
|
1997 };
|
nicholas@2224
|
1998
|
nicholas@2224
|
1999 // Bounded by interface!!
|
nicholas@2224
|
2000 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
nicholas@2224
|
2001 // For example, APE returns the slider position normalised in a <value> tag.
|
nicholas@2224
|
2002 this.interfaceObjects = [];
|
nicholas@2224
|
2003 this.interfaceObject = function(){};
|
nicholas@2224
|
2004
|
nicholas@2224
|
2005 this.resizeWindow = function(event)
|
nicholas@2224
|
2006 {
|
nicholas@2224
|
2007 popup.resize(event);
|
nicholas@2352
|
2008 this.volume.resize();
|
nicholas@2360
|
2009 this.lightbox.resize();
|
nicholas@2224
|
2010 for(var i=0; i<this.commentBoxes.length; i++)
|
nicholas@2224
|
2011 {this.commentBoxes[i].resize();}
|
nicholas@2224
|
2012 for(var i=0; i<this.commentQuestions.length; i++)
|
nicholas@2224
|
2013 {this.commentQuestions[i].resize();}
|
nicholas@2224
|
2014 try
|
nicholas@2224
|
2015 {
|
nicholas@2224
|
2016 resizeWindow(event);
|
nicholas@2224
|
2017 }
|
nicholas@2224
|
2018 catch(err)
|
nicholas@2224
|
2019 {
|
nicholas@2224
|
2020 console.log("Warning - Interface does not have Resize option");
|
nicholas@2224
|
2021 console.log(err);
|
nicholas@2224
|
2022 }
|
nicholas@2224
|
2023 };
|
nicholas@2224
|
2024
|
nicholas@2224
|
2025 this.returnNavigator = function()
|
nicholas@2224
|
2026 {
|
nicholas@2224
|
2027 var node = storage.document.createElement("navigator");
|
nicholas@2224
|
2028 var platform = storage.document.createElement("platform");
|
nicholas@2224
|
2029 platform.textContent = navigator.platform;
|
nicholas@2224
|
2030 var vendor = storage.document.createElement("vendor");
|
nicholas@2224
|
2031 vendor.textContent = navigator.vendor;
|
nicholas@2224
|
2032 var userAgent = storage.document.createElement("uagent");
|
nicholas@2224
|
2033 userAgent.textContent = navigator.userAgent;
|
nicholas@2224
|
2034 var screen = storage.document.createElement("window");
|
nicholas@2224
|
2035 screen.setAttribute('innerWidth',window.innerWidth);
|
nicholas@2224
|
2036 screen.setAttribute('innerHeight',window.innerHeight);
|
nicholas@2224
|
2037 node.appendChild(platform);
|
nicholas@2224
|
2038 node.appendChild(vendor);
|
nicholas@2224
|
2039 node.appendChild(userAgent);
|
nicholas@2224
|
2040 node.appendChild(screen);
|
nicholas@2224
|
2041 return node;
|
nicholas@2224
|
2042 };
|
nicholas@2224
|
2043
|
nicholas@2224
|
2044 this.returnDateNode = function()
|
nicholas@2224
|
2045 {
|
nicholas@2224
|
2046 // Create an XML Node for the Date and Time a test was conducted
|
nicholas@2224
|
2047 // Structure is
|
nicholas@2224
|
2048 // <datetime>
|
nicholas@2224
|
2049 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
nicholas@2224
|
2050 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
nicholas@2224
|
2051 // </datetime>
|
nicholas@2224
|
2052 var dateTime = new Date();
|
nicholas@2224
|
2053 var hold = storage.document.createElement("datetime");
|
nicholas@2224
|
2054 var date = storage.document.createElement("date");
|
nicholas@2224
|
2055 var time = storage.document.createElement("time");
|
nicholas@2224
|
2056 date.setAttribute('year',dateTime.getFullYear());
|
nicholas@2224
|
2057 date.setAttribute('month',dateTime.getMonth()+1);
|
nicholas@2224
|
2058 date.setAttribute('day',dateTime.getDate());
|
nicholas@2224
|
2059 time.setAttribute('hour',dateTime.getHours());
|
nicholas@2224
|
2060 time.setAttribute('minute',dateTime.getMinutes());
|
nicholas@2224
|
2061 time.setAttribute('secs',dateTime.getSeconds());
|
nicholas@2224
|
2062
|
nicholas@2224
|
2063 hold.appendChild(date);
|
nicholas@2224
|
2064 hold.appendChild(time);
|
nicholas@2224
|
2065 return hold;
|
nicholas@2224
|
2066
|
nicholas@2224
|
2067 }
|
nicholas@2360
|
2068
|
nicholas@2360
|
2069 this.lightbox = {
|
nicholas@2360
|
2070 parent: this,
|
nicholas@2360
|
2071 root: document.createElement("div"),
|
nicholas@2360
|
2072 content: document.createElement("div"),
|
nicholas@2360
|
2073 accept: document.createElement("button"),
|
nicholas@2360
|
2074 blanker: document.createElement("div"),
|
nicholas@2360
|
2075 post: function(type,message) {
|
nicholas@2360
|
2076 switch(type) {
|
nicholas@2360
|
2077 case "Error":
|
nicholas@2360
|
2078 this.content.className = "lightbox-error";
|
nicholas@2360
|
2079 break;
|
nicholas@2360
|
2080 case "Warning":
|
nicholas@2360
|
2081 this.content.className = "lightbox-warning";
|
nicholas@2360
|
2082 break;
|
nicholas@2360
|
2083 default:
|
nicholas@2360
|
2084 this.content.className = "lightbox-message";
|
nicholas@2360
|
2085 break;
|
nicholas@2360
|
2086 }
|
nicholas@2360
|
2087 var msg = document.createElement("p");
|
nicholas@2360
|
2088 msg.textContent = message;
|
nicholas@2360
|
2089 this.content.appendChild(msg);
|
nicholas@2360
|
2090 this.show();
|
nicholas@2360
|
2091 },
|
nicholas@2360
|
2092 show: function() {
|
nicholas@2360
|
2093 this.root.style.visibility = "visible";
|
nicholas@2360
|
2094 this.blanker.style.visibility = "visible";
|
nicholas@2360
|
2095 },
|
nicholas@2360
|
2096 clear: function() {
|
nicholas@2360
|
2097 this.root.style.visibility = "";
|
nicholas@2360
|
2098 this.blanker.style.visibility = "";
|
nicholas@2360
|
2099 this.content.textContent = "";
|
nicholas@2360
|
2100 },
|
nicholas@2360
|
2101 handleEvent: function(event) {
|
nicholas@2360
|
2102 if (event.currentTarget == this.accept) {
|
nicholas@2360
|
2103 this.clear();
|
nicholas@2360
|
2104 }
|
nicholas@2360
|
2105 },
|
nicholas@2360
|
2106 resize: function(event) {
|
nicholas@2360
|
2107 this.root.style.left = (window.innerWidth/2)-250 + 'px';
|
nicholas@2360
|
2108 }
|
nicholas@2360
|
2109 }
|
nicholas@2360
|
2110
|
nicholas@2360
|
2111 this.lightbox.root.appendChild(this.lightbox.content);
|
nicholas@2360
|
2112 this.lightbox.root.appendChild(this.lightbox.accept);
|
nicholas@2360
|
2113 this.lightbox.root.className = "popupHolder";
|
nicholas@2360
|
2114 this.lightbox.root.id = "lightbox-root";
|
nicholas@2360
|
2115 this.lightbox.accept.className = "popupButton";
|
nicholas@2360
|
2116 this.lightbox.accept.style.bottom = "10px";
|
nicholas@2360
|
2117 this.lightbox.accept.textContent = "OK";
|
nicholas@2360
|
2118 this.lightbox.accept.style.left = "237.5px";
|
nicholas@2360
|
2119 this.lightbox.accept.addEventListener("click",this.lightbox);
|
nicholas@2360
|
2120 this.lightbox.blanker.className = "testHalt";
|
nicholas@2360
|
2121 this.lightbox.blanker.id = "lightbox-blanker";
|
nicholas@2360
|
2122 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
|
nicholas@2360
|
2123 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
|
nicholas@2224
|
2124
|
nicholas@2224
|
2125 this.commentBoxes = new function() {
|
nicholas@2224
|
2126 this.boxes = [];
|
nicholas@2224
|
2127 this.injectPoint = null;
|
nicholas@2224
|
2128 this.elementCommentBox = function(audioObject) {
|
nicholas@2224
|
2129 var element = audioObject.specification;
|
nicholas@2224
|
2130 this.audioObject = audioObject;
|
nicholas@2224
|
2131 this.id = audioObject.id;
|
nicholas@2224
|
2132 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
2133 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2134 this.trackComment = document.createElement('div');
|
nicholas@2224
|
2135 this.trackComment.className = 'comment-div';
|
nicholas@2224
|
2136 this.trackComment.id = 'comment-div-'+audioObject.id;
|
nicholas@2224
|
2137 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2138 this.trackString = document.createElement('span');
|
nicholas@2224
|
2139 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
2140 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2141 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
2142 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
2143 this.trackCommentBox.cols = '100';
|
nicholas@2224
|
2144 this.trackCommentBox.name = 'trackComment'+audioObject.id;
|
nicholas@2224
|
2145 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
2146 var br = document.createElement('br');
|
nicholas@2224
|
2147 // Add to the holder.
|
nicholas@2224
|
2148 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
2149 this.trackComment.appendChild(br);
|
nicholas@2224
|
2150 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
2151
|
nicholas@2224
|
2152 this.exportXMLDOM = function() {
|
nicholas@2224
|
2153 var root = document.createElement('comment');
|
nicholas@2224
|
2154 var question = document.createElement('question');
|
nicholas@2224
|
2155 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2156 var response = document.createElement('response');
|
nicholas@2224
|
2157 response.textContent = this.trackCommentBox.value;
|
nicholas@2224
|
2158 console.log("Comment frag-"+this.id+": "+response.textContent);
|
nicholas@2224
|
2159 root.appendChild(question);
|
nicholas@2224
|
2160 root.appendChild(response);
|
nicholas@2224
|
2161 return root;
|
nicholas@2224
|
2162 };
|
nicholas@2224
|
2163 this.resize = function()
|
nicholas@2224
|
2164 {
|
nicholas@2224
|
2165 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2166 if (boxwidth >= 600)
|
nicholas@2224
|
2167 {
|
nicholas@2224
|
2168 boxwidth = 600;
|
nicholas@2224
|
2169 }
|
nicholas@2224
|
2170 else if (boxwidth < 400)
|
nicholas@2224
|
2171 {
|
nicholas@2224
|
2172 boxwidth = 400;
|
nicholas@2224
|
2173 }
|
nicholas@2224
|
2174 this.trackComment.style.width = boxwidth+"px";
|
nicholas@2224
|
2175 this.trackCommentBox.style.width = boxwidth-6+"px";
|
nicholas@2224
|
2176 };
|
nicholas@2224
|
2177 this.resize();
|
nicholas@2224
|
2178 };
|
nicholas@2224
|
2179 this.createCommentBox = function(audioObject) {
|
nicholas@2224
|
2180 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2181 this.boxes.push(node);
|
nicholas@2224
|
2182 audioObject.commentDOM = node;
|
nicholas@2224
|
2183 return node;
|
nicholas@2224
|
2184 };
|
nicholas@2224
|
2185 this.sortCommentBoxes = function() {
|
nicholas@2224
|
2186 this.boxes.sort(function(a,b){return a.id - b.id;});
|
nicholas@2224
|
2187 };
|
nicholas@2224
|
2188
|
nicholas@2224
|
2189 this.showCommentBoxes = function(inject, sort) {
|
nicholas@2224
|
2190 this.injectPoint = inject;
|
nicholas@2224
|
2191 if (sort) {this.sortCommentBoxes();}
|
nicholas@2224
|
2192 for (var box of this.boxes) {
|
nicholas@2224
|
2193 inject.appendChild(box.trackComment);
|
nicholas@2224
|
2194 }
|
nicholas@2224
|
2195 };
|
nicholas@2224
|
2196
|
nicholas@2224
|
2197 this.deleteCommentBoxes = function() {
|
nicholas@2224
|
2198 if (this.injectPoint != null) {
|
nicholas@2224
|
2199 for (var box of this.boxes) {
|
nicholas@2224
|
2200 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2224
|
2201 }
|
nicholas@2224
|
2202 this.injectPoint = null;
|
nicholas@2224
|
2203 }
|
nicholas@2224
|
2204 this.boxes = [];
|
nicholas@2224
|
2205 };
|
nicholas@2224
|
2206 }
|
nicholas@2224
|
2207
|
nicholas@2224
|
2208 this.commentQuestions = [];
|
nicholas@2224
|
2209
|
nicholas@2224
|
2210 this.commentBox = function(commentQuestion) {
|
nicholas@2224
|
2211 this.specification = commentQuestion;
|
nicholas@2224
|
2212 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2213 this.holder = document.createElement('div');
|
nicholas@2224
|
2214 this.holder.className = 'comment-div';
|
nicholas@2224
|
2215 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2216 this.string = document.createElement('span');
|
nicholas@2224
|
2217 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2218 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2219 this.textArea = document.createElement('textarea');
|
nicholas@2224
|
2220 this.textArea.rows = '4';
|
nicholas@2224
|
2221 this.textArea.cols = '100';
|
nicholas@2224
|
2222 this.textArea.className = 'trackComment';
|
nicholas@2224
|
2223 var br = document.createElement('br');
|
nicholas@2224
|
2224 // Add to the holder.
|
nicholas@2224
|
2225 this.holder.appendChild(this.string);
|
nicholas@2224
|
2226 this.holder.appendChild(br);
|
nicholas@2224
|
2227 this.holder.appendChild(this.textArea);
|
nicholas@2224
|
2228
|
nicholas@2224
|
2229 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2230 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2231 root.id = this.specification.id;
|
nicholas@2224
|
2232 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2233 console.log("Question: "+this.string.textContent);
|
nicholas@2224
|
2234 console.log("Response: "+root.textContent);
|
nicholas@2224
|
2235 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2236 question.textContent = this.string.textContent;
|
nicholas@2224
|
2237 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2238 response.textContent = this.textArea.value;
|
nicholas@2224
|
2239 root.appendChild(question);
|
nicholas@2224
|
2240 root.appendChild(response);
|
nicholas@2224
|
2241 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2242 return root;
|
nicholas@2224
|
2243 };
|
nicholas@2224
|
2244 this.resize = function()
|
nicholas@2224
|
2245 {
|
nicholas@2224
|
2246 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2247 if (boxwidth >= 600)
|
nicholas@2224
|
2248 {
|
nicholas@2224
|
2249 boxwidth = 600;
|
nicholas@2224
|
2250 }
|
nicholas@2224
|
2251 else if (boxwidth < 400)
|
nicholas@2224
|
2252 {
|
nicholas@2224
|
2253 boxwidth = 400;
|
nicholas@2224
|
2254 }
|
nicholas@2224
|
2255 this.holder.style.width = boxwidth+"px";
|
nicholas@2224
|
2256 this.textArea.style.width = boxwidth-6+"px";
|
nicholas@2224
|
2257 };
|
nicholas@2224
|
2258 this.resize();
|
nicholas@2224
|
2259 };
|
nicholas@2224
|
2260
|
nicholas@2224
|
2261 this.radioBox = function(commentQuestion) {
|
nicholas@2224
|
2262 this.specification = commentQuestion;
|
nicholas@2224
|
2263 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2264 this.holder = document.createElement('div');
|
nicholas@2224
|
2265 this.holder.className = 'comment-div';
|
nicholas@2224
|
2266 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2267 this.string = document.createElement('span');
|
nicholas@2224
|
2268 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2269 var br = document.createElement('br');
|
nicholas@2224
|
2270 // Add to the holder.
|
nicholas@2224
|
2271 this.holder.appendChild(this.string);
|
nicholas@2224
|
2272 this.holder.appendChild(br);
|
nicholas@2224
|
2273 this.options = [];
|
nicholas@2224
|
2274 this.inputs = document.createElement('div');
|
nicholas@2224
|
2275 this.span = document.createElement('div');
|
nicholas@2224
|
2276 this.inputs.align = 'center';
|
nicholas@2224
|
2277 this.inputs.style.marginLeft = '12px';
|
nicholas@2294
|
2278 this.inputs.className = "comment-radio-inputs-holder";
|
nicholas@2224
|
2279 this.span.style.marginLeft = '12px';
|
nicholas@2224
|
2280 this.span.align = 'center';
|
nicholas@2224
|
2281 this.span.style.marginTop = '15px';
|
nicholas@2294
|
2282 this.span.className = "comment-radio-span-holder";
|
nicholas@2224
|
2283
|
nicholas@2224
|
2284 var optCount = commentQuestion.options.length;
|
nicholas@2224
|
2285 for (var optNode of commentQuestion.options)
|
nicholas@2224
|
2286 {
|
nicholas@2224
|
2287 var div = document.createElement('div');
|
nicholas@2224
|
2288 div.style.width = '80px';
|
nicholas@2224
|
2289 div.style.float = 'left';
|
nicholas@2224
|
2290 var input = document.createElement('input');
|
nicholas@2224
|
2291 input.type = 'radio';
|
nicholas@2224
|
2292 input.name = commentQuestion.id;
|
nicholas@2224
|
2293 input.setAttribute('setvalue',optNode.name);
|
nicholas@2224
|
2294 input.className = 'comment-radio';
|
nicholas@2224
|
2295 div.appendChild(input);
|
nicholas@2224
|
2296 this.inputs.appendChild(div);
|
nicholas@2224
|
2297
|
nicholas@2224
|
2298
|
nicholas@2224
|
2299 div = document.createElement('div');
|
nicholas@2224
|
2300 div.style.width = '80px';
|
nicholas@2224
|
2301 div.style.float = 'left';
|
nicholas@2224
|
2302 div.align = 'center';
|
nicholas@2224
|
2303 var span = document.createElement('span');
|
nicholas@2224
|
2304 span.textContent = optNode.text;
|
nicholas@2224
|
2305 span.className = 'comment-radio-span';
|
nicholas@2224
|
2306 div.appendChild(span);
|
nicholas@2224
|
2307 this.span.appendChild(div);
|
nicholas@2224
|
2308 this.options.push(input);
|
nicholas@2224
|
2309 }
|
nicholas@2224
|
2310 this.holder.appendChild(this.span);
|
nicholas@2224
|
2311 this.holder.appendChild(this.inputs);
|
nicholas@2224
|
2312
|
nicholas@2224
|
2313 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2314 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2315 root.id = this.specification.id;
|
nicholas@2224
|
2316 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2317 var question = document.createElement('question');
|
nicholas@2224
|
2318 question.textContent = this.string.textContent;
|
nicholas@2224
|
2319 var response = document.createElement('response');
|
nicholas@2224
|
2320 var i=0;
|
nicholas@2224
|
2321 while(this.options[i].checked == false) {
|
nicholas@2224
|
2322 i++;
|
nicholas@2224
|
2323 if (i >= this.options.length) {
|
nicholas@2224
|
2324 break;
|
nicholas@2224
|
2325 }
|
nicholas@2224
|
2326 }
|
nicholas@2224
|
2327 if (i >= this.options.length) {
|
nicholas@2224
|
2328 response.textContent = 'null';
|
nicholas@2224
|
2329 } else {
|
nicholas@2224
|
2330 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2224
|
2331 response.setAttribute('number',i);
|
nicholas@2224
|
2332 }
|
nicholas@2224
|
2333 console.log('Comment: '+question.textContent);
|
nicholas@2224
|
2334 console.log('Response: '+response.textContent);
|
nicholas@2224
|
2335 root.appendChild(question);
|
nicholas@2224
|
2336 root.appendChild(response);
|
nicholas@2224
|
2337 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2338 return root;
|
nicholas@2224
|
2339 };
|
nicholas@2224
|
2340 this.resize = function()
|
nicholas@2224
|
2341 {
|
nicholas@2224
|
2342 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2343 if (boxwidth >= 600)
|
nicholas@2224
|
2344 {
|
nicholas@2224
|
2345 boxwidth = 600;
|
nicholas@2224
|
2346 }
|
nicholas@2224
|
2347 else if (boxwidth < 400)
|
nicholas@2224
|
2348 {
|
nicholas@2224
|
2349 boxwidth = 400;
|
nicholas@2224
|
2350 }
|
nicholas@2224
|
2351 this.holder.style.width = boxwidth+"px";
|
nicholas@2294
|
2352 var text = this.holder.getElementsByClassName("comment-radio-span-holder")[0];
|
nicholas@2294
|
2353 var options = this.holder.getElementsByClassName("comment-radio-inputs-holder")[0];
|
nicholas@2294
|
2354 var optCount = options.childElementCount;
|
nicholas@2224
|
2355 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
nicholas@2224
|
2356 var options = options.firstChild;
|
nicholas@2224
|
2357 var text = text.firstChild;
|
nicholas@2224
|
2358 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2359 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2360 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2361 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2362 while(options.nextSibling != undefined)
|
nicholas@2224
|
2363 {
|
nicholas@2224
|
2364 options = options.nextSibling;
|
nicholas@2224
|
2365 text = text.nextSibling;
|
nicholas@2224
|
2366 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2367 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2368 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2369 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2370 }
|
nicholas@2224
|
2371 };
|
nicholas@2224
|
2372 this.resize();
|
nicholas@2224
|
2373 };
|
nicholas@2224
|
2374
|
nicholas@2224
|
2375 this.checkboxBox = function(commentQuestion) {
|
nicholas@2224
|
2376 this.specification = commentQuestion;
|
nicholas@2224
|
2377 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2378 this.holder = document.createElement('div');
|
nicholas@2224
|
2379 this.holder.className = 'comment-div';
|
nicholas@2224
|
2380 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2381 this.string = document.createElement('span');
|
nicholas@2224
|
2382 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2224
|
2383 var br = document.createElement('br');
|
nicholas@2224
|
2384 // Add to the holder.
|
nicholas@2224
|
2385 this.holder.appendChild(this.string);
|
nicholas@2224
|
2386 this.holder.appendChild(br);
|
nicholas@2224
|
2387 this.options = [];
|
nicholas@2224
|
2388 this.inputs = document.createElement('div');
|
nicholas@2224
|
2389 this.span = document.createElement('div');
|
nicholas@2224
|
2390 this.inputs.align = 'center';
|
nicholas@2224
|
2391 this.inputs.style.marginLeft = '12px';
|
nicholas@2294
|
2392 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2224
|
2393 this.span.style.marginLeft = '12px';
|
nicholas@2224
|
2394 this.span.align = 'center';
|
nicholas@2224
|
2395 this.span.style.marginTop = '15px';
|
nicholas@2294
|
2396 this.span.className = "comment-checkbox-span-holder";
|
nicholas@2224
|
2397
|
nicholas@2224
|
2398 var optCount = commentQuestion.options.length;
|
nicholas@2224
|
2399 for (var i=0; i<optCount; i++)
|
nicholas@2224
|
2400 {
|
nicholas@2224
|
2401 var div = document.createElement('div');
|
nicholas@2224
|
2402 div.style.width = '80px';
|
nicholas@2224
|
2403 div.style.float = 'left';
|
nicholas@2224
|
2404 var input = document.createElement('input');
|
nicholas@2224
|
2405 input.type = 'checkbox';
|
nicholas@2224
|
2406 input.name = commentQuestion.id;
|
nicholas@2224
|
2407 input.setAttribute('setvalue',commentQuestion.options[i].name);
|
nicholas@2224
|
2408 input.className = 'comment-radio';
|
nicholas@2224
|
2409 div.appendChild(input);
|
nicholas@2224
|
2410 this.inputs.appendChild(div);
|
nicholas@2224
|
2411
|
nicholas@2224
|
2412
|
nicholas@2224
|
2413 div = document.createElement('div');
|
nicholas@2224
|
2414 div.style.width = '80px';
|
nicholas@2224
|
2415 div.style.float = 'left';
|
nicholas@2224
|
2416 div.align = 'center';
|
nicholas@2224
|
2417 var span = document.createElement('span');
|
nicholas@2224
|
2418 span.textContent = commentQuestion.options[i].text;
|
nicholas@2224
|
2419 span.className = 'comment-radio-span';
|
nicholas@2224
|
2420 div.appendChild(span);
|
nicholas@2224
|
2421 this.span.appendChild(div);
|
nicholas@2224
|
2422 this.options.push(input);
|
nicholas@2224
|
2423 }
|
nicholas@2224
|
2424 this.holder.appendChild(this.span);
|
nicholas@2224
|
2425 this.holder.appendChild(this.inputs);
|
nicholas@2224
|
2426
|
nicholas@2224
|
2427 this.exportXMLDOM = function(storePoint) {
|
nicholas@2224
|
2428 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2224
|
2429 root.id = this.specification.id;
|
nicholas@2224
|
2430 root.setAttribute('type',this.specification.type);
|
nicholas@2224
|
2431 var question = document.createElement('question');
|
nicholas@2224
|
2432 question.textContent = this.string.textContent;
|
nicholas@2224
|
2433 root.appendChild(question);
|
nicholas@2224
|
2434 console.log('Comment: '+question.textContent);
|
nicholas@2224
|
2435 for (var i=0; i<this.options.length; i++) {
|
nicholas@2224
|
2436 var response = document.createElement('response');
|
nicholas@2224
|
2437 response.textContent = this.options[i].checked;
|
nicholas@2224
|
2438 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
|
nicholas@2224
|
2439 root.appendChild(response);
|
nicholas@2224
|
2440 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
|
nicholas@2224
|
2441 }
|
nicholas@2224
|
2442 storePoint.XMLDOM.appendChild(root);
|
nicholas@2224
|
2443 return root;
|
nicholas@2224
|
2444 };
|
nicholas@2224
|
2445 this.resize = function()
|
nicholas@2224
|
2446 {
|
nicholas@2224
|
2447 var boxwidth = (window.innerWidth-100)/2;
|
nicholas@2224
|
2448 if (boxwidth >= 600)
|
nicholas@2224
|
2449 {
|
nicholas@2224
|
2450 boxwidth = 600;
|
nicholas@2224
|
2451 }
|
nicholas@2224
|
2452 else if (boxwidth < 400)
|
nicholas@2224
|
2453 {
|
nicholas@2224
|
2454 boxwidth = 400;
|
nicholas@2224
|
2455 }
|
nicholas@2224
|
2456 this.holder.style.width = boxwidth+"px";
|
nicholas@2294
|
2457 var text = this.holder.getElementsByClassName("comment-checkbox-span-holder")[0];
|
nicholas@2294
|
2458 var options = this.holder.getElementsByClassName("comment-checkbox-inputs-holder")[0];
|
nicholas@2294
|
2459 var optCount = options.childElementCount;
|
nicholas@2224
|
2460 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
nicholas@2224
|
2461 var options = options.firstChild;
|
nicholas@2224
|
2462 var text = text.firstChild;
|
nicholas@2224
|
2463 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2464 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2465 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2466 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2467 while(options.nextSibling != undefined)
|
nicholas@2224
|
2468 {
|
nicholas@2224
|
2469 options = options.nextSibling;
|
nicholas@2224
|
2470 text = text.nextSibling;
|
nicholas@2224
|
2471 options.style.marginRight = spanMargin;
|
nicholas@2224
|
2472 options.style.marginLeft = spanMargin;
|
nicholas@2224
|
2473 text.style.marginRight = spanMargin;
|
nicholas@2224
|
2474 text.style.marginLeft = spanMargin;
|
nicholas@2224
|
2475 }
|
nicholas@2224
|
2476 };
|
nicholas@2224
|
2477 this.resize();
|
nicholas@2224
|
2478 };
|
nicholas@2224
|
2479
|
nicholas@2224
|
2480 this.createCommentQuestion = function(element) {
|
nicholas@2224
|
2481 var node;
|
nicholas@2224
|
2482 if (element.type == 'question') {
|
nicholas@2224
|
2483 node = new this.commentBox(element);
|
nicholas@2224
|
2484 } else if (element.type == 'radio') {
|
nicholas@2224
|
2485 node = new this.radioBox(element);
|
nicholas@2224
|
2486 } else if (element.type == 'checkbox') {
|
nicholas@2224
|
2487 node = new this.checkboxBox(element);
|
nicholas@2224
|
2488 }
|
nicholas@2224
|
2489 this.commentQuestions.push(node);
|
nicholas@2224
|
2490 return node;
|
nicholas@2224
|
2491 };
|
nicholas@2224
|
2492
|
nicholas@2224
|
2493 this.deleteCommentQuestions = function()
|
nicholas@2224
|
2494 {
|
nicholas@2224
|
2495 this.commentQuestions = [];
|
nicholas@2224
|
2496 };
|
nicholas@2224
|
2497
|
nicholas@2224
|
2498 this.outsideReferenceDOM = function(audioObject,index,inject)
|
nicholas@2224
|
2499 {
|
nicholas@2224
|
2500 this.parent = audioObject;
|
nicholas@2224
|
2501 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2502 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2224
|
2503 this.outsideReferenceHolder.setAttribute('track-id',index);
|
nicholas@2224
|
2504 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2505 this.outsideReferenceHolder.disabled = true;
|
nicholas@2224
|
2506
|
nicholas@2224
|
2507 this.outsideReferenceHolder.onclick = function(event)
|
nicholas@2224
|
2508 {
|
nicholas@2224
|
2509 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
|
nicholas@2224
|
2510 };
|
nicholas@2224
|
2511 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2224
|
2512 this.enable = function()
|
nicholas@2224
|
2513 {
|
nicholas@2224
|
2514 if (this.parent.state == 1)
|
nicholas@2224
|
2515 {
|
nicholas@2224
|
2516 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2517 }
|
nicholas@2224
|
2518 };
|
nicholas@2224
|
2519 this.updateLoading = function(progress)
|
nicholas@2224
|
2520 {
|
nicholas@2224
|
2521 if (progress != 100)
|
nicholas@2224
|
2522 {
|
nicholas@2224
|
2523 progress = String(progress);
|
nicholas@2224
|
2524 progress = progress.split('.')[0];
|
nicholas@2224
|
2525 this.outsideReferenceHolder.textContent = progress+'%';
|
nicholas@2224
|
2526 } else {
|
nicholas@2224
|
2527 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2528 }
|
nicholas@2224
|
2529 };
|
nicholas@2224
|
2530 this.startPlayback = function()
|
nicholas@2224
|
2531 {
|
nicholas@2224
|
2532 // Called when playback has begun
|
nicholas@2224
|
2533 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2534 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2535 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2536 };
|
nicholas@2224
|
2537 this.stopPlayback = function()
|
nicholas@2224
|
2538 {
|
nicholas@2224
|
2539 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2540 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2541 };
|
nicholas@2224
|
2542 this.exportXMLDOM = function(audioObject)
|
nicholas@2224
|
2543 {
|
nicholas@2224
|
2544 return null;
|
nicholas@2224
|
2545 };
|
nicholas@2224
|
2546 this.getValue = function()
|
nicholas@2224
|
2547 {
|
nicholas@2224
|
2548 return 0;
|
nicholas@2224
|
2549 };
|
nicholas@2224
|
2550 this.getPresentedId = function()
|
nicholas@2224
|
2551 {
|
nicholas@2224
|
2552 return 'Reference';
|
nicholas@2224
|
2553 };
|
nicholas@2224
|
2554 this.canMove = function()
|
nicholas@2224
|
2555 {
|
nicholas@2224
|
2556 return false;
|
nicholas@2224
|
2557 };
|
nicholas@2224
|
2558 this.error = function() {
|
nicholas@2224
|
2559 // audioObject has an error!!
|
nicholas@2224
|
2560 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2561 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2224
|
2562 }
|
nicholas@2224
|
2563 }
|
nicholas@2224
|
2564
|
nicholas@2224
|
2565 this.playhead = new function()
|
nicholas@2224
|
2566 {
|
nicholas@2224
|
2567 this.object = document.createElement('div');
|
nicholas@2224
|
2568 this.object.className = 'playhead';
|
nicholas@2224
|
2569 this.object.align = 'left';
|
nicholas@2224
|
2570 var curTime = document.createElement('div');
|
nicholas@2224
|
2571 curTime.style.width = '50px';
|
nicholas@2224
|
2572 this.curTimeSpan = document.createElement('span');
|
nicholas@2224
|
2573 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2574 curTime.appendChild(this.curTimeSpan);
|
nicholas@2224
|
2575 this.object.appendChild(curTime);
|
nicholas@2224
|
2576 this.scrubberTrack = document.createElement('div');
|
nicholas@2224
|
2577 this.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2224
|
2578
|
nicholas@2224
|
2579 this.scrubberHead = document.createElement('div');
|
nicholas@2224
|
2580 this.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2224
|
2581 this.scrubberTrack.appendChild(this.scrubberHead);
|
nicholas@2224
|
2582 this.object.appendChild(this.scrubberTrack);
|
nicholas@2224
|
2583
|
nicholas@2224
|
2584 this.timePerPixel = 0;
|
nicholas@2224
|
2585 this.maxTime = 0;
|
nicholas@2224
|
2586
|
nicholas@2224
|
2587 this.playbackObject;
|
nicholas@2224
|
2588
|
nicholas@2224
|
2589 this.setTimePerPixel = function(audioObject) {
|
nicholas@2224
|
2590 //maxTime must be in seconds
|
nicholas@2224
|
2591 this.playbackObject = audioObject;
|
nicholas@2224
|
2592 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2224
|
2593 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2224
|
2594 this.timePerPixel = this.maxTime/490;
|
nicholas@2224
|
2595 if (this.maxTime < 60) {
|
nicholas@2224
|
2596 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2597 } else {
|
nicholas@2224
|
2598 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2599 }
|
nicholas@2224
|
2600 };
|
nicholas@2224
|
2601
|
nicholas@2224
|
2602 this.update = function() {
|
nicholas@2224
|
2603 // Update the playhead position, startPlay must be called
|
nicholas@2224
|
2604 if (this.timePerPixel > 0) {
|
nicholas@2224
|
2605 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2224
|
2606 if (time > 0 && time < this.maxTime) {
|
nicholas@2224
|
2607 var width = 490;
|
nicholas@2224
|
2608 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@2224
|
2609 this.scrubberHead.style.left = pix+'px';
|
nicholas@2224
|
2610 if (this.maxTime > 60.0) {
|
nicholas@2224
|
2611 var secs = time%60;
|
nicholas@2224
|
2612 var mins = Math.floor((time-secs)/60);
|
nicholas@2224
|
2613 secs = secs.toString();
|
nicholas@2224
|
2614 secs = secs.substr(0,2);
|
nicholas@2224
|
2615 mins = mins.toString();
|
nicholas@2224
|
2616 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@2224
|
2617 } else {
|
nicholas@2224
|
2618 time = time.toString();
|
nicholas@2224
|
2619 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@2224
|
2620 }
|
nicholas@2224
|
2621 } else {
|
nicholas@2224
|
2622 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2623 if (this.maxTime < 60) {
|
nicholas@2224
|
2624 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2625 } else {
|
nicholas@2224
|
2626 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2627 }
|
nicholas@2224
|
2628 }
|
nicholas@2224
|
2629 }
|
nicholas@2224
|
2630 };
|
nicholas@2224
|
2631
|
nicholas@2224
|
2632 this.interval = undefined;
|
nicholas@2224
|
2633
|
nicholas@2224
|
2634 this.start = function() {
|
nicholas@2224
|
2635 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@2224
|
2636 if (this.maxTime < 60) {
|
nicholas@2224
|
2637 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@2224
|
2638 } else {
|
nicholas@2224
|
2639 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@2224
|
2640 }
|
nicholas@2224
|
2641 }
|
nicholas@2224
|
2642 };
|
nicholas@2224
|
2643 this.stop = function() {
|
nicholas@2224
|
2644 clearInterval(this.interval);
|
nicholas@2224
|
2645 this.interval = undefined;
|
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 this.volume = new function()
|
nicholas@2224
|
2656 {
|
nicholas@2224
|
2657 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
2658 // Includes trackers on page-by-page data
|
nicholas@2224
|
2659 // Volume does NOT reset to 0dB on each page load
|
nicholas@2224
|
2660 this.valueLin = 1.0;
|
nicholas@2224
|
2661 this.valueDB = 0.0;
|
nicholas@2352
|
2662 this.root = document.createElement('div');
|
nicholas@2352
|
2663 this.root.id = 'master-volume-root';
|
nicholas@2224
|
2664 this.object = document.createElement('div');
|
nicholas@2352
|
2665 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
2666 this.object.appendChild(this.root);
|
nicholas@2224
|
2667 this.slider = document.createElement('input');
|
nicholas@2224
|
2668 this.slider.id = 'master-volume-control';
|
nicholas@2224
|
2669 this.slider.type = 'range';
|
nicholas@2224
|
2670 this.valueText = document.createElement('span');
|
nicholas@2224
|
2671 this.valueText.id = 'master-volume-feedback';
|
nicholas@2224
|
2672 this.valueText.textContent = '0dB';
|
nicholas@2224
|
2673
|
nicholas@2224
|
2674 this.slider.min = -60;
|
nicholas@2224
|
2675 this.slider.max = 12;
|
nicholas@2224
|
2676 this.slider.value = 0;
|
nicholas@2224
|
2677 this.slider.step = 1;
|
nicholas@2224
|
2678 this.slider.onmousemove = function(event)
|
nicholas@2224
|
2679 {
|
nicholas@2224
|
2680 interfaceContext.volume.valueDB = event.currentTarget.value;
|
nicholas@2224
|
2681 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
nicholas@2224
|
2682 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
nicholas@2224
|
2683 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
nicholas@2224
|
2684 }
|
nicholas@2224
|
2685 this.slider.onmouseup = function(event)
|
nicholas@2224
|
2686 {
|
nicholas@2224
|
2687 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2224
|
2688 if (storePoint.length == 0)
|
nicholas@2224
|
2689 {
|
nicholas@2224
|
2690 storePoint = storage.document.createElement('metricresult');
|
nicholas@2224
|
2691 storePoint.setAttribute('name','volumeTracker');
|
nicholas@2224
|
2692 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2224
|
2693 }
|
nicholas@2224
|
2694 else {
|
nicholas@2224
|
2695 storePoint = storePoint[0];
|
nicholas@2224
|
2696 }
|
nicholas@2224
|
2697 var node = storage.document.createElement('movement');
|
nicholas@2224
|
2698 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
2699 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
nicholas@2224
|
2700 node.setAttribute('format','dBFS');
|
nicholas@2224
|
2701 storePoint.appendChild(node);
|
nicholas@2224
|
2702 }
|
nicholas@2224
|
2703
|
nicholas@2224
|
2704 var title = document.createElement('div');
|
nicholas@2224
|
2705 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
2706 title.style.fontSize = '0.75em';
|
nicholas@2224
|
2707 title.style.width = "100%";
|
nicholas@2224
|
2708 title.align = 'center';
|
nicholas@2352
|
2709 this.root.appendChild(title);
|
nicholas@2224
|
2710
|
nicholas@2352
|
2711 this.root.appendChild(this.slider);
|
nicholas@2352
|
2712 this.root.appendChild(this.valueText);
|
nicholas@2352
|
2713
|
nicholas@2352
|
2714 this.resize = function(event) {
|
nicholas@2352
|
2715 if (window.innerWidth < 1000) {
|
nicholas@2352
|
2716 this.object.className = "master-volume-holder-inline"
|
nicholas@2352
|
2717 } else {
|
nicholas@2352
|
2718 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
2719 }
|
nicholas@2352
|
2720 }
|
nicholas@2224
|
2721 }
|
nicholas@2224
|
2722
|
nicholas@2224
|
2723 this.calibrationModuleObject = null;
|
nicholas@2224
|
2724 this.calibrationModule = function() {
|
nicholas@2224
|
2725 // This creates an on-page calibration module
|
nicholas@2224
|
2726 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
2727 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
2728 // The calibration is a fixed state module
|
nicholas@2224
|
2729 this.calibrationNodes = [];
|
nicholas@2224
|
2730 this.holder = null;
|
nicholas@2224
|
2731 this.build = function(inject) {
|
nicholas@2224
|
2732 var f0 = 62.5;
|
nicholas@2224
|
2733 this.holder = document.createElement("div");
|
nicholas@2224
|
2734 this.holder.className = "calibration-holder";
|
nicholas@2224
|
2735 this.calibrationNodes = [];
|
nicholas@2224
|
2736 while(f0 < 20000) {
|
nicholas@2224
|
2737 var obj = {
|
nicholas@2224
|
2738 root: document.createElement("div"),
|
nicholas@2224
|
2739 input: document.createElement("input"),
|
nicholas@2224
|
2740 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
2741 gain: audioContext.createGain(),
|
nicholas@2224
|
2742 f: f0,
|
nicholas@2224
|
2743 parent: this,
|
nicholas@2224
|
2744 handleEvent: function(event) {
|
nicholas@2224
|
2745 switch(event.type) {
|
nicholas@2224
|
2746 case "mouseenter":
|
nicholas@2224
|
2747 this.oscillator.start(0);
|
nicholas@2224
|
2748 break;
|
nicholas@2224
|
2749 case "mouseleave":
|
nicholas@2224
|
2750 this.oscillator.stop(0);
|
nicholas@2224
|
2751 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
2752 this.oscillator.connect(this.gain);
|
nicholas@2224
|
2753 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
2754 break;
|
nicholas@2224
|
2755 case "mousemove":
|
nicholas@2224
|
2756 var value = Math.pow(10,this.input.value/20);
|
nicholas@2224
|
2757 if (this.f == 1000) {
|
nicholas@2224
|
2758 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
2759 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
2760 } else {
|
nicholas@2224
|
2761 this.gain.gain.value = value
|
nicholas@2224
|
2762 }
|
nicholas@2224
|
2763 break;
|
nicholas@2224
|
2764 }
|
nicholas@2224
|
2765 },
|
nicholas@2224
|
2766 disconnect: function() {
|
nicholas@2224
|
2767 this.gain.disconnect();
|
nicholas@2224
|
2768 }
|
nicholas@2224
|
2769 }
|
nicholas@2224
|
2770 obj.root.className = "calibration-slider";
|
nicholas@2224
|
2771 obj.root.appendChild(obj.input);
|
nicholas@2224
|
2772 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
2773 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
2774 obj.gain.gain.value = Math.random()*2;
|
nicholas@2224
|
2775 obj.input.value = obj.gain.gain.value;
|
nicholas@2224
|
2776 obj.input.setAttribute('orient','vertical');
|
nicholas@2224
|
2777 obj.input.type = "range";
|
nicholas@2224
|
2778 obj.input.min = -6;
|
nicholas@2224
|
2779 obj.input.max = 6;
|
nicholas@2224
|
2780 obj.input.step = 0.25;
|
nicholas@2224
|
2781 if (f0 != 1000) {
|
nicholas@2224
|
2782 obj.input.value = (Math.random()*12)-6;
|
nicholas@2224
|
2783 } else {
|
nicholas@2224
|
2784 obj.input.value = 0;
|
nicholas@2224
|
2785 obj.root.style.backgroundColor="rgb(255,125,125)";
|
nicholas@2224
|
2786 }
|
nicholas@2224
|
2787 obj.input.addEventListener("mousemove",obj);
|
nicholas@2224
|
2788 obj.input.addEventListener("mouseenter",obj);
|
nicholas@2224
|
2789 obj.input.addEventListener("mouseleave",obj);
|
nicholas@2224
|
2790 obj.gain.gain.value = Math.pow(10,obj.input.value/20);
|
nicholas@2224
|
2791 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
2792 this.calibrationNodes.push(obj);
|
nicholas@2224
|
2793 this.holder.appendChild(obj.root);
|
nicholas@2224
|
2794 f0 *= 2;
|
nicholas@2224
|
2795 }
|
nicholas@2224
|
2796 inject.appendChild(this.holder);
|
nicholas@2224
|
2797 }
|
nicholas@2224
|
2798 this.collect = function() {
|
nicholas@2224
|
2799 for (var obj of this.calibrationNodes) {
|
nicholas@2224
|
2800 var node = storage.document.createElement("calibrationresult");
|
nicholas@2224
|
2801 node.setAttribute("frequency",obj.f);
|
nicholas@2224
|
2802 node.setAttribute("range-min",obj.input.min);
|
nicholas@2224
|
2803 node.setAttribute("range-max",obj.input.max);
|
nicholas@2224
|
2804 node.setAttribute("gain-lin",obj.gain.gain.value);
|
nicholas@2224
|
2805 this.storeDOM.appendChild(node);
|
nicholas@2224
|
2806 }
|
nicholas@2224
|
2807 }
|
nicholas@2224
|
2808 }
|
nicholas@2224
|
2809
|
nicholas@2224
|
2810
|
nicholas@2224
|
2811 // Global Checkers
|
nicholas@2224
|
2812 // These functions will help enforce the checkers
|
nicholas@2224
|
2813 this.checkHiddenAnchor = function()
|
nicholas@2224
|
2814 {
|
nicholas@2224
|
2815 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2816 {
|
nicholas@2224
|
2817 if (ao.specification.type == "anchor")
|
nicholas@2224
|
2818 {
|
nicholas@2224
|
2819 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2820 // Anchor is not set below
|
nicholas@2224
|
2821 console.log('Anchor node not below marker value');
|
nicholas@2361
|
2822 interfaceContext.lightbox.post("Message",'Please keep listening');
|
nicholas@2224
|
2823 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2224
|
2824 return false;
|
nicholas@2224
|
2825 }
|
nicholas@2224
|
2826 }
|
nicholas@2224
|
2827 }
|
nicholas@2224
|
2828 return true;
|
nicholas@2224
|
2829 };
|
nicholas@2224
|
2830
|
nicholas@2224
|
2831 this.checkHiddenReference = function()
|
nicholas@2224
|
2832 {
|
nicholas@2224
|
2833 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2834 {
|
nicholas@2224
|
2835 if (ao.specification.type == "reference")
|
nicholas@2224
|
2836 {
|
nicholas@2224
|
2837 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2838 // Anchor is not set below
|
nicholas@2224
|
2839 console.log('Reference node not above marker value');
|
nicholas@2224
|
2840 this.storeErrorNode('Reference node not above marker value');
|
nicholas@2361
|
2841 interfaceContext.lightbox.post("Message",'Please keep listening');
|
nicholas@2224
|
2842 return false;
|
nicholas@2224
|
2843 }
|
nicholas@2224
|
2844 }
|
nicholas@2224
|
2845 }
|
nicholas@2224
|
2846 return true;
|
nicholas@2224
|
2847 };
|
nicholas@2224
|
2848
|
nicholas@2224
|
2849 this.checkFragmentsFullyPlayed = function ()
|
nicholas@2224
|
2850 {
|
nicholas@2224
|
2851 // Checks the entire file has been played back
|
nicholas@2224
|
2852 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2224
|
2853 if (audioEngineContext.loopPlayback)
|
nicholas@2224
|
2854 {
|
nicholas@2224
|
2855 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2224
|
2856 return true;
|
nicholas@2224
|
2857 }
|
nicholas@2224
|
2858 var check_pass = true;
|
nicholas@2224
|
2859 var error_obj = [];
|
nicholas@2224
|
2860 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
nicholas@2224
|
2861 {
|
nicholas@2224
|
2862 var object = audioEngineContext.audioObjects[i];
|
nicholas@2224
|
2863 var time = object.buffer.buffer.duration;
|
nicholas@2224
|
2864 var metric = object.metric;
|
nicholas@2224
|
2865 var passed = false;
|
nicholas@2224
|
2866 for (var j=0; j<metric.listenTracker.length; j++)
|
nicholas@2224
|
2867 {
|
nicholas@2224
|
2868 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
nicholas@2224
|
2869 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2224
|
2870 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2224
|
2871 var delta = stop_time - start_time;
|
nicholas@2224
|
2872 if (delta >= time)
|
nicholas@2224
|
2873 {
|
nicholas@2224
|
2874 passed = true;
|
nicholas@2224
|
2875 break;
|
nicholas@2224
|
2876 }
|
nicholas@2224
|
2877 }
|
nicholas@2224
|
2878 if (passed == false)
|
nicholas@2224
|
2879 {
|
nicholas@2224
|
2880 check_pass = false;
|
b@2258
|
2881 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2882 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2883 }
|
nicholas@2224
|
2884 }
|
nicholas@2224
|
2885 if (check_pass == false)
|
nicholas@2224
|
2886 {
|
nicholas@2224
|
2887 var str_start = "You have not completely listened to fragments ";
|
nicholas@2224
|
2888 for (var i=0; i<error_obj.length; i++)
|
nicholas@2224
|
2889 {
|
nicholas@2224
|
2890 str_start += error_obj[i];
|
nicholas@2224
|
2891 if (i != error_obj.length-1)
|
nicholas@2224
|
2892 {
|
nicholas@2224
|
2893 str_start += ', ';
|
nicholas@2224
|
2894 }
|
nicholas@2224
|
2895 }
|
nicholas@2224
|
2896 str_start += ". Please keep listening";
|
nicholas@2224
|
2897 console.log("[ALERT]: "+str_start);
|
nicholas@2224
|
2898 this.storeErrorNode("[ALERT]: "+str_start);
|
nicholas@2361
|
2899 interfaceContext.lightbox.post("Error",str_start);
|
nicholas@2224
|
2900 }
|
nicholas@2224
|
2901 };
|
nicholas@2224
|
2902 this.checkAllMoved = function()
|
nicholas@2224
|
2903 {
|
nicholas@2224
|
2904 var str = "You have not moved ";
|
nicholas@2224
|
2905 var failed = [];
|
nicholas@2224
|
2906 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2907 {
|
nicholas@2224
|
2908 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@2224
|
2909 {
|
b@2258
|
2910 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2911 }
|
nicholas@2224
|
2912 }
|
nicholas@2224
|
2913 if (failed.length == 0)
|
nicholas@2224
|
2914 {
|
nicholas@2224
|
2915 return true;
|
nicholas@2224
|
2916 } else if (failed.length == 1)
|
nicholas@2224
|
2917 {
|
nicholas@2224
|
2918 str += 'track '+failed[0];
|
nicholas@2224
|
2919 } else {
|
nicholas@2224
|
2920 str += 'tracks ';
|
nicholas@2224
|
2921 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2922 {
|
nicholas@2224
|
2923 str += failed[i]+', ';
|
nicholas@2224
|
2924 }
|
nicholas@2224
|
2925 str += 'and '+failed[i];
|
nicholas@2224
|
2926 }
|
nicholas@2224
|
2927 str +='.';
|
nicholas@2361
|
2928 interfaceContext.lightbox.post("Error",str);
|
nicholas@2224
|
2929 console.log(str);
|
nicholas@2224
|
2930 this.storeErrorNode(str);
|
nicholas@2224
|
2931 return false;
|
nicholas@2224
|
2932 };
|
nicholas@2224
|
2933 this.checkAllPlayed = function()
|
nicholas@2224
|
2934 {
|
nicholas@2224
|
2935 var str = "You have not played ";
|
nicholas@2224
|
2936 var failed = [];
|
nicholas@2224
|
2937 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2938 {
|
nicholas@2224
|
2939 if(ao.metric.wasListenedTo == false)
|
nicholas@2224
|
2940 {
|
b@2258
|
2941 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2942 }
|
nicholas@2224
|
2943 }
|
nicholas@2224
|
2944 if (failed.length == 0)
|
nicholas@2224
|
2945 {
|
nicholas@2224
|
2946 return true;
|
nicholas@2224
|
2947 } else if (failed.length == 1)
|
nicholas@2224
|
2948 {
|
nicholas@2224
|
2949 str += 'track '+failed[0];
|
nicholas@2224
|
2950 } else {
|
nicholas@2224
|
2951 str += 'tracks ';
|
nicholas@2224
|
2952 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2953 {
|
nicholas@2224
|
2954 str += failed[i]+', ';
|
nicholas@2224
|
2955 }
|
nicholas@2224
|
2956 str += 'and '+failed[i];
|
nicholas@2224
|
2957 }
|
nicholas@2224
|
2958 str +='.';
|
nicholas@2361
|
2959 interfaceContext.lightbox.post("Error",str);
|
nicholas@2224
|
2960 console.log(str);
|
nicholas@2224
|
2961 this.storeErrorNode(str);
|
nicholas@2224
|
2962 return false;
|
nicholas@2224
|
2963 };
|
nicholas@2310
|
2964 this.checkScaleRange = function(min, max) {
|
nicholas@2310
|
2965 var page = testState.getCurrentTestPage();
|
nicholas@2310
|
2966 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2310
|
2967 var state = true;
|
nicholas@2310
|
2968 var str = "Please keep listening. ";
|
nicholas@2310
|
2969 var minRanking = Infinity;
|
nicholas@2310
|
2970 var maxRanking = -Infinity;
|
nicholas@2310
|
2971 for (var ao of audioObjects) {
|
nicholas@2310
|
2972 var rank = ao.interfaceDOM.getValue();
|
nicholas@2310
|
2973 if (rank < minRanking) {minRanking = rank;}
|
nicholas@2310
|
2974 if (rank > maxRanking) {maxRanking = rank;}
|
nicholas@2310
|
2975 }
|
nicholas@2310
|
2976 if (minRanking*100 > min) {
|
nicholas@2310
|
2977 str += "At least one fragment must be below the "+min+" mark.";
|
nicholas@2310
|
2978 state = false;
|
nicholas@2310
|
2979 }
|
nicholas@2358
|
2980 if (maxRanking*100 < max) {
|
nicholas@2358
|
2981 str += "At least one fragment must be above the "+max+" mark."
|
nicholas@2310
|
2982 state = false;
|
nicholas@2310
|
2983 }
|
nicholas@2310
|
2984 if (!state) {
|
nicholas@2310
|
2985 console.log(str);
|
nicholas@2310
|
2986 this.storeErrorNode(str);
|
nicholas@2361
|
2987 interfaceContext.lightbox.post("Error",str);
|
nicholas@2310
|
2988 }
|
nicholas@2310
|
2989 return state;
|
nicholas@2310
|
2990 }
|
nicholas@2358
|
2991
|
nicholas@2224
|
2992 this.storeErrorNode = function(errorMessage)
|
nicholas@2224
|
2993 {
|
nicholas@2224
|
2994 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
2995 var node = storage.document.createElement('error');
|
nicholas@2224
|
2996 node.setAttribute('time',time);
|
nicholas@2224
|
2997 node.textContent = errorMessage;
|
nicholas@2224
|
2998 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
2999 };
|
nicholas@2224
|
3000 }
|
nicholas@2224
|
3001
|
nicholas@2224
|
3002 function Storage()
|
nicholas@2224
|
3003 {
|
nicholas@2224
|
3004 // Holds results in XML format until ready for collection
|
nicholas@2224
|
3005 this.globalPreTest = null;
|
nicholas@2224
|
3006 this.globalPostTest = null;
|
nicholas@2224
|
3007 this.testPages = [];
|
nicholas@2224
|
3008 this.document = null;
|
nicholas@2224
|
3009 this.root = null;
|
nicholas@2224
|
3010 this.state = 0;
|
nicholas@2224
|
3011
|
nicholas@2224
|
3012 this.initialise = function(existingStore)
|
nicholas@2224
|
3013 {
|
nicholas@2224
|
3014 if (existingStore == undefined) {
|
nicholas@2224
|
3015 // We need to get the sessionKey
|
nicholas@2224
|
3016 this.SessionKey.generateKey();
|
nicholas@2376
|
3017 this.document = document.implementation.createDocument(null,"waetresult",null);
|
nicholas@2224
|
3018 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3019 var projectDocument = specification.projectXML;
|
nicholas@2224
|
3020 projectDocument.setAttribute('file-name',url);
|
nicholas@2224
|
3021 projectDocument.setAttribute('url',qualifyURL(url));
|
nicholas@2224
|
3022 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3023 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3024 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3025 } else {
|
nicholas@2224
|
3026 this.document = existingStore;
|
nicholas@2294
|
3027 this.root = existingStore.firstChild;
|
nicholas@2224
|
3028 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3029 }
|
nicholas@2224
|
3030 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
nicholas@2224
|
3031 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
nicholas@2224
|
3032 };
|
nicholas@2224
|
3033
|
nicholas@2224
|
3034 this.SessionKey = {
|
nicholas@2224
|
3035 key: null,
|
nicholas@2224
|
3036 request: new XMLHttpRequest(),
|
nicholas@2224
|
3037 parent: this,
|
nicholas@2224
|
3038 handleEvent: function() {
|
nicholas@2224
|
3039 var parse = new DOMParser();
|
nicholas@2224
|
3040 var xml = parse.parseFromString(this.request.response,"text/xml");
|
giuliomoro@2301
|
3041 var shouldGenerateKey = true;
|
nicholas@2376
|
3042 if (this.request.response.length == 0) {
|
nicholas@2376
|
3043 console.log("Error: Server did not respond");
|
nicholas@2376
|
3044 return;
|
nicholas@2376
|
3045 }
|
nicholas@2376
|
3046 if(xml.getElementsByTagName("state").length > 0){
|
nicholas@2376
|
3047 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
giuliomoro@2301
|
3048 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
giuliomoro@2301
|
3049 this.parent.root.setAttribute("key",this.key);
|
giuliomoro@2301
|
3050 this.parent.root.setAttribute("state","empty");
|
giuliomoro@2301
|
3051 shouldGenerateKey = false;
|
giuliomoro@2301
|
3052 }
|
giuliomoro@2301
|
3053 }
|
giuliomoro@2301
|
3054 if(shouldGenerateKey === true){
|
giuliomoro@2301
|
3055 this.generateKey();
|
giuliomoro@2301
|
3056 }
|
nicholas@2224
|
3057 },
|
nicholas@2224
|
3058 generateKey: function() {
|
nicholas@2224
|
3059 var temp_key = randomString(32);
|
nicholas@2302
|
3060 var returnURL = "";
|
nicholas@2302
|
3061 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
3062 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
3063 returnURL = specification.projectReturn;
|
nicholas@2302
|
3064 }
|
nicholas@2302
|
3065 }
|
nicholas@2302
|
3066 this.request.open("GET",returnURL+"php/keygen.php?key="+temp_key,true);
|
nicholas@2224
|
3067 this.request.addEventListener("load",this);
|
nicholas@2224
|
3068 this.request.send();
|
nicholas@2224
|
3069 },
|
nicholas@2224
|
3070 update: function() {
|
nicholas@2357
|
3071 if (this.key == null) {
|
nicholas@2357
|
3072 console.log("Cannot save as key == null");
|
nicholas@2357
|
3073 return;
|
nicholas@2357
|
3074 }
|
nicholas@2224
|
3075 this.parent.root.setAttribute("state","update");
|
nicholas@2224
|
3076 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3077 var returnURL = "";
|
nicholas@2302
|
3078 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
3079 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
3080 returnURL = specification.projectReturn;
|
nicholas@2302
|
3081 }
|
nicholas@2302
|
3082 }
|
nicholas@2302
|
3083 xmlhttp.open("POST",returnURL+"php/save.php?key="+this.key);
|
nicholas@2224
|
3084 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2224
|
3085 xmlhttp.onerror = function(){
|
nicholas@2224
|
3086 console.log('Error updating file to server!');
|
nicholas@2224
|
3087 };
|
nicholas@2224
|
3088 var hold = document.createElement("div");
|
nicholas@2224
|
3089 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3090 hold.appendChild(clone);
|
nicholas@2224
|
3091 xmlhttp.onload = function() {
|
nicholas@2224
|
3092 if (this.status >= 300) {
|
nicholas@2224
|
3093 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3094 } else {
|
nicholas@2224
|
3095 var parser = new DOMParser();
|
nicholas@2224
|
3096 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3097 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3098 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3099 var file = response.getElementsByTagName("file")[0];
|
nicholas@2224
|
3100 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
|
nicholas@2224
|
3101 } else {
|
nicholas@2224
|
3102 var message = response.getElementsByTagName("message");
|
nicholas@2224
|
3103 console.log("Intermediate save: Error! "+message.textContent);
|
nicholas@2224
|
3104 }
|
nicholas@2224
|
3105 }
|
nicholas@2224
|
3106 }
|
nicholas@2224
|
3107 xmlhttp.send([hold.innerHTML]);
|
nicholas@2224
|
3108 }
|
nicholas@2224
|
3109 }
|
nicholas@2224
|
3110
|
nicholas@2224
|
3111 this.createTestPageStore = function(specification)
|
nicholas@2224
|
3112 {
|
nicholas@2224
|
3113 var store = new this.pageNode(this,specification);
|
nicholas@2224
|
3114 this.testPages.push(store);
|
nicholas@2224
|
3115 return this.testPages[this.testPages.length-1];
|
nicholas@2224
|
3116 };
|
nicholas@2224
|
3117
|
nicholas@2224
|
3118 this.surveyNode = function(parent,root,specification)
|
nicholas@2224
|
3119 {
|
nicholas@2224
|
3120 this.specification = specification;
|
nicholas@2224
|
3121 this.parent = parent;
|
nicholas@2224
|
3122 this.state = "empty";
|
nicholas@2224
|
3123 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2224
|
3124 this.XMLDOM.setAttribute('location',this.specification.location);
|
nicholas@2224
|
3125 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3126 for (var optNode of this.specification.options)
|
nicholas@2224
|
3127 {
|
nicholas@2224
|
3128 if (optNode.type != 'statement')
|
nicholas@2224
|
3129 {
|
nicholas@2224
|
3130 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2224
|
3131 node.setAttribute("ref",optNode.id);
|
nicholas@2224
|
3132 node.setAttribute('type',optNode.type);
|
nicholas@2224
|
3133 this.XMLDOM.appendChild(node);
|
nicholas@2224
|
3134 }
|
nicholas@2224
|
3135 }
|
nicholas@2224
|
3136 root.appendChild(this.XMLDOM);
|
nicholas@2224
|
3137
|
nicholas@2224
|
3138 this.postResult = function(node)
|
nicholas@2224
|
3139 {
|
nicholas@2224
|
3140 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2224
|
3141 // ID is the position
|
nicholas@2224
|
3142 if (node.specification.type == 'statement'){return;}
|
nicholas@2294
|
3143 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2224
|
3144 while(surveyresult != null) {
|
nicholas@2224
|
3145 if (surveyresult.getAttribute("ref") == node.specification.id)
|
nicholas@2224
|
3146 {
|
nicholas@2224
|
3147 break;
|
nicholas@2224
|
3148 }
|
nicholas@2224
|
3149 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3150 }
|
nicholas@2224
|
3151 switch(node.specification.type)
|
nicholas@2224
|
3152 {
|
nicholas@2224
|
3153 case "number":
|
nicholas@2224
|
3154 case "question":
|
nicholas@2224
|
3155 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
3156 child.textContent = node.response;
|
nicholas@2224
|
3157 surveyresult.appendChild(child);
|
nicholas@2224
|
3158 break;
|
nicholas@2224
|
3159 case "radio":
|
nicholas@2224
|
3160 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
3161 child.setAttribute('name',node.response.name);
|
nicholas@2224
|
3162 child.textContent = node.response.text;
|
nicholas@2224
|
3163 surveyresult.appendChild(child);
|
nicholas@2224
|
3164 break;
|
nicholas@2224
|
3165 case "checkbox":
|
nicholas@2224
|
3166 for (var i=0; i<node.response.length; i++)
|
nicholas@2224
|
3167 {
|
nicholas@2224
|
3168 var checkNode = this.parent.document.createElement('response');
|
nicholas@2224
|
3169 checkNode.setAttribute('name',node.response[i].name);
|
nicholas@2224
|
3170 checkNode.setAttribute('checked',node.response[i].checked);
|
nicholas@2224
|
3171 surveyresult.appendChild(checkNode);
|
nicholas@2224
|
3172 }
|
nicholas@2224
|
3173 break;
|
nicholas@2224
|
3174 }
|
nicholas@2224
|
3175 };
|
nicholas@2224
|
3176 this.complete = function() {
|
nicholas@2224
|
3177 this.state = "complete";
|
nicholas@2224
|
3178 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3179 }
|
nicholas@2224
|
3180 };
|
nicholas@2224
|
3181
|
nicholas@2224
|
3182 this.pageNode = function(parent,specification)
|
nicholas@2224
|
3183 {
|
nicholas@2224
|
3184 // Create one store per test page
|
nicholas@2224
|
3185 this.specification = specification;
|
nicholas@2224
|
3186 this.parent = parent;
|
nicholas@2224
|
3187 this.state = "empty";
|
nicholas@2224
|
3188 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2224
|
3189 this.XMLDOM.setAttribute('ref',specification.id);
|
nicholas@2224
|
3190 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
nicholas@2224
|
3191 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3192 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
nicholas@2224
|
3193 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
nicholas@2224
|
3194
|
nicholas@2224
|
3195 // Add any page metrics
|
nicholas@2224
|
3196 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3197 this.XMLDOM.appendChild(page_metric);
|
nicholas@2224
|
3198
|
nicholas@2224
|
3199 // Add the audioelement
|
nicholas@2224
|
3200 for (var element of this.specification.audioElements)
|
nicholas@2224
|
3201 {
|
nicholas@2224
|
3202 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2224
|
3203 aeNode.setAttribute('ref',element.id);
|
nicholas@2224
|
3204 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
|
nicholas@2224
|
3205 aeNode.setAttribute('type',element.type);
|
nicholas@2224
|
3206 aeNode.setAttribute('url', element.url);
|
nicholas@2224
|
3207 aeNode.setAttribute('fqurl',qualifyURL(element.url));
|
nicholas@2224
|
3208 aeNode.setAttribute('gain', element.gain);
|
nicholas@2224
|
3209 if (element.type == 'anchor' || element.type == 'reference')
|
nicholas@2224
|
3210 {
|
nicholas@2224
|
3211 if (element.marker > 0)
|
nicholas@2224
|
3212 {
|
nicholas@2224
|
3213 aeNode.setAttribute('marker',element.marker);
|
nicholas@2224
|
3214 }
|
nicholas@2224
|
3215 }
|
nicholas@2224
|
3216 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3217 aeNode.appendChild(ae_metric);
|
nicholas@2224
|
3218 this.XMLDOM.appendChild(aeNode);
|
nicholas@2224
|
3219 }
|
nicholas@2224
|
3220
|
nicholas@2224
|
3221 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2224
|
3222
|
nicholas@2224
|
3223 this.complete = function() {
|
nicholas@2224
|
3224 this.state = "complete";
|
nicholas@2224
|
3225 this.XMLDOM.setAttribute("state","complete");
|
nicholas@2224
|
3226 }
|
nicholas@2224
|
3227 };
|
nicholas@2224
|
3228 this.update = function() {
|
nicholas@2224
|
3229 this.SessionKey.update();
|
nicholas@2224
|
3230 }
|
nicholas@2224
|
3231 this.finish = function()
|
nicholas@2224
|
3232 {
|
nicholas@2224
|
3233 if (this.state == 0)
|
nicholas@2224
|
3234 {
|
nicholas@2224
|
3235 this.update();
|
nicholas@2224
|
3236 }
|
nicholas@2224
|
3237 this.state = 1;
|
nicholas@2224
|
3238 return this.root;
|
nicholas@2224
|
3239 };
|
nicholas@2224
|
3240 }
|
nicholas@2384
|
3241
|
nicholas@2401
|
3242 var window_depedancy_callback;
|
nicholas@2401
|
3243 window_depedancy_callback = window.setInterval(function(){
|
nicholas@2401
|
3244 if (check_dependancies()) {
|
nicholas@2401
|
3245 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
3246 onload();
|
nicholas@2401
|
3247 } else {
|
nicholas@2401
|
3248 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
3249 }
|
nicholas@2401
|
3250 },100); |