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