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