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