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