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