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