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