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