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