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