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