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