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