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