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