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