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