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