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@2224
|
119 window.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@2224
|
1679 this.bufferNode.start(startTime);
|
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.id = 'outside-reference';
|
nicholas@2224
|
2489 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2224
|
2490 this.outsideReferenceHolder.setAttribute('track-id',index);
|
nicholas@2224
|
2491 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2492 this.outsideReferenceHolder.disabled = true;
|
nicholas@2224
|
2493
|
nicholas@2224
|
2494 this.outsideReferenceHolder.onclick = function(event)
|
nicholas@2224
|
2495 {
|
nicholas@2224
|
2496 audioEngineContext.play(event.currentTarget.getAttribute('track-id'));
|
nicholas@2224
|
2497 };
|
nicholas@2224
|
2498 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2224
|
2499 this.enable = function()
|
nicholas@2224
|
2500 {
|
nicholas@2224
|
2501 if (this.parent.state == 1)
|
nicholas@2224
|
2502 {
|
nicholas@2224
|
2503 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2504 }
|
nicholas@2224
|
2505 };
|
nicholas@2224
|
2506 this.updateLoading = function(progress)
|
nicholas@2224
|
2507 {
|
nicholas@2224
|
2508 if (progress != 100)
|
nicholas@2224
|
2509 {
|
nicholas@2224
|
2510 progress = String(progress);
|
nicholas@2224
|
2511 progress = progress.split('.')[0];
|
nicholas@2224
|
2512 this.outsideReferenceHolder.textContent = progress+'%';
|
nicholas@2224
|
2513 } else {
|
nicholas@2224
|
2514 this.outsideReferenceHolder.textContent = "Play Reference";
|
nicholas@2224
|
2515 }
|
nicholas@2224
|
2516 };
|
nicholas@2224
|
2517 this.startPlayback = function()
|
nicholas@2224
|
2518 {
|
nicholas@2224
|
2519 // Called when playback has begun
|
nicholas@2224
|
2520 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2521 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2522 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2523 };
|
nicholas@2224
|
2524 this.stopPlayback = function()
|
nicholas@2224
|
2525 {
|
nicholas@2224
|
2526 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2527 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2528 };
|
nicholas@2224
|
2529 this.exportXMLDOM = function(audioObject)
|
nicholas@2224
|
2530 {
|
nicholas@2224
|
2531 return null;
|
nicholas@2224
|
2532 };
|
nicholas@2224
|
2533 this.getValue = function()
|
nicholas@2224
|
2534 {
|
nicholas@2224
|
2535 return 0;
|
nicholas@2224
|
2536 };
|
nicholas@2224
|
2537 this.getPresentedId = function()
|
nicholas@2224
|
2538 {
|
nicholas@2224
|
2539 return 'Reference';
|
nicholas@2224
|
2540 };
|
nicholas@2224
|
2541 this.canMove = function()
|
nicholas@2224
|
2542 {
|
nicholas@2224
|
2543 return false;
|
nicholas@2224
|
2544 };
|
nicholas@2224
|
2545 this.error = function() {
|
nicholas@2224
|
2546 // audioObject has an error!!
|
nicholas@2224
|
2547 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2548 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2224
|
2549 }
|
nicholas@2224
|
2550 }
|
nicholas@2224
|
2551
|
nicholas@2224
|
2552 this.playhead = new function()
|
nicholas@2224
|
2553 {
|
nicholas@2224
|
2554 this.object = document.createElement('div');
|
nicholas@2224
|
2555 this.object.className = 'playhead';
|
nicholas@2224
|
2556 this.object.align = 'left';
|
nicholas@2224
|
2557 var curTime = document.createElement('div');
|
nicholas@2224
|
2558 curTime.style.width = '50px';
|
nicholas@2224
|
2559 this.curTimeSpan = document.createElement('span');
|
nicholas@2224
|
2560 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2561 curTime.appendChild(this.curTimeSpan);
|
nicholas@2224
|
2562 this.object.appendChild(curTime);
|
nicholas@2224
|
2563 this.scrubberTrack = document.createElement('div');
|
nicholas@2224
|
2564 this.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2224
|
2565
|
nicholas@2224
|
2566 this.scrubberHead = document.createElement('div');
|
nicholas@2224
|
2567 this.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2224
|
2568 this.scrubberTrack.appendChild(this.scrubberHead);
|
nicholas@2224
|
2569 this.object.appendChild(this.scrubberTrack);
|
nicholas@2224
|
2570
|
nicholas@2224
|
2571 this.timePerPixel = 0;
|
nicholas@2224
|
2572 this.maxTime = 0;
|
nicholas@2224
|
2573
|
nicholas@2224
|
2574 this.playbackObject;
|
nicholas@2224
|
2575
|
nicholas@2224
|
2576 this.setTimePerPixel = function(audioObject) {
|
nicholas@2224
|
2577 //maxTime must be in seconds
|
nicholas@2224
|
2578 this.playbackObject = audioObject;
|
nicholas@2224
|
2579 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2224
|
2580 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2224
|
2581 this.timePerPixel = this.maxTime/490;
|
nicholas@2224
|
2582 if (this.maxTime < 60) {
|
nicholas@2224
|
2583 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2584 } else {
|
nicholas@2224
|
2585 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2586 }
|
nicholas@2224
|
2587 };
|
nicholas@2224
|
2588
|
nicholas@2224
|
2589 this.update = function() {
|
nicholas@2224
|
2590 // Update the playhead position, startPlay must be called
|
nicholas@2224
|
2591 if (this.timePerPixel > 0) {
|
nicholas@2224
|
2592 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2224
|
2593 if (time > 0 && time < this.maxTime) {
|
nicholas@2224
|
2594 var width = 490;
|
nicholas@2224
|
2595 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@2224
|
2596 this.scrubberHead.style.left = pix+'px';
|
nicholas@2224
|
2597 if (this.maxTime > 60.0) {
|
nicholas@2224
|
2598 var secs = time%60;
|
nicholas@2224
|
2599 var mins = Math.floor((time-secs)/60);
|
nicholas@2224
|
2600 secs = secs.toString();
|
nicholas@2224
|
2601 secs = secs.substr(0,2);
|
nicholas@2224
|
2602 mins = mins.toString();
|
nicholas@2224
|
2603 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@2224
|
2604 } else {
|
nicholas@2224
|
2605 time = time.toString();
|
nicholas@2224
|
2606 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@2224
|
2607 }
|
nicholas@2224
|
2608 } else {
|
nicholas@2224
|
2609 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2610 if (this.maxTime < 60) {
|
nicholas@2224
|
2611 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2612 } else {
|
nicholas@2224
|
2613 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2614 }
|
nicholas@2224
|
2615 }
|
nicholas@2224
|
2616 }
|
nicholas@2224
|
2617 };
|
nicholas@2224
|
2618
|
nicholas@2224
|
2619 this.interval = undefined;
|
nicholas@2224
|
2620
|
nicholas@2224
|
2621 this.start = function() {
|
nicholas@2224
|
2622 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@2224
|
2623 if (this.maxTime < 60) {
|
nicholas@2224
|
2624 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@2224
|
2625 } else {
|
nicholas@2224
|
2626 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@2224
|
2627 }
|
nicholas@2224
|
2628 }
|
nicholas@2224
|
2629 };
|
nicholas@2224
|
2630 this.stop = function() {
|
nicholas@2224
|
2631 clearInterval(this.interval);
|
nicholas@2224
|
2632 this.interval = undefined;
|
nicholas@2224
|
2633 this.scrubberHead.style.left = '0px';
|
nicholas@2224
|
2634 if (this.maxTime < 60) {
|
nicholas@2224
|
2635 this.curTimeSpan.textContent = '0.00';
|
nicholas@2224
|
2636 } else {
|
nicholas@2224
|
2637 this.curTimeSpan.textContent = '00:00';
|
nicholas@2224
|
2638 }
|
nicholas@2224
|
2639 };
|
nicholas@2224
|
2640 };
|
nicholas@2224
|
2641
|
nicholas@2224
|
2642 this.volume = new function()
|
nicholas@2224
|
2643 {
|
nicholas@2224
|
2644 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
2645 // Includes trackers on page-by-page data
|
nicholas@2224
|
2646 // Volume does NOT reset to 0dB on each page load
|
nicholas@2224
|
2647 this.valueLin = 1.0;
|
nicholas@2224
|
2648 this.valueDB = 0.0;
|
nicholas@2352
|
2649 this.root = document.createElement('div');
|
nicholas@2352
|
2650 this.root.id = 'master-volume-root';
|
nicholas@2224
|
2651 this.object = document.createElement('div');
|
nicholas@2352
|
2652 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
2653 this.object.appendChild(this.root);
|
nicholas@2224
|
2654 this.slider = document.createElement('input');
|
nicholas@2224
|
2655 this.slider.id = 'master-volume-control';
|
nicholas@2224
|
2656 this.slider.type = 'range';
|
nicholas@2224
|
2657 this.valueText = document.createElement('span');
|
nicholas@2224
|
2658 this.valueText.id = 'master-volume-feedback';
|
nicholas@2224
|
2659 this.valueText.textContent = '0dB';
|
nicholas@2224
|
2660
|
nicholas@2224
|
2661 this.slider.min = -60;
|
nicholas@2224
|
2662 this.slider.max = 12;
|
nicholas@2224
|
2663 this.slider.value = 0;
|
nicholas@2224
|
2664 this.slider.step = 1;
|
nicholas@2224
|
2665 this.slider.onmousemove = function(event)
|
nicholas@2224
|
2666 {
|
nicholas@2224
|
2667 interfaceContext.volume.valueDB = event.currentTarget.value;
|
nicholas@2224
|
2668 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
nicholas@2224
|
2669 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
nicholas@2224
|
2670 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
nicholas@2224
|
2671 }
|
nicholas@2224
|
2672 this.slider.onmouseup = function(event)
|
nicholas@2224
|
2673 {
|
nicholas@2224
|
2674 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2224
|
2675 if (storePoint.length == 0)
|
nicholas@2224
|
2676 {
|
nicholas@2224
|
2677 storePoint = storage.document.createElement('metricresult');
|
nicholas@2224
|
2678 storePoint.setAttribute('name','volumeTracker');
|
nicholas@2224
|
2679 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2224
|
2680 }
|
nicholas@2224
|
2681 else {
|
nicholas@2224
|
2682 storePoint = storePoint[0];
|
nicholas@2224
|
2683 }
|
nicholas@2224
|
2684 var node = storage.document.createElement('movement');
|
nicholas@2224
|
2685 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
nicholas@2224
|
2686 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
nicholas@2224
|
2687 node.setAttribute('format','dBFS');
|
nicholas@2224
|
2688 storePoint.appendChild(node);
|
nicholas@2224
|
2689 }
|
nicholas@2224
|
2690
|
nicholas@2224
|
2691 var title = document.createElement('div');
|
nicholas@2224
|
2692 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
2693 title.style.fontSize = '0.75em';
|
nicholas@2224
|
2694 title.style.width = "100%";
|
nicholas@2224
|
2695 title.align = 'center';
|
nicholas@2352
|
2696 this.root.appendChild(title);
|
nicholas@2224
|
2697
|
nicholas@2352
|
2698 this.root.appendChild(this.slider);
|
nicholas@2352
|
2699 this.root.appendChild(this.valueText);
|
nicholas@2352
|
2700
|
nicholas@2352
|
2701 this.resize = function(event) {
|
nicholas@2352
|
2702 if (window.innerWidth < 1000) {
|
nicholas@2352
|
2703 this.object.className = "master-volume-holder-inline"
|
nicholas@2352
|
2704 } else {
|
nicholas@2352
|
2705 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
2706 }
|
nicholas@2352
|
2707 }
|
nicholas@2224
|
2708 }
|
nicholas@2224
|
2709
|
nicholas@2224
|
2710 this.calibrationModuleObject = null;
|
nicholas@2224
|
2711 this.calibrationModule = function() {
|
nicholas@2224
|
2712 // This creates an on-page calibration module
|
nicholas@2224
|
2713 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
2714 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
2715 // The calibration is a fixed state module
|
nicholas@2224
|
2716 this.calibrationNodes = [];
|
nicholas@2224
|
2717 this.holder = null;
|
nicholas@2224
|
2718 this.build = function(inject) {
|
nicholas@2224
|
2719 var f0 = 62.5;
|
nicholas@2224
|
2720 this.holder = document.createElement("div");
|
nicholas@2224
|
2721 this.holder.className = "calibration-holder";
|
nicholas@2224
|
2722 this.calibrationNodes = [];
|
nicholas@2224
|
2723 while(f0 < 20000) {
|
nicholas@2224
|
2724 var obj = {
|
nicholas@2224
|
2725 root: document.createElement("div"),
|
nicholas@2224
|
2726 input: document.createElement("input"),
|
nicholas@2224
|
2727 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
2728 gain: audioContext.createGain(),
|
nicholas@2224
|
2729 f: f0,
|
nicholas@2224
|
2730 parent: this,
|
nicholas@2224
|
2731 handleEvent: function(event) {
|
nicholas@2224
|
2732 switch(event.type) {
|
nicholas@2224
|
2733 case "mouseenter":
|
nicholas@2224
|
2734 this.oscillator.start(0);
|
nicholas@2224
|
2735 break;
|
nicholas@2224
|
2736 case "mouseleave":
|
nicholas@2224
|
2737 this.oscillator.stop(0);
|
nicholas@2224
|
2738 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
2739 this.oscillator.connect(this.gain);
|
nicholas@2224
|
2740 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
2741 break;
|
nicholas@2224
|
2742 case "mousemove":
|
nicholas@2224
|
2743 var value = Math.pow(10,this.input.value/20);
|
nicholas@2224
|
2744 if (this.f == 1000) {
|
nicholas@2224
|
2745 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
2746 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
2747 } else {
|
nicholas@2224
|
2748 this.gain.gain.value = value
|
nicholas@2224
|
2749 }
|
nicholas@2224
|
2750 break;
|
nicholas@2224
|
2751 }
|
nicholas@2224
|
2752 },
|
nicholas@2224
|
2753 disconnect: function() {
|
nicholas@2224
|
2754 this.gain.disconnect();
|
nicholas@2224
|
2755 }
|
nicholas@2224
|
2756 }
|
nicholas@2224
|
2757 obj.root.className = "calibration-slider";
|
nicholas@2224
|
2758 obj.root.appendChild(obj.input);
|
nicholas@2224
|
2759 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
2760 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2224
|
2761 obj.gain.gain.value = Math.random()*2;
|
nicholas@2224
|
2762 obj.input.value = obj.gain.gain.value;
|
nicholas@2224
|
2763 obj.input.setAttribute('orient','vertical');
|
nicholas@2224
|
2764 obj.input.type = "range";
|
nicholas@2224
|
2765 obj.input.min = -6;
|
nicholas@2224
|
2766 obj.input.max = 6;
|
nicholas@2224
|
2767 obj.input.step = 0.25;
|
nicholas@2224
|
2768 if (f0 != 1000) {
|
nicholas@2224
|
2769 obj.input.value = (Math.random()*12)-6;
|
nicholas@2224
|
2770 } else {
|
nicholas@2224
|
2771 obj.input.value = 0;
|
nicholas@2224
|
2772 obj.root.style.backgroundColor="rgb(255,125,125)";
|
nicholas@2224
|
2773 }
|
nicholas@2224
|
2774 obj.input.addEventListener("mousemove",obj);
|
nicholas@2224
|
2775 obj.input.addEventListener("mouseenter",obj);
|
nicholas@2224
|
2776 obj.input.addEventListener("mouseleave",obj);
|
nicholas@2224
|
2777 obj.gain.gain.value = Math.pow(10,obj.input.value/20);
|
nicholas@2224
|
2778 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
2779 this.calibrationNodes.push(obj);
|
nicholas@2224
|
2780 this.holder.appendChild(obj.root);
|
nicholas@2224
|
2781 f0 *= 2;
|
nicholas@2224
|
2782 }
|
nicholas@2224
|
2783 inject.appendChild(this.holder);
|
nicholas@2224
|
2784 }
|
nicholas@2224
|
2785 this.collect = function() {
|
nicholas@2224
|
2786 for (var obj of this.calibrationNodes) {
|
nicholas@2224
|
2787 var node = storage.document.createElement("calibrationresult");
|
nicholas@2224
|
2788 node.setAttribute("frequency",obj.f);
|
nicholas@2224
|
2789 node.setAttribute("range-min",obj.input.min);
|
nicholas@2224
|
2790 node.setAttribute("range-max",obj.input.max);
|
nicholas@2224
|
2791 node.setAttribute("gain-lin",obj.gain.gain.value);
|
nicholas@2224
|
2792 this.storeDOM.appendChild(node);
|
nicholas@2224
|
2793 }
|
nicholas@2224
|
2794 }
|
nicholas@2224
|
2795 }
|
nicholas@2224
|
2796
|
nicholas@2224
|
2797
|
nicholas@2224
|
2798 // Global Checkers
|
nicholas@2224
|
2799 // These functions will help enforce the checkers
|
nicholas@2224
|
2800 this.checkHiddenAnchor = function()
|
nicholas@2224
|
2801 {
|
nicholas@2224
|
2802 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2803 {
|
nicholas@2224
|
2804 if (ao.specification.type == "anchor")
|
nicholas@2224
|
2805 {
|
nicholas@2224
|
2806 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2807 // Anchor is not set below
|
nicholas@2224
|
2808 console.log('Anchor node not below marker value');
|
nicholas@2361
|
2809 interfaceContext.lightbox.post("Message",'Please keep listening');
|
nicholas@2224
|
2810 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2224
|
2811 return false;
|
nicholas@2224
|
2812 }
|
nicholas@2224
|
2813 }
|
nicholas@2224
|
2814 }
|
nicholas@2224
|
2815 return true;
|
nicholas@2224
|
2816 };
|
nicholas@2224
|
2817
|
nicholas@2224
|
2818 this.checkHiddenReference = function()
|
nicholas@2224
|
2819 {
|
nicholas@2224
|
2820 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2821 {
|
nicholas@2224
|
2822 if (ao.specification.type == "reference")
|
nicholas@2224
|
2823 {
|
nicholas@2224
|
2824 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
nicholas@2224
|
2825 // Anchor is not set below
|
nicholas@2224
|
2826 console.log('Reference node not above marker value');
|
nicholas@2224
|
2827 this.storeErrorNode('Reference node not above marker value');
|
nicholas@2361
|
2828 interfaceContext.lightbox.post("Message",'Please keep listening');
|
nicholas@2224
|
2829 return false;
|
nicholas@2224
|
2830 }
|
nicholas@2224
|
2831 }
|
nicholas@2224
|
2832 }
|
nicholas@2224
|
2833 return true;
|
nicholas@2224
|
2834 };
|
nicholas@2224
|
2835
|
nicholas@2224
|
2836 this.checkFragmentsFullyPlayed = function ()
|
nicholas@2224
|
2837 {
|
nicholas@2224
|
2838 // Checks the entire file has been played back
|
nicholas@2224
|
2839 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2224
|
2840 if (audioEngineContext.loopPlayback)
|
nicholas@2224
|
2841 {
|
nicholas@2224
|
2842 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2224
|
2843 return true;
|
nicholas@2224
|
2844 }
|
nicholas@2224
|
2845 var check_pass = true;
|
nicholas@2224
|
2846 var error_obj = [];
|
nicholas@2224
|
2847 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
nicholas@2224
|
2848 {
|
nicholas@2224
|
2849 var object = audioEngineContext.audioObjects[i];
|
nicholas@2224
|
2850 var time = object.buffer.buffer.duration;
|
nicholas@2224
|
2851 var metric = object.metric;
|
nicholas@2224
|
2852 var passed = false;
|
nicholas@2224
|
2853 for (var j=0; j<metric.listenTracker.length; j++)
|
nicholas@2224
|
2854 {
|
nicholas@2224
|
2855 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
nicholas@2224
|
2856 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2224
|
2857 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2224
|
2858 var delta = stop_time - start_time;
|
nicholas@2224
|
2859 if (delta >= time)
|
nicholas@2224
|
2860 {
|
nicholas@2224
|
2861 passed = true;
|
nicholas@2224
|
2862 break;
|
nicholas@2224
|
2863 }
|
nicholas@2224
|
2864 }
|
nicholas@2224
|
2865 if (passed == false)
|
nicholas@2224
|
2866 {
|
nicholas@2224
|
2867 check_pass = false;
|
b@2258
|
2868 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2869 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2870 }
|
nicholas@2224
|
2871 }
|
nicholas@2224
|
2872 if (check_pass == false)
|
nicholas@2224
|
2873 {
|
nicholas@2224
|
2874 var str_start = "You have not completely listened to fragments ";
|
nicholas@2224
|
2875 for (var i=0; i<error_obj.length; i++)
|
nicholas@2224
|
2876 {
|
nicholas@2224
|
2877 str_start += error_obj[i];
|
nicholas@2224
|
2878 if (i != error_obj.length-1)
|
nicholas@2224
|
2879 {
|
nicholas@2224
|
2880 str_start += ', ';
|
nicholas@2224
|
2881 }
|
nicholas@2224
|
2882 }
|
nicholas@2224
|
2883 str_start += ". Please keep listening";
|
nicholas@2224
|
2884 console.log("[ALERT]: "+str_start);
|
nicholas@2224
|
2885 this.storeErrorNode("[ALERT]: "+str_start);
|
nicholas@2361
|
2886 interfaceContext.lightbox.post("Error",str_start);
|
nicholas@2224
|
2887 }
|
nicholas@2224
|
2888 };
|
nicholas@2224
|
2889 this.checkAllMoved = function()
|
nicholas@2224
|
2890 {
|
nicholas@2224
|
2891 var str = "You have not moved ";
|
nicholas@2224
|
2892 var failed = [];
|
nicholas@2224
|
2893 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2894 {
|
nicholas@2224
|
2895 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@2224
|
2896 {
|
b@2258
|
2897 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2898 }
|
nicholas@2224
|
2899 }
|
nicholas@2224
|
2900 if (failed.length == 0)
|
nicholas@2224
|
2901 {
|
nicholas@2224
|
2902 return true;
|
nicholas@2224
|
2903 } else if (failed.length == 1)
|
nicholas@2224
|
2904 {
|
nicholas@2224
|
2905 str += 'track '+failed[0];
|
nicholas@2224
|
2906 } else {
|
nicholas@2224
|
2907 str += 'tracks ';
|
nicholas@2224
|
2908 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2909 {
|
nicholas@2224
|
2910 str += failed[i]+', ';
|
nicholas@2224
|
2911 }
|
nicholas@2224
|
2912 str += 'and '+failed[i];
|
nicholas@2224
|
2913 }
|
nicholas@2224
|
2914 str +='.';
|
nicholas@2361
|
2915 interfaceContext.lightbox.post("Error",str);
|
nicholas@2224
|
2916 console.log(str);
|
nicholas@2224
|
2917 this.storeErrorNode(str);
|
nicholas@2224
|
2918 return false;
|
nicholas@2224
|
2919 };
|
nicholas@2224
|
2920 this.checkAllPlayed = function()
|
nicholas@2224
|
2921 {
|
nicholas@2224
|
2922 var str = "You have not played ";
|
nicholas@2224
|
2923 var failed = [];
|
nicholas@2224
|
2924 for (var ao of audioEngineContext.audioObjects)
|
nicholas@2224
|
2925 {
|
nicholas@2224
|
2926 if(ao.metric.wasListenedTo == false)
|
nicholas@2224
|
2927 {
|
b@2258
|
2928 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2224
|
2929 }
|
nicholas@2224
|
2930 }
|
nicholas@2224
|
2931 if (failed.length == 0)
|
nicholas@2224
|
2932 {
|
nicholas@2224
|
2933 return true;
|
nicholas@2224
|
2934 } else if (failed.length == 1)
|
nicholas@2224
|
2935 {
|
nicholas@2224
|
2936 str += 'track '+failed[0];
|
nicholas@2224
|
2937 } else {
|
nicholas@2224
|
2938 str += 'tracks ';
|
nicholas@2224
|
2939 for (var i=0; i<failed.length-1; i++)
|
nicholas@2224
|
2940 {
|
nicholas@2224
|
2941 str += failed[i]+', ';
|
nicholas@2224
|
2942 }
|
nicholas@2224
|
2943 str += 'and '+failed[i];
|
nicholas@2224
|
2944 }
|
nicholas@2224
|
2945 str +='.';
|
nicholas@2361
|
2946 interfaceContext.lightbox.post("Error",str);
|
nicholas@2224
|
2947 console.log(str);
|
nicholas@2224
|
2948 this.storeErrorNode(str);
|
nicholas@2224
|
2949 return false;
|
nicholas@2224
|
2950 };
|
nicholas@2310
|
2951 this.checkScaleRange = function(min, max) {
|
nicholas@2310
|
2952 var page = testState.getCurrentTestPage();
|
nicholas@2310
|
2953 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2310
|
2954 var state = true;
|
nicholas@2310
|
2955 var str = "Please keep listening. ";
|
nicholas@2310
|
2956 var minRanking = Infinity;
|
nicholas@2310
|
2957 var maxRanking = -Infinity;
|
nicholas@2310
|
2958 for (var ao of audioObjects) {
|
nicholas@2310
|
2959 var rank = ao.interfaceDOM.getValue();
|
nicholas@2310
|
2960 if (rank < minRanking) {minRanking = rank;}
|
nicholas@2310
|
2961 if (rank > maxRanking) {maxRanking = rank;}
|
nicholas@2310
|
2962 }
|
nicholas@2310
|
2963 if (minRanking*100 > min) {
|
nicholas@2310
|
2964 str += "At least one fragment must be below the "+min+" mark.";
|
nicholas@2310
|
2965 state = false;
|
nicholas@2310
|
2966 }
|
nicholas@2358
|
2967 if (maxRanking*100 < max) {
|
nicholas@2358
|
2968 str += "At least one fragment must be above the "+max+" mark."
|
nicholas@2310
|
2969 state = false;
|
nicholas@2310
|
2970 }
|
nicholas@2310
|
2971 if (!state) {
|
nicholas@2310
|
2972 console.log(str);
|
nicholas@2310
|
2973 this.storeErrorNode(str);
|
nicholas@2361
|
2974 interfaceContext.lightbox.post("Error",str);
|
nicholas@2310
|
2975 }
|
nicholas@2310
|
2976 return state;
|
nicholas@2310
|
2977 }
|
nicholas@2358
|
2978
|
nicholas@2224
|
2979 this.storeErrorNode = function(errorMessage)
|
nicholas@2224
|
2980 {
|
nicholas@2224
|
2981 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
2982 var node = storage.document.createElement('error');
|
nicholas@2224
|
2983 node.setAttribute('time',time);
|
nicholas@2224
|
2984 node.textContent = errorMessage;
|
nicholas@2224
|
2985 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
2986 };
|
nicholas@2224
|
2987 }
|
nicholas@2224
|
2988
|
nicholas@2224
|
2989 function Storage()
|
nicholas@2224
|
2990 {
|
nicholas@2224
|
2991 // Holds results in XML format until ready for collection
|
nicholas@2224
|
2992 this.globalPreTest = null;
|
nicholas@2224
|
2993 this.globalPostTest = null;
|
nicholas@2224
|
2994 this.testPages = [];
|
nicholas@2224
|
2995 this.document = null;
|
nicholas@2224
|
2996 this.root = null;
|
nicholas@2224
|
2997 this.state = 0;
|
nicholas@2224
|
2998
|
nicholas@2224
|
2999 this.initialise = function(existingStore)
|
nicholas@2224
|
3000 {
|
nicholas@2224
|
3001 if (existingStore == undefined) {
|
nicholas@2224
|
3002 // We need to get the sessionKey
|
nicholas@2224
|
3003 this.SessionKey.generateKey();
|
nicholas@2376
|
3004 this.document = document.implementation.createDocument(null,"waetresult",null);
|
nicholas@2224
|
3005 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3006 var projectDocument = specification.projectXML;
|
nicholas@2224
|
3007 projectDocument.setAttribute('file-name',url);
|
nicholas@2224
|
3008 projectDocument.setAttribute('url',qualifyURL(url));
|
nicholas@2224
|
3009 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3010 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3011 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3012 } else {
|
nicholas@2224
|
3013 this.document = existingStore;
|
nicholas@2294
|
3014 this.root = existingStore.firstChild;
|
nicholas@2224
|
3015 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3016 }
|
nicholas@2224
|
3017 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
nicholas@2224
|
3018 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
nicholas@2224
|
3019 };
|
nicholas@2224
|
3020
|
nicholas@2224
|
3021 this.SessionKey = {
|
nicholas@2224
|
3022 key: null,
|
nicholas@2224
|
3023 request: new XMLHttpRequest(),
|
nicholas@2224
|
3024 parent: this,
|
nicholas@2224
|
3025 handleEvent: function() {
|
nicholas@2224
|
3026 var parse = new DOMParser();
|
nicholas@2224
|
3027 var xml = parse.parseFromString(this.request.response,"text/xml");
|
giuliomoro@2301
|
3028 var shouldGenerateKey = true;
|
nicholas@2376
|
3029 if (this.request.response.length == 0) {
|
nicholas@2376
|
3030 console.log("Error: Server did not respond");
|
nicholas@2376
|
3031 return;
|
nicholas@2376
|
3032 }
|
nicholas@2376
|
3033 if(xml.getElementsByTagName("state").length > 0){
|
nicholas@2376
|
3034 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
giuliomoro@2301
|
3035 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
giuliomoro@2301
|
3036 this.parent.root.setAttribute("key",this.key);
|
giuliomoro@2301
|
3037 this.parent.root.setAttribute("state","empty");
|
giuliomoro@2301
|
3038 shouldGenerateKey = false;
|
giuliomoro@2301
|
3039 }
|
giuliomoro@2301
|
3040 }
|
giuliomoro@2301
|
3041 if(shouldGenerateKey === true){
|
giuliomoro@2301
|
3042 this.generateKey();
|
giuliomoro@2301
|
3043 }
|
nicholas@2224
|
3044 },
|
nicholas@2224
|
3045 generateKey: function() {
|
nicholas@2224
|
3046 var temp_key = randomString(32);
|
nicholas@2302
|
3047 var returnURL = "";
|
nicholas@2302
|
3048 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
3049 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
3050 returnURL = specification.projectReturn;
|
nicholas@2302
|
3051 }
|
nicholas@2302
|
3052 }
|
nicholas@2302
|
3053 this.request.open("GET",returnURL+"php/keygen.php?key="+temp_key,true);
|
nicholas@2224
|
3054 this.request.addEventListener("load",this);
|
nicholas@2224
|
3055 this.request.send();
|
nicholas@2224
|
3056 },
|
nicholas@2224
|
3057 update: function() {
|
nicholas@2357
|
3058 if (this.key == null) {
|
nicholas@2357
|
3059 console.log("Cannot save as key == null");
|
nicholas@2357
|
3060 return;
|
nicholas@2357
|
3061 }
|
nicholas@2224
|
3062 this.parent.root.setAttribute("state","update");
|
nicholas@2224
|
3063 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3064 var returnURL = "";
|
nicholas@2302
|
3065 if (typeof specification.projectReturn == "string") {
|
nicholas@2302
|
3066 if (specification.projectReturn.substr(0,4) == "http") {
|
nicholas@2302
|
3067 returnURL = specification.projectReturn;
|
nicholas@2302
|
3068 }
|
nicholas@2302
|
3069 }
|
nicholas@2302
|
3070 xmlhttp.open("POST",returnURL+"php/save.php?key="+this.key);
|
nicholas@2224
|
3071 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2224
|
3072 xmlhttp.onerror = function(){
|
nicholas@2224
|
3073 console.log('Error updating file to server!');
|
nicholas@2224
|
3074 };
|
nicholas@2224
|
3075 var hold = document.createElement("div");
|
nicholas@2224
|
3076 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3077 hold.appendChild(clone);
|
nicholas@2224
|
3078 xmlhttp.onload = function() {
|
nicholas@2224
|
3079 if (this.status >= 300) {
|
nicholas@2224
|
3080 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3081 } else {
|
nicholas@2224
|
3082 var parser = new DOMParser();
|
nicholas@2224
|
3083 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3084 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3085 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3086 var file = response.getElementsByTagName("file")[0];
|
nicholas@2224
|
3087 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
|
nicholas@2224
|
3088 } else {
|
nicholas@2224
|
3089 var message = response.getElementsByTagName("message");
|
nicholas@2224
|
3090 console.log("Intermediate save: Error! "+message.textContent);
|
nicholas@2224
|
3091 }
|
nicholas@2224
|
3092 }
|
nicholas@2224
|
3093 }
|
nicholas@2224
|
3094 xmlhttp.send([hold.innerHTML]);
|
nicholas@2224
|
3095 }
|
nicholas@2224
|
3096 }
|
nicholas@2224
|
3097
|
nicholas@2224
|
3098 this.createTestPageStore = function(specification)
|
nicholas@2224
|
3099 {
|
nicholas@2224
|
3100 var store = new this.pageNode(this,specification);
|
nicholas@2224
|
3101 this.testPages.push(store);
|
nicholas@2224
|
3102 return this.testPages[this.testPages.length-1];
|
nicholas@2224
|
3103 };
|
nicholas@2224
|
3104
|
nicholas@2224
|
3105 this.surveyNode = function(parent,root,specification)
|
nicholas@2224
|
3106 {
|
nicholas@2224
|
3107 this.specification = specification;
|
nicholas@2224
|
3108 this.parent = parent;
|
nicholas@2224
|
3109 this.state = "empty";
|
nicholas@2224
|
3110 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2224
|
3111 this.XMLDOM.setAttribute('location',this.specification.location);
|
nicholas@2224
|
3112 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3113 for (var optNode of this.specification.options)
|
nicholas@2224
|
3114 {
|
nicholas@2224
|
3115 if (optNode.type != 'statement')
|
nicholas@2224
|
3116 {
|
nicholas@2224
|
3117 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2224
|
3118 node.setAttribute("ref",optNode.id);
|
nicholas@2224
|
3119 node.setAttribute('type',optNode.type);
|
nicholas@2224
|
3120 this.XMLDOM.appendChild(node);
|
nicholas@2224
|
3121 }
|
nicholas@2224
|
3122 }
|
nicholas@2224
|
3123 root.appendChild(this.XMLDOM);
|
nicholas@2224
|
3124
|
nicholas@2224
|
3125 this.postResult = function(node)
|
nicholas@2224
|
3126 {
|
nicholas@2224
|
3127 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2224
|
3128 // ID is the position
|
nicholas@2224
|
3129 if (node.specification.type == 'statement'){return;}
|
nicholas@2294
|
3130 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2224
|
3131 while(surveyresult != null) {
|
nicholas@2224
|
3132 if (surveyresult.getAttribute("ref") == node.specification.id)
|
nicholas@2224
|
3133 {
|
nicholas@2224
|
3134 break;
|
nicholas@2224
|
3135 }
|
nicholas@2224
|
3136 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3137 }
|
nicholas@2224
|
3138 switch(node.specification.type)
|
nicholas@2224
|
3139 {
|
nicholas@2224
|
3140 case "number":
|
nicholas@2224
|
3141 case "question":
|
nicholas@2224
|
3142 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
3143 child.textContent = node.response;
|
nicholas@2224
|
3144 surveyresult.appendChild(child);
|
nicholas@2224
|
3145 break;
|
nicholas@2224
|
3146 case "radio":
|
nicholas@2224
|
3147 var child = this.parent.document.createElement('response');
|
nicholas@2224
|
3148 child.setAttribute('name',node.response.name);
|
nicholas@2224
|
3149 child.textContent = node.response.text;
|
nicholas@2224
|
3150 surveyresult.appendChild(child);
|
nicholas@2224
|
3151 break;
|
nicholas@2224
|
3152 case "checkbox":
|
nicholas@2224
|
3153 for (var i=0; i<node.response.length; i++)
|
nicholas@2224
|
3154 {
|
nicholas@2224
|
3155 var checkNode = this.parent.document.createElement('response');
|
nicholas@2224
|
3156 checkNode.setAttribute('name',node.response[i].name);
|
nicholas@2224
|
3157 checkNode.setAttribute('checked',node.response[i].checked);
|
nicholas@2224
|
3158 surveyresult.appendChild(checkNode);
|
nicholas@2224
|
3159 }
|
nicholas@2224
|
3160 break;
|
nicholas@2224
|
3161 }
|
nicholas@2224
|
3162 };
|
nicholas@2224
|
3163 this.complete = function() {
|
nicholas@2224
|
3164 this.state = "complete";
|
nicholas@2224
|
3165 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3166 }
|
nicholas@2224
|
3167 };
|
nicholas@2224
|
3168
|
nicholas@2224
|
3169 this.pageNode = function(parent,specification)
|
nicholas@2224
|
3170 {
|
nicholas@2224
|
3171 // Create one store per test page
|
nicholas@2224
|
3172 this.specification = specification;
|
nicholas@2224
|
3173 this.parent = parent;
|
nicholas@2224
|
3174 this.state = "empty";
|
nicholas@2224
|
3175 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2224
|
3176 this.XMLDOM.setAttribute('ref',specification.id);
|
nicholas@2224
|
3177 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
nicholas@2224
|
3178 this.XMLDOM.setAttribute("state",this.state);
|
nicholas@2224
|
3179 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
nicholas@2224
|
3180 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
nicholas@2224
|
3181
|
nicholas@2224
|
3182 // Add any page metrics
|
nicholas@2224
|
3183 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3184 this.XMLDOM.appendChild(page_metric);
|
nicholas@2224
|
3185
|
nicholas@2224
|
3186 // Add the audioelement
|
nicholas@2224
|
3187 for (var element of this.specification.audioElements)
|
nicholas@2224
|
3188 {
|
nicholas@2224
|
3189 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2224
|
3190 aeNode.setAttribute('ref',element.id);
|
nicholas@2224
|
3191 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
|
nicholas@2224
|
3192 aeNode.setAttribute('type',element.type);
|
nicholas@2224
|
3193 aeNode.setAttribute('url', element.url);
|
nicholas@2224
|
3194 aeNode.setAttribute('fqurl',qualifyURL(element.url));
|
nicholas@2224
|
3195 aeNode.setAttribute('gain', element.gain);
|
nicholas@2224
|
3196 if (element.type == 'anchor' || element.type == 'reference')
|
nicholas@2224
|
3197 {
|
nicholas@2224
|
3198 if (element.marker > 0)
|
nicholas@2224
|
3199 {
|
nicholas@2224
|
3200 aeNode.setAttribute('marker',element.marker);
|
nicholas@2224
|
3201 }
|
nicholas@2224
|
3202 }
|
nicholas@2224
|
3203 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2224
|
3204 aeNode.appendChild(ae_metric);
|
nicholas@2224
|
3205 this.XMLDOM.appendChild(aeNode);
|
nicholas@2224
|
3206 }
|
nicholas@2224
|
3207
|
nicholas@2224
|
3208 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2224
|
3209
|
nicholas@2224
|
3210 this.complete = function() {
|
nicholas@2224
|
3211 this.state = "complete";
|
nicholas@2224
|
3212 this.XMLDOM.setAttribute("state","complete");
|
nicholas@2224
|
3213 }
|
nicholas@2224
|
3214 };
|
nicholas@2224
|
3215 this.update = function() {
|
nicholas@2224
|
3216 this.SessionKey.update();
|
nicholas@2224
|
3217 }
|
nicholas@2224
|
3218 this.finish = function()
|
nicholas@2224
|
3219 {
|
nicholas@2224
|
3220 if (this.state == 0)
|
nicholas@2224
|
3221 {
|
nicholas@2224
|
3222 this.update();
|
nicholas@2224
|
3223 }
|
nicholas@2224
|
3224 this.state = 1;
|
nicholas@2224
|
3225 return this.root;
|
nicholas@2224
|
3226 };
|
nicholas@2224
|
3227 }
|