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