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