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