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