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";
|
nicholas@2360
|
2410 },
|
nicholas@2498
|
2411 clear: function () {
|
nicholas@2360
|
2412 this.root.style.visibility = "";
|
nicholas@2360
|
2413 this.blanker.style.visibility = "";
|
nicholas@2360
|
2414 this.content.textContent = "";
|
nicholas@2360
|
2415 },
|
nicholas@2498
|
2416 handleEvent: function (event) {
|
nicholas@2360
|
2417 if (event.currentTarget == this.accept) {
|
nicholas@2360
|
2418 this.clear();
|
nicholas@2360
|
2419 }
|
nicholas@2360
|
2420 },
|
nicholas@2498
|
2421 resize: function (event) {
|
nicholas@2498
|
2422 this.root.style.left = (window.innerWidth / 2) - 250 + 'px';
|
nicholas@2360
|
2423 }
|
nicholas@2708
|
2424 };
|
nicholas@2498
|
2425
|
nicholas@2360
|
2426 this.lightbox.root.appendChild(this.lightbox.content);
|
nicholas@2360
|
2427 this.lightbox.root.appendChild(this.lightbox.accept);
|
nicholas@2360
|
2428 this.lightbox.root.className = "popupHolder";
|
nicholas@2360
|
2429 this.lightbox.root.id = "lightbox-root";
|
nicholas@2360
|
2430 this.lightbox.accept.className = "popupButton";
|
nicholas@2360
|
2431 this.lightbox.accept.style.bottom = "10px";
|
nicholas@2360
|
2432 this.lightbox.accept.textContent = "OK";
|
nicholas@2360
|
2433 this.lightbox.accept.style.left = "237.5px";
|
nicholas@2498
|
2434 this.lightbox.accept.addEventListener("click", this.lightbox);
|
nicholas@2360
|
2435 this.lightbox.blanker.className = "testHalt";
|
nicholas@2360
|
2436 this.lightbox.blanker.id = "lightbox-blanker";
|
nicholas@2360
|
2437 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
|
nicholas@2360
|
2438 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
|
nicholas@2498
|
2439
|
nicholas@2712
|
2440 this.commentBoxes = (function () {
|
nicholas@2712
|
2441 var commentBoxes = {};
|
nicholas@2712
|
2442 commentBoxes.boxes = [];
|
nicholas@2712
|
2443 commentBoxes.injectPoint = null;
|
nicholas@2712
|
2444 commentBoxes.elementCommentBox = function (audioObject) {
|
nicholas@2224
|
2445 var element = audioObject.specification;
|
nicholas@2224
|
2446 this.audioObject = audioObject;
|
nicholas@2224
|
2447 this.id = audioObject.id;
|
nicholas@2224
|
2448 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
2449 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2450 this.trackComment = document.createElement('div');
|
nicholas@2224
|
2451 this.trackComment.className = 'comment-div';
|
nicholas@2498
|
2452 this.trackComment.id = 'comment-div-' + audioObject.id;
|
nicholas@2224
|
2453 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2454 this.trackString = document.createElement('span');
|
nicholas@2498
|
2455 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
2456 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2457 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
2458 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
2459 this.trackCommentBox.cols = '100';
|
nicholas@2498
|
2460 this.trackCommentBox.name = 'trackComment' + audioObject.id;
|
nicholas@2224
|
2461 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
2462 var br = document.createElement('br');
|
nicholas@2224
|
2463 // Add to the holder.
|
nicholas@2224
|
2464 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
2465 this.trackComment.appendChild(br);
|
nicholas@2224
|
2466 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
2467
|
nicholas@2498
|
2468 this.exportXMLDOM = function () {
|
nicholas@2224
|
2469 var root = document.createElement('comment');
|
nicholas@2224
|
2470 var question = document.createElement('question');
|
nicholas@2224
|
2471 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2472 var response = document.createElement('response');
|
nicholas@2224
|
2473 response.textContent = this.trackCommentBox.value;
|
nicholas@2498
|
2474 console.log("Comment frag-" + this.id + ": " + response.textContent);
|
nicholas@2224
|
2475 root.appendChild(question);
|
nicholas@2224
|
2476 root.appendChild(response);
|
nicholas@2224
|
2477 return root;
|
nicholas@2224
|
2478 };
|
nicholas@2498
|
2479 this.resize = function () {
|
nicholas@2498
|
2480 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2481 if (boxwidth >= 600) {
|
nicholas@2224
|
2482 boxwidth = 600;
|
nicholas@2498
|
2483 } else if (boxwidth < 400) {
|
nicholas@2224
|
2484 boxwidth = 400;
|
nicholas@2224
|
2485 }
|
nicholas@2498
|
2486 this.trackComment.style.width = boxwidth + "px";
|
nicholas@2498
|
2487 this.trackCommentBox.style.width = boxwidth - 6 + "px";
|
nicholas@2224
|
2488 };
|
nicholas@2224
|
2489 this.resize();
|
nicholas@2725
|
2490 this.highlight = function (state) {
|
nicholas@2725
|
2491 if (state === true) {
|
nicholas@2725
|
2492 $(this.trackComment).addClass("comment-box-playing");
|
nicholas@2725
|
2493 } else {
|
nicholas@2725
|
2494 $(this.trackComment).removeClass("comment-box-playing");
|
nicholas@2725
|
2495 }
|
nicholas@2725
|
2496 };
|
nicholas@2224
|
2497 };
|
nicholas@2712
|
2498 commentBoxes.createCommentBox = function (audioObject) {
|
nicholas@2224
|
2499 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2500 this.boxes.push(node);
|
nicholas@2224
|
2501 audioObject.commentDOM = node;
|
nicholas@2224
|
2502 return node;
|
nicholas@2224
|
2503 };
|
nicholas@2712
|
2504 commentBoxes.sortCommentBoxes = function () {
|
nicholas@2498
|
2505 this.boxes.sort(function (a, b) {
|
nicholas@2498
|
2506 return a.id - b.id;
|
nicholas@2498
|
2507 });
|
nicholas@2224
|
2508 };
|
nicholas@2224
|
2509
|
nicholas@2712
|
2510 commentBoxes.showCommentBoxes = function (inject, sort) {
|
nicholas@2224
|
2511 this.injectPoint = inject;
|
nicholas@2498
|
2512 if (sort) {
|
nicholas@2498
|
2513 this.sortCommentBoxes();
|
nicholas@2498
|
2514 }
|
nicholas@2708
|
2515 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2516 inject.appendChild(box.trackComment);
|
nicholas@2708
|
2517 });
|
nicholas@2224
|
2518 };
|
nicholas@2224
|
2519
|
nicholas@2712
|
2520 commentBoxes.deleteCommentBoxes = function () {
|
nicholas@2708
|
2521 if (this.injectPoint !== null) {
|
nicholas@2708
|
2522 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2523 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2708
|
2524 }, this);
|
nicholas@2224
|
2525 this.injectPoint = null;
|
nicholas@2224
|
2526 }
|
nicholas@2224
|
2527 this.boxes = [];
|
nicholas@2224
|
2528 };
|
nicholas@2725
|
2529 commentBoxes.highlightById = function (id) {
|
nicholas@2725
|
2530 if (id === undefined || typeof id !== "number" || id >= this.boxes.length) {
|
nicholas@2725
|
2531 console.log("Error - Invalid id");
|
nicholas@2725
|
2532 id = -1;
|
nicholas@2725
|
2533 }
|
nicholas@2725
|
2534 this.boxes.forEach(function (a) {
|
nicholas@2725
|
2535 if (a.id === id) {
|
nicholas@2725
|
2536 a.highlight(true);
|
nicholas@2725
|
2537 } else {
|
nicholas@2725
|
2538 a.highlight(false);
|
nicholas@2725
|
2539 }
|
nicholas@2725
|
2540 });
|
nicholas@2725
|
2541 };
|
nicholas@2712
|
2542 return commentBoxes;
|
nicholas@2712
|
2543 })();
|
nicholas@2498
|
2544
|
nicholas@2498
|
2545 this.commentQuestions = [];
|
nicholas@2498
|
2546
|
nicholas@2498
|
2547 this.commentBox = function (commentQuestion) {
|
nicholas@2498
|
2548 this.specification = commentQuestion;
|
nicholas@2498
|
2549 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2550 this.holder = document.createElement('div');
|
nicholas@2498
|
2551 this.holder.className = 'comment-div';
|
nicholas@2498
|
2552 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2553 this.string = document.createElement('span');
|
nicholas@2498
|
2554 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2555 // Create the HTML5 comment box 'textarea'
|
nicholas@2498
|
2556 this.textArea = document.createElement('textarea');
|
nicholas@2498
|
2557 this.textArea.rows = '4';
|
nicholas@2498
|
2558 this.textArea.cols = '100';
|
nicholas@2498
|
2559 this.textArea.className = 'trackComment';
|
nicholas@2498
|
2560 var br = document.createElement('br');
|
nicholas@2498
|
2561 // Add to the holder.
|
nicholas@2498
|
2562 this.holder.appendChild(this.string);
|
nicholas@2498
|
2563 this.holder.appendChild(br);
|
nicholas@2498
|
2564 this.holder.appendChild(this.textArea);
|
nicholas@2498
|
2565
|
nicholas@2498
|
2566 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2567 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2568 root.id = this.specification.id;
|
nicholas@2498
|
2569 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2570 console.log("Question: " + this.string.textContent);
|
nicholas@2498
|
2571 console.log("Response: " + root.textContent);
|
nicholas@2224
|
2572 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2573 question.textContent = this.string.textContent;
|
nicholas@2224
|
2574 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2575 response.textContent = this.textArea.value;
|
nicholas@2224
|
2576 root.appendChild(question);
|
nicholas@2224
|
2577 root.appendChild(response);
|
nicholas@2224
|
2578 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2579 return root;
|
nicholas@2498
|
2580 };
|
nicholas@2498
|
2581 this.resize = function () {
|
nicholas@2498
|
2582 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2583 if (boxwidth >= 600) {
|
nicholas@2498
|
2584 boxwidth = 600;
|
nicholas@2498
|
2585 } else if (boxwidth < 400) {
|
nicholas@2498
|
2586 boxwidth = 400;
|
nicholas@2498
|
2587 }
|
nicholas@2498
|
2588 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2589 this.textArea.style.width = boxwidth - 6 + "px";
|
nicholas@2498
|
2590 };
|
nicholas@2498
|
2591 this.resize();
|
nicholas@2498
|
2592 };
|
nicholas@2498
|
2593
|
nicholas@2498
|
2594 this.radioBox = function (commentQuestion) {
|
nicholas@2498
|
2595 this.specification = commentQuestion;
|
nicholas@2498
|
2596 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2597 this.holder = document.createElement('div');
|
nicholas@2498
|
2598 this.holder.className = 'comment-div';
|
nicholas@2498
|
2599 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2600 this.string = document.createElement('span');
|
nicholas@2498
|
2601 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2602 // Add to the holder.
|
nicholas@2498
|
2603 this.holder.appendChild(this.string);
|
nicholas@2498
|
2604 this.options = [];
|
nicholas@2498
|
2605 this.inputs = document.createElement('div');
|
nicholas@2711
|
2606 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2607
|
nicholas@2498
|
2608 var optCount = commentQuestion.options.length;
|
nicholas@2711
|
2609 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2610 var div = document.createElement('div');
|
nicholas@2711
|
2611 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2612
|
nicholas@2711
|
2613 var span = document.createElement('span');
|
nicholas@2711
|
2614 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2615 span.className = 'comment-radio-span';
|
nicholas@2711
|
2616 div.appendChild(span);
|
nicholas@2722
|
2617
|
nicholas@2498
|
2618 var input = document.createElement('input');
|
nicholas@2498
|
2619 input.type = 'radio';
|
nicholas@2498
|
2620 input.name = commentQuestion.id;
|
nicholas@2711
|
2621 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2622 input.className = 'comment-radio';
|
nicholas@2498
|
2623 div.appendChild(input);
|
nicholas@2722
|
2624
|
nicholas@2498
|
2625 this.inputs.appendChild(div);
|
nicholas@2498
|
2626 this.options.push(input);
|
nicholas@2498
|
2627 }
|
nicholas@2498
|
2628 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2629
|
nicholas@2498
|
2630 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2631 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2632 root.id = this.specification.id;
|
nicholas@2498
|
2633 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2634 var question = document.createElement('question');
|
nicholas@2498
|
2635 question.textContent = this.string.textContent;
|
nicholas@2498
|
2636 var response = document.createElement('response');
|
nicholas@2498
|
2637 var i = 0;
|
nicholas@2708
|
2638 while (this.options[i].checked === false) {
|
nicholas@2498
|
2639 i++;
|
nicholas@2498
|
2640 if (i >= this.options.length) {
|
nicholas@2498
|
2641 break;
|
nicholas@2498
|
2642 }
|
nicholas@2498
|
2643 }
|
nicholas@2498
|
2644 if (i >= this.options.length) {
|
nicholas@2498
|
2645 response.textContent = 'null';
|
nicholas@2498
|
2646 } else {
|
nicholas@2498
|
2647 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2498
|
2648 response.setAttribute('number', i);
|
nicholas@2498
|
2649 }
|
nicholas@2498
|
2650 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2651 console.log('Response: ' + response.textContent);
|
nicholas@2498
|
2652 root.appendChild(question);
|
nicholas@2498
|
2653 root.appendChild(response);
|
nicholas@2224
|
2654 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2655 return root;
|
nicholas@2498
|
2656 };
|
nicholas@2498
|
2657 this.resize = function () {
|
nicholas@2498
|
2658 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2659 if (boxwidth >= 600) {
|
nicholas@2498
|
2660 boxwidth = 600;
|
nicholas@2498
|
2661 } else if (boxwidth < 400) {
|
nicholas@2498
|
2662 boxwidth = 400;
|
nicholas@2498
|
2663 }
|
nicholas@2498
|
2664 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2665 };
|
nicholas@2498
|
2666 this.resize();
|
nicholas@2498
|
2667 };
|
nicholas@2498
|
2668
|
nicholas@2498
|
2669 this.checkboxBox = function (commentQuestion) {
|
nicholas@2498
|
2670 this.specification = commentQuestion;
|
nicholas@2498
|
2671 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2672 this.holder = document.createElement('div');
|
nicholas@2498
|
2673 this.holder.className = 'comment-div';
|
nicholas@2498
|
2674 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2675 this.string = document.createElement('span');
|
nicholas@2498
|
2676 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2677 // Add to the holder.
|
nicholas@2498
|
2678 this.holder.appendChild(this.string);
|
nicholas@2498
|
2679 this.options = [];
|
nicholas@2498
|
2680 this.inputs = document.createElement('div');
|
nicholas@2294
|
2681 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2682
|
nicholas@2498
|
2683 var optCount = commentQuestion.options.length;
|
nicholas@2498
|
2684 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2685 var div = document.createElement('div');
|
nicholas@2711
|
2686 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2687
|
nicholas@2711
|
2688 var span = document.createElement('span');
|
nicholas@2711
|
2689 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2690 span.className = 'comment-radio-span';
|
nicholas@2711
|
2691 div.appendChild(span);
|
nicholas@2722
|
2692
|
nicholas@2498
|
2693 var input = document.createElement('input');
|
nicholas@2498
|
2694 input.type = 'checkbox';
|
nicholas@2498
|
2695 input.name = commentQuestion.id;
|
nicholas@2498
|
2696 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2697 input.className = 'comment-radio';
|
nicholas@2498
|
2698 div.appendChild(input);
|
nicholas@2722
|
2699
|
nicholas@2498
|
2700 this.inputs.appendChild(div);
|
nicholas@2498
|
2701 this.options.push(input);
|
nicholas@2498
|
2702 }
|
nicholas@2498
|
2703 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2704
|
nicholas@2498
|
2705 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2706 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2707 root.id = this.specification.id;
|
nicholas@2498
|
2708 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2709 var question = document.createElement('question');
|
nicholas@2498
|
2710 question.textContent = this.string.textContent;
|
nicholas@2498
|
2711 root.appendChild(question);
|
nicholas@2498
|
2712 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2713 for (var i = 0; i < this.options.length; i++) {
|
nicholas@2498
|
2714 var response = document.createElement('response');
|
nicholas@2498
|
2715 response.textContent = this.options[i].checked;
|
nicholas@2498
|
2716 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
|
nicholas@2498
|
2717 root.appendChild(response);
|
nicholas@2498
|
2718 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
|
nicholas@2498
|
2719 }
|
nicholas@2224
|
2720 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2721 return root;
|
nicholas@2498
|
2722 };
|
nicholas@2498
|
2723 this.resize = function () {
|
nicholas@2498
|
2724 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2725 if (boxwidth >= 600) {
|
nicholas@2498
|
2726 boxwidth = 600;
|
nicholas@2498
|
2727 } else if (boxwidth < 400) {
|
nicholas@2498
|
2728 boxwidth = 400;
|
nicholas@2498
|
2729 }
|
nicholas@2498
|
2730 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2731 };
|
nicholas@2498
|
2732 this.resize();
|
nicholas@2498
|
2733 };
|
nicholas@2498
|
2734
|
n@2579
|
2735 this.sliderBox = function (commentQuestion) {
|
n@2579
|
2736 this.specification = commentQuestion;
|
n@2579
|
2737 this.holder = document.createElement("div");
|
n@2579
|
2738 this.holder.className = 'comment-div';
|
n@2579
|
2739 this.string = document.createElement("span");
|
n@2579
|
2740 this.string.innerHTML = commentQuestion.statement;
|
n@2579
|
2741 this.slider = document.createElement("input");
|
n@2579
|
2742 this.slider.type = "range";
|
n@2579
|
2743 this.slider.min = commentQuestion.min;
|
n@2579
|
2744 this.slider.max = commentQuestion.max;
|
n@2579
|
2745 this.slider.step = commentQuestion.step;
|
n@2579
|
2746 this.slider.value = commentQuestion.value;
|
n@2579
|
2747 var br = document.createElement('br');
|
n@2579
|
2748
|
n@2580
|
2749 var textHolder = document.createElement("div");
|
n@2580
|
2750 textHolder.className = "comment-slider-text-holder";
|
n@2580
|
2751
|
n@2580
|
2752 this.leftText = document.createElement("span");
|
n@2580
|
2753 this.leftText.textContent = commentQuestion.leftText;
|
n@2580
|
2754 this.rightText = document.createElement("span");
|
n@2580
|
2755 this.rightText.textContent = commentQuestion.rightText;
|
n@2580
|
2756 textHolder.appendChild(this.leftText);
|
n@2580
|
2757 textHolder.appendChild(this.rightText);
|
n@2580
|
2758
|
n@2579
|
2759 this.holder.appendChild(this.string);
|
n@2579
|
2760 this.holder.appendChild(br);
|
n@2579
|
2761 this.holder.appendChild(this.slider);
|
n@2580
|
2762 this.holder.appendChild(textHolder);
|
n@2579
|
2763
|
n@2579
|
2764 this.exportXMLDOM = function (storePoint) {
|
n@2579
|
2765 var root = storePoint.parent.document.createElement('comment');
|
n@2579
|
2766 root.id = this.specification.id;
|
n@2579
|
2767 root.setAttribute('type', this.specification.type);
|
n@2579
|
2768 console.log("Question: " + this.string.textContent);
|
n@2579
|
2769 console.log("Response: " + this.slider.value);
|
n@2579
|
2770 var question = storePoint.parent.document.createElement('question');
|
n@2579
|
2771 question.textContent = this.string.textContent;
|
n@2579
|
2772 var response = storePoint.parent.document.createElement('response');
|
n@2579
|
2773 response.textContent = this.slider.value;
|
n@2579
|
2774 root.appendChild(question);
|
n@2579
|
2775 root.appendChild(response);
|
n@2579
|
2776 storePoint.XMLDOM.appendChild(root);
|
n@2579
|
2777 return root;
|
n@2579
|
2778 };
|
n@2579
|
2779 this.resize = function () {
|
n@2579
|
2780 var boxwidth = (window.innerWidth - 100) / 2;
|
n@2579
|
2781 if (boxwidth >= 600) {
|
n@2579
|
2782 boxwidth = 600;
|
n@2579
|
2783 } else if (boxwidth < 400) {
|
n@2579
|
2784 boxwidth = 400;
|
n@2579
|
2785 }
|
n@2579
|
2786 this.holder.style.width = boxwidth + "px";
|
n@2579
|
2787 this.slider.style.width = boxwidth - 24 + "px";
|
n@2579
|
2788 };
|
n@2579
|
2789 this.resize();
|
n@2579
|
2790 };
|
n@2579
|
2791
|
nicholas@2498
|
2792 this.createCommentQuestion = function (element) {
|
nicholas@2498
|
2793 var node;
|
nicholas@2498
|
2794 if (element.type == 'question') {
|
nicholas@2498
|
2795 node = new this.commentBox(element);
|
nicholas@2498
|
2796 } else if (element.type == 'radio') {
|
nicholas@2498
|
2797 node = new this.radioBox(element);
|
nicholas@2498
|
2798 } else if (element.type == 'checkbox') {
|
nicholas@2498
|
2799 node = new this.checkboxBox(element);
|
n@2579
|
2800 } else if (element.type == 'slider') {
|
n@2579
|
2801 node = new this.sliderBox(element);
|
nicholas@2498
|
2802 }
|
nicholas@2498
|
2803 this.commentQuestions.push(node);
|
nicholas@2498
|
2804 return node;
|
nicholas@2498
|
2805 };
|
nicholas@2498
|
2806
|
nicholas@2498
|
2807 this.deleteCommentQuestions = function () {
|
nicholas@2498
|
2808 this.commentQuestions = [];
|
nicholas@2498
|
2809 };
|
nicholas@2498
|
2810
|
nicholas@2498
|
2811 this.outsideReferenceDOM = function (audioObject, index, inject) {
|
nicholas@2224
|
2812 this.parent = audioObject;
|
nicholas@2224
|
2813 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2814 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2498
|
2815 this.outsideReferenceHolder.setAttribute('track-id', index);
|
nicholas@2409
|
2816 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2817 this.outsideReferenceHolder.disabled = true;
|
nicholas@2708
|
2818 this.handleEvent = function (event) {
|
nicholas@2708
|
2819 audioEngineContext.play(this.parent.id);
|
nicholas@2224
|
2820 };
|
nicholas@2708
|
2821 this.outsideReferenceHolder.addEventListener("click", this);
|
nicholas@2224
|
2822 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2498
|
2823 this.enable = function () {
|
nicholas@2498
|
2824 if (this.parent.state == 1) {
|
nicholas@2224
|
2825 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2826 }
|
nicholas@2224
|
2827 };
|
nicholas@2498
|
2828 this.updateLoading = function (progress) {
|
nicholas@2498
|
2829 if (progress != 100) {
|
nicholas@2224
|
2830 progress = String(progress);
|
nicholas@2224
|
2831 progress = progress.split('.')[0];
|
nicholas@2498
|
2832 this.outsideReferenceHolder.textContent = progress + '%';
|
nicholas@2224
|
2833 } else {
|
nicholas@2409
|
2834 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2835 }
|
nicholas@2224
|
2836 };
|
nicholas@2498
|
2837 this.startPlayback = function () {
|
nicholas@2224
|
2838 // Called when playback has begun
|
nicholas@2224
|
2839 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2840 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2841 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2842 };
|
nicholas@2498
|
2843 this.stopPlayback = function () {
|
nicholas@2224
|
2844 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2845 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2846 };
|
nicholas@2498
|
2847 this.exportXMLDOM = function (audioObject) {
|
nicholas@2224
|
2848 return null;
|
nicholas@2224
|
2849 };
|
nicholas@2498
|
2850 this.getValue = function () {
|
nicholas@2224
|
2851 return 0;
|
nicholas@2224
|
2852 };
|
nicholas@2498
|
2853 this.getPresentedId = function () {
|
nicholas@2409
|
2854 return this.parent.specification.label || "Reference";
|
nicholas@2224
|
2855 };
|
nicholas@2498
|
2856 this.canMove = function () {
|
nicholas@2224
|
2857 return false;
|
nicholas@2224
|
2858 };
|
nicholas@2498
|
2859 this.error = function () {
|
nicholas@2498
|
2860 // audioObject has an error!!
|
nicholas@2224
|
2861 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2862 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2708
|
2863 };
|
nicholas@2708
|
2864 };
|
nicholas@2498
|
2865
|
nicholas@2712
|
2866 this.playhead = (function () {
|
nicholas@2722
|
2867 var playhead = {};
|
nicholas@2712
|
2868 playhead.object = document.createElement('div');
|
nicholas@2712
|
2869 playhead.object.className = 'playhead';
|
nicholas@2712
|
2870 playhead.object.align = 'left';
|
nicholas@2498
|
2871 var curTime = document.createElement('div');
|
nicholas@2498
|
2872 curTime.style.width = '50px';
|
nicholas@2712
|
2873 playhead.curTimeSpan = document.createElement('span');
|
nicholas@2712
|
2874 playhead.curTimeSpan.textContent = '00:00';
|
nicholas@2712
|
2875 curTime.appendChild(playhead.curTimeSpan);
|
nicholas@2712
|
2876 playhead.object.appendChild(curTime);
|
nicholas@2712
|
2877 playhead.scrubberTrack = document.createElement('div');
|
nicholas@2712
|
2878 playhead.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2498
|
2879
|
nicholas@2712
|
2880 playhead.scrubberHead = document.createElement('div');
|
nicholas@2712
|
2881 playhead.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2712
|
2882 playhead.scrubberTrack.appendChild(playhead.scrubberHead);
|
nicholas@2712
|
2883 playhead.object.appendChild(playhead.scrubberTrack);
|
nicholas@2498
|
2884
|
nicholas@2712
|
2885 playhead.timePerPixel = 0;
|
nicholas@2712
|
2886 playhead.maxTime = 0;
|
nicholas@2498
|
2887
|
nicholas@2712
|
2888 playhead.playbackObject = undefined;
|
nicholas@2498
|
2889
|
nicholas@2712
|
2890 playhead.setTimePerPixel = function (audioObject) {
|
nicholas@2498
|
2891 //maxTime must be in seconds
|
nicholas@2498
|
2892 this.playbackObject = audioObject;
|
nicholas@2498
|
2893 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2498
|
2894 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2498
|
2895 this.timePerPixel = this.maxTime / 490;
|
nicholas@2498
|
2896 if (this.maxTime < 60) {
|
nicholas@2498
|
2897 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
2898 } else {
|
nicholas@2498
|
2899 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2900 }
|
nicholas@2498
|
2901 };
|
nicholas@2498
|
2902
|
nicholas@2712
|
2903 playhead.update = function () {
|
nicholas@2498
|
2904 // Update the playhead position, startPlay must be called
|
nicholas@2498
|
2905 if (this.timePerPixel > 0) {
|
nicholas@2498
|
2906 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2498
|
2907 if (time > 0 && time < this.maxTime) {
|
nicholas@2498
|
2908 var width = 490;
|
nicholas@2498
|
2909 var pix = Math.floor(time / this.timePerPixel);
|
nicholas@2498
|
2910 this.scrubberHead.style.left = pix + 'px';
|
nicholas@2498
|
2911 if (this.maxTime > 60.0) {
|
nicholas@2498
|
2912 var secs = time % 60;
|
nicholas@2498
|
2913 var mins = Math.floor((time - secs) / 60);
|
nicholas@2498
|
2914 secs = secs.toString();
|
nicholas@2498
|
2915 secs = secs.substr(0, 2);
|
nicholas@2498
|
2916 mins = mins.toString();
|
nicholas@2498
|
2917 this.curTimeSpan.textContent = mins + ':' + secs;
|
nicholas@2498
|
2918 } else {
|
nicholas@2498
|
2919 time = time.toString();
|
nicholas@2498
|
2920 this.curTimeSpan.textContent = time.substr(0, 4);
|
nicholas@2498
|
2921 }
|
nicholas@2498
|
2922 } else {
|
nicholas@2498
|
2923 this.scrubberHead.style.left = '0px';
|
nicholas@2498
|
2924 if (this.maxTime < 60) {
|
nicholas@2498
|
2925 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
2926 } else {
|
nicholas@2498
|
2927 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2928 }
|
nicholas@2498
|
2929 }
|
nicholas@2498
|
2930 }
|
nicholas@2817
|
2931 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
2932 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2817
|
2933 }
|
nicholas@2498
|
2934 };
|
nicholas@2498
|
2935
|
nicholas@2712
|
2936 playhead.interval = undefined;
|
nicholas@2498
|
2937
|
nicholas@2712
|
2938 playhead.start = function () {
|
nicholas@2708
|
2939 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
2940 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2498
|
2941 }
|
nicholas@2498
|
2942 };
|
nicholas@2712
|
2943 playhead.stop = function () {
|
nicholas@2817
|
2944 this.timePerPixel = 0;
|
nicholas@2498
|
2945 };
|
nicholas@2712
|
2946 return playhead;
|
nicholas@2712
|
2947 })();
|
nicholas@2498
|
2948
|
nicholas@2712
|
2949 this.volume = (function () {
|
nicholas@2224
|
2950 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
2951 // Includes trackers on page-by-page data
|
nicholas@2224
|
2952 // Volume does NOT reset to 0dB on each page load
|
nicholas@2712
|
2953 var volume = {};
|
nicholas@2712
|
2954 volume.valueLin = 1.0;
|
nicholas@2712
|
2955 volume.valueDB = 0.0;
|
nicholas@2712
|
2956 volume.root = document.createElement('div');
|
nicholas@2712
|
2957 volume.root.id = 'master-volume-root';
|
nicholas@2712
|
2958 volume.object = document.createElement('div');
|
nicholas@2712
|
2959 volume.object.className = 'master-volume-holder-float';
|
nicholas@2712
|
2960 volume.object.appendChild(volume.root);
|
nicholas@2712
|
2961 volume.slider = document.createElement('input');
|
nicholas@2712
|
2962 volume.slider.id = 'master-volume-control';
|
nicholas@2712
|
2963 volume.slider.type = 'range';
|
nicholas@2712
|
2964 volume.valueText = document.createElement('span');
|
nicholas@2712
|
2965 volume.valueText.id = 'master-volume-feedback';
|
nicholas@2712
|
2966 volume.valueText.textContent = '0dB';
|
nicholas@2498
|
2967
|
nicholas@2712
|
2968 volume.slider.min = -60;
|
nicholas@2712
|
2969 volume.slider.max = 12;
|
nicholas@2712
|
2970 volume.slider.value = 0;
|
nicholas@2712
|
2971 volume.slider.step = 1;
|
nicholas@2712
|
2972 volume.handleEvent = function (event) {
|
nicholas@2669
|
2973 if (event.type == "mousemove") {
|
nicholas@2669
|
2974 this.valueDB = Number(this.slider.value);
|
nicholas@2669
|
2975 this.valueLin = decibelToLinear(this.valueDB);
|
nicholas@2669
|
2976 this.valueText.textContent = this.valueDB + 'dB';
|
nicholas@2669
|
2977 audioEngineContext.outputGain.gain.value = this.valueLin;
|
nicholas@2669
|
2978 } else if (event.type == "mouseup") {
|
nicholas@2669
|
2979 this.onmouseup();
|
nicholas@2669
|
2980 }
|
nicholas@2669
|
2981 this.slider.value = this.valueDB;
|
nicholas@2669
|
2982
|
nicholas@2669
|
2983 if (event.stopPropagation) {
|
nicholas@2669
|
2984 event.stopPropagation();
|
nicholas@2669
|
2985 }
|
nicholas@2711
|
2986 };
|
nicholas@2712
|
2987 volume.onmouseup = function () {
|
nicholas@2224
|
2988 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2708
|
2989 if (storePoint.length === 0) {
|
nicholas@2224
|
2990 storePoint = storage.document.createElement('metricresult');
|
nicholas@2498
|
2991 storePoint.setAttribute('name', 'volumeTracker');
|
nicholas@2224
|
2992 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2498
|
2993 } else {
|
nicholas@2224
|
2994 storePoint = storePoint[0];
|
nicholas@2224
|
2995 }
|
nicholas@2224
|
2996 var node = storage.document.createElement('movement');
|
nicholas@2498
|
2997 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
|
nicholas@2669
|
2998 node.setAttribute('volume', this.valueDB);
|
nicholas@2498
|
2999 node.setAttribute('format', 'dBFS');
|
nicholas@2224
|
3000 storePoint.appendChild(node);
|
nicholas@2711
|
3001 };
|
nicholas@2712
|
3002 volume.slider.addEventListener("mousemove", volume);
|
nicholas@2712
|
3003 volume.root.addEventListener("mouseup", volume);
|
nicholas@2498
|
3004
|
nicholas@2224
|
3005 var title = document.createElement('div');
|
nicholas@2224
|
3006 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
3007 title.style.fontSize = '0.75em';
|
nicholas@2224
|
3008 title.style.width = "100%";
|
nicholas@2224
|
3009 title.align = 'center';
|
nicholas@2712
|
3010 volume.root.appendChild(title);
|
nicholas@2498
|
3011
|
nicholas@2712
|
3012 volume.root.appendChild(volume.slider);
|
nicholas@2712
|
3013 volume.root.appendChild(volume.valueText);
|
nicholas@2498
|
3014
|
nicholas@2712
|
3015 volume.resize = function (event) {
|
nicholas@2352
|
3016 if (window.innerWidth < 1000) {
|
nicholas@2708
|
3017 this.object.className = "master-volume-holder-inline";
|
nicholas@2352
|
3018 } else {
|
nicholas@2352
|
3019 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
3020 }
|
nicholas@2708
|
3021 };
|
nicholas@2712
|
3022 return volume;
|
nicholas@2712
|
3023 })();
|
nicholas@2498
|
3024
|
nicholas@2778
|
3025 this.imageHolder = (function () {
|
nicholas@2778
|
3026 var imageController = {};
|
nicholas@2778
|
3027 imageController.root = document.createElement("div");
|
nicholas@2778
|
3028 imageController.root.id = "imageController";
|
nicholas@2778
|
3029 imageController.img = document.createElement("img");
|
nicholas@2778
|
3030 imageController.root.appendChild(imageController.img);
|
nicholas@2778
|
3031 imageController.setImage = function (src) {
|
nicholas@2778
|
3032 imageController.img.src = "";
|
n@2785
|
3033 if (typeof src !== "string" || src.length === undefined) {
|
nicholas@2778
|
3034 return;
|
nicholas@2778
|
3035 }
|
nicholas@2778
|
3036 imageController.img.src = src;
|
n@2785
|
3037 };
|
nicholas@2778
|
3038 return imageController;
|
nicholas@2778
|
3039 })();
|
nicholas@2778
|
3040
|
nicholas@2224
|
3041 this.calibrationModuleObject = null;
|
nicholas@2498
|
3042 this.calibrationModule = function () {
|
nicholas@2224
|
3043 // This creates an on-page calibration module
|
nicholas@2224
|
3044 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
3045 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
3046 // The calibration is a fixed state module
|
nicholas@2224
|
3047 this.calibrationNodes = [];
|
nicholas@2224
|
3048 this.holder = null;
|
nicholas@2498
|
3049 this.build = function (inject) {
|
nicholas@2224
|
3050 var f0 = 62.5;
|
nicholas@2224
|
3051 this.holder = document.createElement("div");
|
nicholas@2224
|
3052 this.holder.className = "calibration-holder";
|
nicholas@2224
|
3053 this.calibrationNodes = [];
|
nicholas@2498
|
3054 while (f0 < 20000) {
|
nicholas@2712
|
3055 /* jshint loopfunc: true */
|
nicholas@2224
|
3056 var obj = {
|
nicholas@2224
|
3057 root: document.createElement("div"),
|
nicholas@2224
|
3058 input: document.createElement("input"),
|
nicholas@2224
|
3059 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
3060 gain: audioContext.createGain(),
|
nicholas@2224
|
3061 f: f0,
|
nicholas@2224
|
3062 parent: this,
|
nicholas@2498
|
3063 handleEvent: function (event) {
|
nicholas@2498
|
3064 switch (event.type) {
|
nicholas@2224
|
3065 case "mouseenter":
|
nicholas@2224
|
3066 this.oscillator.start(0);
|
nicholas@2224
|
3067 break;
|
nicholas@2224
|
3068 case "mouseleave":
|
nicholas@2224
|
3069 this.oscillator.stop(0);
|
nicholas@2224
|
3070 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
3071 this.oscillator.connect(this.gain);
|
nicholas@2224
|
3072 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
3073 break;
|
nicholas@2224
|
3074 case "mousemove":
|
nicholas@2498
|
3075 var value = Math.pow(10, this.input.value / 20);
|
nicholas@2224
|
3076 if (this.f == 1000) {
|
nicholas@2224
|
3077 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
3078 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
3079 } else {
|
nicholas@2708
|
3080 this.gain.gain.value = value;
|
nicholas@2224
|
3081 }
|
nicholas@2224
|
3082 break;
|
nicholas@2224
|
3083 }
|
nicholas@2224
|
3084 },
|
nicholas@2498
|
3085 disconnect: function () {
|
nicholas@2224
|
3086 this.gain.disconnect();
|
nicholas@2224
|
3087 }
|
nicholas@2708
|
3088 };
|
nicholas@2224
|
3089 obj.root.className = "calibration-slider";
|
nicholas@2224
|
3090 obj.root.appendChild(obj.input);
|
nicholas@2224
|
3091 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
3092 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2498
|
3093 obj.gain.gain.value = Math.random() * 2;
|
nicholas@2224
|
3094 obj.input.value = obj.gain.gain.value;
|
nicholas@2498
|
3095 obj.input.setAttribute('orient', 'vertical');
|
nicholas@2224
|
3096 obj.input.type = "range";
|
nicholas@2593
|
3097 obj.input.min = -12;
|
nicholas@2593
|
3098 obj.input.max = 0;
|
nicholas@2224
|
3099 obj.input.step = 0.25;
|
nicholas@2224
|
3100 if (f0 != 1000) {
|
nicholas@2498
|
3101 obj.input.value = (Math.random() * 12) - 6;
|
nicholas@2224
|
3102 } else {
|
nicholas@2224
|
3103 obj.input.value = 0;
|
nicholas@2498
|
3104 obj.root.style.backgroundColor = "rgb(255,125,125)";
|
nicholas@2224
|
3105 }
|
nicholas@2498
|
3106 obj.input.addEventListener("mousemove", obj);
|
nicholas@2498
|
3107 obj.input.addEventListener("mouseenter", obj);
|
nicholas@2498
|
3108 obj.input.addEventListener("mouseleave", obj);
|
nicholas@2498
|
3109 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
|
nicholas@2224
|
3110 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
3111 this.calibrationNodes.push(obj);
|
nicholas@2224
|
3112 this.holder.appendChild(obj.root);
|
nicholas@2224
|
3113 f0 *= 2;
|
nicholas@2224
|
3114 }
|
nicholas@2224
|
3115 inject.appendChild(this.holder);
|
nicholas@2708
|
3116 };
|
nicholas@2498
|
3117 this.collect = function () {
|
nicholas@2708
|
3118 this.calibrationNodes.forEach(function (obj) {
|
nicholas@2224
|
3119 var node = storage.document.createElement("calibrationresult");
|
nicholas@2498
|
3120 node.setAttribute("frequency", obj.f);
|
nicholas@2498
|
3121 node.setAttribute("range-min", obj.input.min);
|
nicholas@2498
|
3122 node.setAttribute("range-max", obj.input.max);
|
nicholas@2498
|
3123 node.setAttribute("gain-lin", obj.gain.gain.value);
|
nicholas@2224
|
3124 this.storeDOM.appendChild(node);
|
nicholas@2708
|
3125 }, this);
|
nicholas@2708
|
3126 };
|
nicholas@2708
|
3127 };
|
nicholas@2498
|
3128
|
nicholas@2498
|
3129
|
nicholas@2498
|
3130 // Global Checkers
|
nicholas@2498
|
3131 // These functions will help enforce the checkers
|
n@2789
|
3132 this.checkHiddenAnchor = function (message) {
|
nicholas@2708
|
3133 var anchors = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3134 return ao.specification.type === "anchor";
|
nicholas@2708
|
3135 });
|
nicholas@2708
|
3136 var state = anchors.some(function (ao) {
|
nicholas@2708
|
3137 return (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3138 });
|
nicholas@2708
|
3139 if (state) {
|
nicholas@2708
|
3140 console.log('Anchor node not below marker value');
|
n@2789
|
3141 if (message) {
|
n@2789
|
3142 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3143 } else {
|
n@2789
|
3144 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3145 }
|
nicholas@2708
|
3146 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2708
|
3147 return false;
|
nicholas@2498
|
3148 }
|
nicholas@2498
|
3149 return true;
|
nicholas@2498
|
3150 };
|
nicholas@2498
|
3151
|
n@2789
|
3152 this.checkHiddenReference = function (message) {
|
nicholas@2708
|
3153 var references = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3154 return ao.specification.type === "reference";
|
nicholas@2708
|
3155 });
|
nicholas@2708
|
3156 var state = references.some(function (ao) {
|
nicholas@2708
|
3157 return (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3158 });
|
nicholas@2708
|
3159 if (state) {
|
nicholas@2708
|
3160 console.log('Reference node not below marker value');
|
n@2789
|
3161 if (message) {
|
n@2789
|
3162 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3163 } else {
|
n@2789
|
3164 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3165 }
|
nicholas@2708
|
3166 this.storeErrorNode('Reference node not below marker value');
|
nicholas@2708
|
3167 return false;
|
nicholas@2498
|
3168 }
|
nicholas@2498
|
3169 return true;
|
nicholas@2498
|
3170 };
|
nicholas@2498
|
3171
|
n@2789
|
3172 this.checkFragmentsFullyPlayed = function (message) {
|
nicholas@2498
|
3173 // Checks the entire file has been played back
|
nicholas@2498
|
3174 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2498
|
3175 if (audioEngineContext.loopPlayback) {
|
nicholas@2498
|
3176 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2498
|
3177 return true;
|
nicholas@2498
|
3178 }
|
nicholas@2498
|
3179 var check_pass = true;
|
nicholas@2708
|
3180 var error_obj = [],
|
nicholas@2708
|
3181 i;
|
nicholas@2708
|
3182 for (i = 0; i < audioEngineContext.audioObjects.length; i++) {
|
nicholas@2498
|
3183 var object = audioEngineContext.audioObjects[i];
|
nicholas@2498
|
3184 var time = object.buffer.buffer.duration;
|
nicholas@2498
|
3185 var metric = object.metric;
|
nicholas@2498
|
3186 var passed = false;
|
nicholas@2498
|
3187 for (var j = 0; j < metric.listenTracker.length; j++) {
|
nicholas@2498
|
3188 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
|
nicholas@2498
|
3189 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2498
|
3190 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2498
|
3191 var delta = stop_time - start_time;
|
nicholas@2498
|
3192 if (delta >= time) {
|
nicholas@2498
|
3193 passed = true;
|
nicholas@2498
|
3194 break;
|
nicholas@2498
|
3195 }
|
nicholas@2498
|
3196 }
|
nicholas@2708
|
3197 if (passed === false) {
|
nicholas@2498
|
3198 check_pass = false;
|
nicholas@2498
|
3199 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3200 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3201 }
|
nicholas@2498
|
3202 }
|
nicholas@2708
|
3203 if (check_pass === false) {
|
nicholas@2498
|
3204 var str_start = "You have not completely listened to fragments ";
|
nicholas@2708
|
3205 for (i = 0; i < error_obj.length; i++) {
|
nicholas@2498
|
3206 str_start += error_obj[i];
|
nicholas@2498
|
3207 if (i != error_obj.length - 1) {
|
nicholas@2498
|
3208 str_start += ', ';
|
nicholas@2498
|
3209 }
|
nicholas@2498
|
3210 }
|
nicholas@2498
|
3211 str_start += ". Please keep listening";
|
n@2789
|
3212 console.log(str_start);
|
n@2789
|
3213 this.storeErrorNode(str_start);
|
n@2789
|
3214 if (message) {
|
n@2789
|
3215 str_start = message;
|
n@2789
|
3216 }
|
nicholas@2498
|
3217 interfaceContext.lightbox.post("Error", str_start);
|
nicholas@2444
|
3218 return false;
|
nicholas@2498
|
3219 }
|
nicholas@2444
|
3220 return true;
|
nicholas@2498
|
3221 };
|
n@2789
|
3222 this.checkAllMoved = function (message) {
|
nicholas@2498
|
3223 var str = "You have not moved ";
|
nicholas@2498
|
3224 var failed = [];
|
nicholas@2708
|
3225 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3226 if (ao.metric.wasMoved === false && ao.interfaceDOM.canMove() === true) {
|
nicholas@2498
|
3227 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3228 }
|
nicholas@2708
|
3229 }, this);
|
nicholas@2708
|
3230 if (failed.length === 0) {
|
nicholas@2498
|
3231 return true;
|
nicholas@2498
|
3232 } else if (failed.length == 1) {
|
nicholas@2498
|
3233 str += 'track ' + failed[0];
|
nicholas@2498
|
3234 } else {
|
nicholas@2498
|
3235 str += 'tracks ';
|
nicholas@2498
|
3236 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3237 str += failed[i] + ', ';
|
nicholas@2498
|
3238 }
|
nicholas@2498
|
3239 str += 'and ' + failed[i];
|
nicholas@2498
|
3240 }
|
nicholas@2498
|
3241 str += '.';
|
nicholas@2498
|
3242 console.log(str);
|
nicholas@2224
|
3243 this.storeErrorNode(str);
|
n@2789
|
3244 if (message) {
|
n@2789
|
3245 str = message;
|
n@2789
|
3246 }
|
n@2789
|
3247 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3248 return false;
|
nicholas@2498
|
3249 };
|
n@2789
|
3250 this.checkAllPlayed = function (message) {
|
nicholas@2498
|
3251 var str = "You have not played ";
|
nicholas@2498
|
3252 var failed = [];
|
nicholas@2708
|
3253 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3254 if (ao.metric.wasListenedTo === false) {
|
nicholas@2498
|
3255 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3256 }
|
nicholas@2708
|
3257 }, this);
|
nicholas@2708
|
3258 if (failed.length === 0) {
|
nicholas@2498
|
3259 return true;
|
nicholas@2498
|
3260 } else if (failed.length == 1) {
|
nicholas@2498
|
3261 str += 'track ' + failed[0];
|
nicholas@2498
|
3262 } else {
|
nicholas@2498
|
3263 str += 'tracks ';
|
nicholas@2498
|
3264 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3265 str += failed[i] + ', ';
|
nicholas@2498
|
3266 }
|
nicholas@2498
|
3267 str += 'and ' + failed[i];
|
nicholas@2498
|
3268 }
|
nicholas@2498
|
3269 str += '.';
|
nicholas@2498
|
3270 console.log(str);
|
nicholas@2224
|
3271 this.storeErrorNode(str);
|
n@2789
|
3272 if (message) {
|
n@2789
|
3273 str = message;
|
n@2789
|
3274 }
|
n@2789
|
3275 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3276 return false;
|
nicholas@2498
|
3277 };
|
n@2789
|
3278 this.checkAllCommented = function (message) {
|
nicholas@2540
|
3279 var str = "You have not commented on all the fragments.";
|
nicholas@2540
|
3280 var cont = true,
|
nicholas@2540
|
3281 boxes = this.commentBoxes.boxes,
|
nicholas@2540
|
3282 numBoxes = boxes.length,
|
nicholas@2540
|
3283 i;
|
nicholas@2540
|
3284 for (i = 0; i < numBoxes; i++) {
|
nicholas@2540
|
3285 if (boxes[i].trackCommentBox.value === "") {
|
nicholas@2540
|
3286 console.log(str);
|
nicholas@2540
|
3287 this.storeErrorNode(str);
|
n@2789
|
3288 if (message) {
|
n@2789
|
3289 str = message;
|
n@2789
|
3290 }
|
n@2789
|
3291 interfaceContext.lightbox.post("Error", str);
|
nicholas@2540
|
3292 return false;
|
nicholas@2540
|
3293 }
|
nicholas@2540
|
3294 }
|
nicholas@2540
|
3295 return true;
|
nicholas@2708
|
3296 };
|
n@2789
|
3297 this.checkScaleRange = function (message) {
|
nicholas@2310
|
3298 var page = testState.getCurrentTestPage();
|
nicholas@2708
|
3299 var interfaceObject = page.interfaces;
|
nicholas@2310
|
3300 var state = true;
|
nicholas@2310
|
3301 var str = "Please keep listening. ";
|
nicholas@2708
|
3302 if (interfaceObject === undefined) {
|
nicholas@2708
|
3303 return true;
|
nicholas@2310
|
3304 }
|
nicholas@2708
|
3305 interfaceObject = interfaceObject[0];
|
nicholas@2708
|
3306 var scales = (function () {
|
nicholas@2708
|
3307 var scaleRange = interfaceObject.options.find(function (a) {
|
nicholas@2708
|
3308 return a.name == "scalerange";
|
nicholas@2708
|
3309 });
|
nicholas@2708
|
3310 return {
|
nicholas@2708
|
3311 min: scaleRange.min,
|
nicholas@2708
|
3312 max: scaleRange.max
|
nicholas@2708
|
3313 };
|
nicholas@2708
|
3314 })();
|
nicholas@2708
|
3315 var range = audioEngineContext.audioObjects.reduce(function (a, b) {
|
nicholas@2742
|
3316 var v = b.interfaceDOM.getValue() * 100.0;
|
nicholas@2708
|
3317 return {
|
nicholas@2708
|
3318 min: Math.min(a.min, v),
|
nicholas@2708
|
3319 max: Math.max(a.max, v)
|
nicholas@2712
|
3320 };
|
nicholas@2708
|
3321 }, {
|
nicholas@2708
|
3322 min: 100,
|
nicholas@2708
|
3323 max: 0
|
nicholas@2708
|
3324 });
|
nicholas@2708
|
3325 if (range.min > scales.min) {
|
nicholas@2742
|
3326 str += "At least one fragment must be below the " + scales.min + " mark.";
|
nicholas@2708
|
3327 state = false;
|
nicholas@2712
|
3328 } else if (range.max < scales.max) {
|
nicholas@2742
|
3329 str += "At least one fragment must be above the " + scales.max + " mark.";
|
nicholas@2310
|
3330 state = false;
|
nicholas@2310
|
3331 }
|
nicholas@2708
|
3332 if (state === false) {
|
nicholas@2310
|
3333 console.log(str);
|
nicholas@2310
|
3334 this.storeErrorNode(str);
|
n@2789
|
3335 if (message) {
|
n@2789
|
3336 str = message;
|
n@2789
|
3337 }
|
nicholas@2498
|
3338 interfaceContext.lightbox.post("Error", str);
|
nicholas@2310
|
3339 }
|
nicholas@2310
|
3340 return state;
|
nicholas@2708
|
3341 };
|
nicholas@2826
|
3342 this.checkFragmentMinPlays = function () {
|
nicholas@2826
|
3343 var failedObjects = audioEngineContext.audioObjects.filter(function (a) {
|
nicholas@2826
|
3344 var minPlays = a.specification.minNumberPlays || a.specification.parent.minNumberPlays || specification.minNumberPlays;
|
nicholas@2826
|
3345 if (minPlays === undefined || a.numberOfPlays >= minPlays) {
|
nicholas@2826
|
3346 return false;
|
nicholas@2826
|
3347 }
|
nicholas@2826
|
3348 return true;
|
nicholas@2826
|
3349 });
|
nicholas@2826
|
3350 if (failedObjects.length === 0) {
|
nicholas@2827
|
3351 return true;
|
nicholas@2826
|
3352 }
|
nicholas@2826
|
3353 var failedString = [];
|
nicholas@2826
|
3354 failedObjects.forEach(function (a) {
|
nicholas@2826
|
3355 failedString.push(a.interfaceDOM.getPresentedId());
|
nicholas@2826
|
3356 });
|
nicholas@2826
|
3357 var str = "You have not played fragments " + failedString.join(", ") + " enough. Please keep listening";
|
nicholas@2826
|
3358 interfaceContext.lightbox.post("Message", str);
|
nicholas@2826
|
3359 this.storeErrorNode(str);
|
nicholas@2827
|
3360 return false;
|
nicholas@2826
|
3361 };
|
nicholas@2826
|
3362
|
nicholas@2498
|
3363
|
nicholas@2849
|
3364 this.sortFragmentsByScore = function () {
|
nicholas@2849
|
3365 var elements = audioEngineContext.audioObjects.filter(function (elem) {
|
nicholas@2849
|
3366 return elem.specification.type !== "outside-reference";
|
nicholas@2849
|
3367 });
|
nicholas@2849
|
3368 var indexes = [];
|
nicholas@2849
|
3369 var i = 0;
|
nicholas@2849
|
3370 while (indexes.push(i++) < elements.length);
|
nicholas@2849
|
3371 return indexes.sort(function (x, y) {
|
nicholas@2849
|
3372 var a = elements[x].interfaceDOM.getValue();
|
nicholas@2849
|
3373 var b = elements[y].interfaceDOM.getValue();
|
nicholas@2849
|
3374 if (a > b) {
|
nicholas@2849
|
3375 return 1;
|
nicholas@2849
|
3376 } else if (a < b) {
|
nicholas@2849
|
3377 return -1;
|
nicholas@2849
|
3378 }
|
nicholas@2849
|
3379 return 0;
|
nicholas@2849
|
3380 }, elements[0].interfaceDOM.getValue());
|
nicholas@2849
|
3381 };
|
nicholas@2849
|
3382
|
nicholas@2498
|
3383 this.storeErrorNode = function (errorMessage) {
|
nicholas@2224
|
3384 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
3385 var node = storage.document.createElement('error');
|
nicholas@2498
|
3386 node.setAttribute('time', time);
|
nicholas@2224
|
3387 node.textContent = errorMessage;
|
nicholas@2224
|
3388 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
3389 };
|
nicholas@2595
|
3390
|
nicholas@2595
|
3391 this.getLabel = function (labelType, index, labelStart) {
|
nicholas@2595
|
3392 /*
|
nicholas@2595
|
3393 Get the correct label based on type, index and offset
|
nicholas@2595
|
3394 */
|
nicholas@2595
|
3395
|
nicholas@2595
|
3396 function calculateLabel(labelType, index, offset) {
|
nicholas@2595
|
3397 if (labelType == "none") {
|
nicholas@2595
|
3398 return "";
|
nicholas@2595
|
3399 }
|
nicholas@2595
|
3400 switch (labelType) {
|
nicholas@2595
|
3401 case "letter":
|
nicholas@2596
|
3402 return String.fromCharCode((index + offset) % 26 + 97);
|
nicholas@2595
|
3403 case "capital":
|
nicholas@2607
|
3404 return String.fromCharCode((index + offset) % 26 + 65);
|
nicholas@2625
|
3405 case "samediff":
|
nicholas@2708
|
3406 if (index === 0) {
|
nicholas@2625
|
3407 return "Same";
|
nicholas@2625
|
3408 } else if (index == 1) {
|
nicholas@2625
|
3409 return "Difference";
|
nicholas@2625
|
3410 }
|
nicholas@2708
|
3411 return "";
|
nicholas@2595
|
3412 case "number":
|
nicholas@2595
|
3413 return String(index + offset);
|
nicholas@2595
|
3414 default:
|
nicholas@2595
|
3415 return "";
|
nicholas@2595
|
3416 }
|
nicholas@2595
|
3417 }
|
nicholas@2595
|
3418
|
nicholas@2708
|
3419 if (typeof labelStart !== "string" || labelStart.length === 0) {
|
nicholas@2595
|
3420 labelStart = String.fromCharCode(0);
|
nicholas@2595
|
3421 }
|
nicholas@2595
|
3422
|
nicholas@2595
|
3423 switch (labelType) {
|
nicholas@2595
|
3424 case "letter":
|
nicholas@2595
|
3425 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3426 if (labelStart < 97 || labelStart > 122) {
|
nicholas@2595
|
3427 labelStart = 97;
|
nicholas@2595
|
3428 }
|
nicholas@2595
|
3429 labelStart -= 97;
|
nicholas@2595
|
3430 break;
|
nicholas@2595
|
3431 case "capital":
|
nicholas@2595
|
3432 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3433 if (labelStart < 65 || labelStart > 90) {
|
nicholas@2595
|
3434 labelStart = 65;
|
nicholas@2595
|
3435 }
|
nicholas@2595
|
3436 labelStart -= 65;
|
nicholas@2595
|
3437 break;
|
nicholas@2595
|
3438 case "number":
|
nicholas@2608
|
3439 labelStart = Number(labelStart);
|
nicholas@2608
|
3440 if (!isFinite(labelStart)) {
|
nicholas@2595
|
3441 labelStart = 1;
|
nicholas@2595
|
3442 }
|
nicholas@2595
|
3443 break;
|
nicholas@2595
|
3444 default:
|
nicholas@2596
|
3445 labelStart = 0;
|
nicholas@2595
|
3446 }
|
nicholas@2595
|
3447 if (typeof index == "number") {
|
nicholas@2595
|
3448 return calculateLabel(labelType, index, labelStart);
|
nicholas@2595
|
3449 } else if (index.length && index.length > 0) {
|
nicholas@2595
|
3450 var a = [],
|
nicholas@2595
|
3451 l = index.length,
|
nicholas@2595
|
3452 i;
|
nicholas@2595
|
3453 for (i = 0; i < l; i++) {
|
nicholas@2595
|
3454 a[i] = calculateLabel(labelType, index[i], labelStart);
|
nicholas@2595
|
3455 }
|
nicholas@2595
|
3456 return a;
|
nicholas@2595
|
3457 } else {
|
nicholas@2595
|
3458 throw ("Invalid arguments");
|
nicholas@2595
|
3459 }
|
nicholas@2708
|
3460 };
|
nicholas@2649
|
3461
|
nicholas@2649
|
3462 this.getCombinedInterfaces = function (page) {
|
nicholas@2649
|
3463 // Combine the interfaces with the global interface nodes
|
nicholas@2649
|
3464 var global = specification.interfaces,
|
nicholas@2649
|
3465 local = page.interfaces;
|
nicholas@2649
|
3466 local.forEach(function (locInt) {
|
nicholas@2649
|
3467 // Iterate through the options nodes
|
nicholas@2649
|
3468 var addList = [];
|
nicholas@2649
|
3469 global.options.forEach(function (gopt) {
|
nicholas@2649
|
3470 var lopt = locInt.options.find(function (lopt) {
|
nicholas@2649
|
3471 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
|
nicholas@2649
|
3472 });
|
nicholas@2649
|
3473 if (!lopt) {
|
nicholas@2649
|
3474 // Global option doesn't exist locally
|
nicholas@2649
|
3475 addList.push(gopt);
|
nicholas@2649
|
3476 }
|
nicholas@2649
|
3477 });
|
nicholas@2649
|
3478 locInt.options = locInt.options.concat(addList);
|
nicholas@2649
|
3479 if (!locInt.scales && global.scales) {
|
nicholas@2649
|
3480 // Use the global default scales
|
nicholas@2649
|
3481 locInt.scales = global.scales;
|
nicholas@2649
|
3482 }
|
nicholas@2649
|
3483 });
|
nicholas@2649
|
3484 return local;
|
nicholas@2708
|
3485 };
|
nicholas@2224
|
3486 }
|
nicholas@2224
|
3487
|
nicholas@2498
|
3488 function Storage() {
|
nicholas@2498
|
3489 // Holds results in XML format until ready for collection
|
nicholas@2498
|
3490 this.globalPreTest = null;
|
nicholas@2498
|
3491 this.globalPostTest = null;
|
nicholas@2498
|
3492 this.testPages = [];
|
nicholas@2498
|
3493 this.document = null;
|
nicholas@2498
|
3494 this.root = null;
|
nicholas@2498
|
3495 this.state = 0;
|
nicholas@2733
|
3496 var pFilenamePrefix = "save";
|
nicholas@2498
|
3497
|
nicholas@2498
|
3498 this.initialise = function (existingStore) {
|
nicholas@2708
|
3499 if (existingStore === undefined) {
|
nicholas@2224
|
3500 // We need to get the sessionKey
|
nicholas@2510
|
3501 this.SessionKey.requestKey();
|
nicholas@2498
|
3502 this.document = document.implementation.createDocument(null, "waetresult", null);
|
nicholas@2224
|
3503 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3504 var projectDocument = specification.projectXML;
|
nicholas@2708
|
3505 projectDocument.setAttribute('file-name', specification.url);
|
nicholas@2708
|
3506 projectDocument.setAttribute('url', qualifyURL(specification.url));
|
nicholas@2224
|
3507 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3508 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3509 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3510 } else {
|
nicholas@2224
|
3511 this.document = existingStore;
|
nicholas@2294
|
3512 this.root = existingStore.firstChild;
|
nicholas@2224
|
3513 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3514 }
|
nicholas@2708
|
3515 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3516 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
|
nicholas@2498
|
3517 }
|
nicholas@2708
|
3518 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3519 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
|
nicholas@2498
|
3520 }
|
nicholas@2498
|
3521 };
|
nicholas@2498
|
3522
|
nicholas@2224
|
3523 this.SessionKey = {
|
nicholas@2224
|
3524 key: null,
|
nicholas@2224
|
3525 request: new XMLHttpRequest(),
|
nicholas@2224
|
3526 parent: this,
|
nicholas@2498
|
3527 handleEvent: function () {
|
nicholas@2224
|
3528 var parse = new DOMParser();
|
nicholas@2498
|
3529 var xml = parse.parseFromString(this.request.response, "text/xml");
|
nicholas@2708
|
3530 if (this.request.response.length === 0) {
|
nicholas@2515
|
3531 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2376
|
3532 return;
|
nicholas@2376
|
3533 }
|
nicholas@2498
|
3534 if (xml.getElementsByTagName("state").length > 0) {
|
nicholas@2498
|
3535 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
nicholas@2498
|
3536 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
nicholas@2498
|
3537 this.parent.root.setAttribute("key", this.key);
|
nicholas@2498
|
3538 this.parent.root.setAttribute("state", "empty");
|
nicholas@2516
|
3539 this.update();
|
nicholas@2515
|
3540 return;
|
nicholas@2514
|
3541 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
|
nicholas@2515
|
3542 this.key = null;
|
nicholas@2514
|
3543 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
|
nicholas@2515
|
3544 return;
|
nicholas@2498
|
3545 }
|
nicholas@2498
|
3546 }
|
nicholas@2515
|
3547 this.key = null;
|
nicholas@2515
|
3548 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2224
|
3549 },
|
nicholas@2510
|
3550 requestKey: function () {
|
nicholas@2510
|
3551 // For new servers, request a new key from the server
|
nicholas@2510
|
3552 var returnURL = "";
|
nicholas@2510
|
3553 if (typeof specification.projectReturn == "string") {
|
nicholas@2510
|
3554 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2510
|
3555 returnURL = specification.projectReturn;
|
nicholas@2510
|
3556 }
|
nicholas@2510
|
3557 }
|
nicholas@2510
|
3558 this.request.open("GET", returnURL + "php/requestKey.php", true);
|
nicholas@2510
|
3559 this.request.addEventListener("load", this);
|
nicholas@2510
|
3560 this.request.send();
|
nicholas@2510
|
3561 },
|
nicholas@2498
|
3562 update: function () {
|
nicholas@2708
|
3563 if (this.key === null) {
|
nicholas@2357
|
3564 console.log("Cannot save as key == null");
|
nicholas@2357
|
3565 return;
|
nicholas@2357
|
3566 }
|
nicholas@2498
|
3567 this.parent.root.setAttribute("state", "update");
|
nicholas@2224
|
3568 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3569 var returnURL = "";
|
nicholas@2302
|
3570 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
3571 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2302
|
3572 returnURL = specification.projectReturn;
|
nicholas@2302
|
3573 }
|
nicholas@2302
|
3574 }
|
nicholas@2722
|
3575 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=" + this.parent.filenamePrefix);
|
nicholas@2224
|
3576 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2498
|
3577 xmlhttp.onerror = function () {
|
nicholas@2224
|
3578 console.log('Error updating file to server!');
|
nicholas@2224
|
3579 };
|
nicholas@2224
|
3580 var hold = document.createElement("div");
|
nicholas@2224
|
3581 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3582 hold.appendChild(clone);
|
nicholas@2498
|
3583 xmlhttp.onload = function () {
|
nicholas@2224
|
3584 if (this.status >= 300) {
|
nicholas@2224
|
3585 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3586 } else {
|
nicholas@2224
|
3587 var parser = new DOMParser();
|
nicholas@2224
|
3588 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3589 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3590 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3591 var file = response.getElementsByTagName("file")[0];
|
nicholas@2498
|
3592 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2224
|
3593 } else {
|
nicholas@2224
|
3594 var message = response.getElementsByTagName("message");
|
nicholas@2498
|
3595 console.log("Intermediate save: Error! " + message.textContent);
|
nicholas@2224
|
3596 }
|
nicholas@2224
|
3597 }
|
nicholas@2708
|
3598 };
|
nicholas@2224
|
3599 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
3600 },
|
nicholas@2723
|
3601 finish: function () {
|
nicholas@2723
|
3602 // Final upload to complete the test
|
nicholas@2723
|
3603 this.parent.finish();
|
nicholas@2723
|
3604 var hold = document.createElement("div");
|
nicholas@2723
|
3605 var clone = this.parent.root.cloneNode(true);
|
nicholas@2723
|
3606 hold.appendChild(clone);
|
nicholas@2733
|
3607 var saveURL = specification.returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=";
|
nicholas@2742
|
3608 if (this.parent.filenamePrefix.length === 0) {
|
nicholas@2733
|
3609 saveURL += "save";
|
nicholas@2733
|
3610 } else {
|
nicholas@2733
|
3611 saveURL += this.parent.filenamePrefix;
|
nicholas@2733
|
3612 }
|
nicholas@2723
|
3613 return new Promise(function (resolve, reject) {
|
nicholas@2723
|
3614 var xmlhttp = new XMLHttpRequest();
|
nicholas@2723
|
3615 xmlhttp.open("POST", saveURL);
|
nicholas@2723
|
3616 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2723
|
3617 xmlhttp.onerror = function () {
|
nicholas@2723
|
3618 console.log('Error updating file to server!');
|
nicholas@2723
|
3619 createProjectSave("local");
|
nicholas@2723
|
3620 };
|
nicholas@2723
|
3621 xmlhttp.onload = function () {
|
nicholas@2723
|
3622 if (this.status >= 300) {
|
nicholas@2723
|
3623 console.log("WARNING - Could not update at this time");
|
nicholas@2723
|
3624 createProjectSave("local");
|
nicholas@2723
|
3625 } else {
|
nicholas@2723
|
3626 var parser = new DOMParser();
|
nicholas@2723
|
3627 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2723
|
3628 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2723
|
3629 if (response.getAttribute("state") == "OK") {
|
nicholas@2723
|
3630 var file = response.getElementsByTagName("file")[0];
|
nicholas@2723
|
3631 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2723
|
3632 resolve(response);
|
nicholas@2723
|
3633 } else {
|
nicholas@2723
|
3634 var message = response.getElementsByTagName("message");
|
nicholas@2723
|
3635 reject(message);
|
nicholas@2723
|
3636 }
|
nicholas@2723
|
3637 }
|
nicholas@2723
|
3638 };
|
nicholas@2723
|
3639 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
3640 });
|
nicholas@2224
|
3641 }
|
nicholas@2708
|
3642 };
|
nicholas@2498
|
3643
|
nicholas@2498
|
3644 this.createTestPageStore = function (specification) {
|
nicholas@2498
|
3645 var store = new this.pageNode(this, specification);
|
nicholas@2498
|
3646 this.testPages.push(store);
|
nicholas@2498
|
3647 return this.testPages[this.testPages.length - 1];
|
nicholas@2498
|
3648 };
|
nicholas@2498
|
3649
|
nicholas@2498
|
3650 this.surveyNode = function (parent, root, specification) {
|
nicholas@2498
|
3651 this.specification = specification;
|
nicholas@2498
|
3652 this.parent = parent;
|
nicholas@2224
|
3653 this.state = "empty";
|
nicholas@2498
|
3654 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2498
|
3655 this.XMLDOM.setAttribute('location', this.specification.location);
|
nicholas@2498
|
3656 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3657 this.specification.options.forEach(function (optNode) {
|
nicholas@2498
|
3658 if (optNode.type != 'statement') {
|
nicholas@2498
|
3659 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2498
|
3660 node.setAttribute("ref", optNode.id);
|
nicholas@2498
|
3661 node.setAttribute('type', optNode.type);
|
nicholas@2498
|
3662 this.XMLDOM.appendChild(node);
|
nicholas@2498
|
3663 }
|
nicholas@2708
|
3664 }, this);
|
nicholas@2498
|
3665 root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3666
|
nicholas@2498
|
3667 this.postResult = function (node) {
|
nicholas@2708
|
3668 function postNumber(doc, value) {
|
nicholas@2708
|
3669 var child = doc.createElement("response");
|
nicholas@2708
|
3670 child.textContent = value;
|
nicholas@2708
|
3671 return child;
|
nicholas@2708
|
3672 }
|
nicholas@2708
|
3673
|
nicholas@2708
|
3674 function postRadio(doc, node) {
|
nicholas@2708
|
3675 var child = doc.createElement('response');
|
nicholas@2708
|
3676 if (node.response !== null) {
|
nicholas@2708
|
3677 child.setAttribute('name', node.response.name);
|
nicholas@2708
|
3678 child.textContent = node.response.text;
|
nicholas@2708
|
3679 }
|
nicholas@2708
|
3680 return child;
|
nicholas@2708
|
3681 }
|
nicholas@2708
|
3682
|
nicholas@2708
|
3683 function postCheckbox(doc, node) {
|
nicholas@2708
|
3684 var checkNode = doc.createElement('response');
|
nicholas@2708
|
3685 checkNode.setAttribute('name', node.name);
|
nicholas@2708
|
3686 checkNode.setAttribute('checked', node.checked);
|
nicholas@2708
|
3687 return checkNode;
|
nicholas@2708
|
3688 }
|
nicholas@2498
|
3689 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2498
|
3690 // ID is the position
|
nicholas@2498
|
3691 if (node.specification.type == 'statement') {
|
nicholas@2498
|
3692 return;
|
nicholas@2498
|
3693 }
|
nicholas@2498
|
3694 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2708
|
3695 while (surveyresult !== null) {
|
nicholas@2498
|
3696 if (surveyresult.getAttribute("ref") == node.specification.id) {
|
nicholas@2224
|
3697 break;
|
nicholas@2224
|
3698 }
|
nicholas@2224
|
3699 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3700 }
|
nicholas@2775
|
3701 surveyresult.setAttribute("duration", node.elapsedTime);
|
nicholas@2498
|
3702 switch (node.specification.type) {
|
nicholas@2498
|
3703 case "number":
|
nicholas@2498
|
3704 case "question":
|
n@2583
|
3705 case "slider":
|
nicholas@2708
|
3706 surveyresult.appendChild(postNumber(this.parent.document, node.response));
|
nicholas@2464
|
3707 break;
|
nicholas@2498
|
3708 case "radio":
|
nicholas@2708
|
3709 surveyresult.appendChild(postRadio(this.parent.document, node));
|
nicholas@2498
|
3710 break;
|
nicholas@2498
|
3711 case "checkbox":
|
nicholas@2708
|
3712 if (node.response === undefined) {
|
nicholas@2498
|
3713 surveyresult.appendChild(this.parent.document.createElement('response'));
|
nicholas@2498
|
3714 break;
|
nicholas@2498
|
3715 }
|
nicholas@2498
|
3716 for (var i = 0; i < node.response.length; i++) {
|
nicholas@2708
|
3717 surveyresult.appendChild(postCheckbox(this.parent.document, node.response[i]));
|
nicholas@2498
|
3718 }
|
nicholas@2498
|
3719 break;
|
nicholas@2498
|
3720 }
|
nicholas@2498
|
3721 };
|
nicholas@2498
|
3722 this.complete = function () {
|
nicholas@2498
|
3723 this.state = "complete";
|
nicholas@2498
|
3724 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3725 };
|
nicholas@2498
|
3726 };
|
nicholas@2498
|
3727
|
nicholas@2498
|
3728 this.pageNode = function (parent, specification) {
|
nicholas@2498
|
3729 // Create one store per test page
|
nicholas@2498
|
3730 this.specification = specification;
|
nicholas@2498
|
3731 this.parent = parent;
|
nicholas@2498
|
3732 this.state = "empty";
|
nicholas@2498
|
3733 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2498
|
3734 this.XMLDOM.setAttribute('ref', specification.id);
|
nicholas@2498
|
3735 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
|
nicholas@2498
|
3736 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3737 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3738 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
|
nicholas@2498
|
3739 }
|
nicholas@2708
|
3740 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3741 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
|
nicholas@2498
|
3742 }
|
nicholas@2498
|
3743
|
nicholas@2498
|
3744 // Add any page metrics
|
nicholas@2498
|
3745 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3746 this.XMLDOM.appendChild(page_metric);
|
nicholas@2498
|
3747
|
nicholas@2498
|
3748 // Add the audioelement
|
nicholas@2708
|
3749 this.specification.audioElements.forEach(function (element) {
|
nicholas@2498
|
3750 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2498
|
3751 aeNode.setAttribute('ref', element.id);
|
nicholas@2708
|
3752 if (element.name !== undefined) {
|
nicholas@2708
|
3753 aeNode.setAttribute('name', element.name);
|
nicholas@2708
|
3754 }
|
nicholas@2498
|
3755 aeNode.setAttribute('type', element.type);
|
nicholas@2498
|
3756 aeNode.setAttribute('url', element.url);
|
nicholas@2498
|
3757 aeNode.setAttribute('fqurl', qualifyURL(element.url));
|
nicholas@2498
|
3758 aeNode.setAttribute('gain', element.gain);
|
nicholas@2498
|
3759 if (element.type == 'anchor' || element.type == 'reference') {
|
nicholas@2498
|
3760 if (element.marker > 0) {
|
nicholas@2498
|
3761 aeNode.setAttribute('marker', element.marker);
|
nicholas@2464
|
3762 }
|
nicholas@2498
|
3763 }
|
nicholas@2498
|
3764 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3765 aeNode.appendChild(ae_metric);
|
nicholas@2498
|
3766 this.XMLDOM.appendChild(aeNode);
|
nicholas@2708
|
3767 }, this);
|
nicholas@2498
|
3768
|
nicholas@2498
|
3769 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3770
|
nicholas@2498
|
3771 this.complete = function () {
|
nicholas@2224
|
3772 this.state = "complete";
|
nicholas@2498
|
3773 this.XMLDOM.setAttribute("state", "complete");
|
nicholas@2708
|
3774 };
|
nicholas@2498
|
3775 };
|
nicholas@2498
|
3776 this.update = function () {
|
nicholas@2224
|
3777 this.SessionKey.update();
|
nicholas@2708
|
3778 };
|
nicholas@2498
|
3779 this.finish = function () {
|
nicholas@2498
|
3780 this.state = 1;
|
nicholas@2498
|
3781 this.root.setAttribute("state", "complete");
|
nicholas@2498
|
3782 return this.root;
|
nicholas@2498
|
3783 };
|
nicholas@2722
|
3784
|
nicholas@2722
|
3785 Object.defineProperties(this, {
|
nicholas@2722
|
3786 'filenamePrefix': {
|
nicholas@2722
|
3787 'get': function () {
|
nicholas@2722
|
3788 return pFilenamePrefix;
|
nicholas@2722
|
3789 },
|
nicholas@2722
|
3790 'set': function (value) {
|
nicholas@2722
|
3791 if (typeof value !== "string") {
|
nicholas@2722
|
3792 value = String(value);
|
nicholas@2722
|
3793 }
|
nicholas@2722
|
3794 pFilenamePrefix = value;
|
nicholas@2722
|
3795 return value;
|
nicholas@2722
|
3796 }
|
nicholas@2722
|
3797 }
|
nicholas@2725
|
3798 });
|
nicholas@2224
|
3799 }
|
nicholas@2384
|
3800
|
nicholas@2401
|
3801 var window_depedancy_callback;
|
nicholas@2498
|
3802 window_depedancy_callback = window.setInterval(function () {
|
nicholas@2401
|
3803 if (check_dependancies()) {
|
nicholas@2401
|
3804 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
3805 onload();
|
nicholas@2401
|
3806 } else {
|
nicholas@2401
|
3807 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
3808 }
|
nicholas@2498
|
3809 }, 100);
|