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