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