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