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