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