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