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@2498
|
2432 this.commentBoxes = new function () {
|
nicholas@2224
|
2433 this.boxes = [];
|
nicholas@2224
|
2434 this.injectPoint = null;
|
nicholas@2498
|
2435 this.elementCommentBox = function (audioObject) {
|
nicholas@2224
|
2436 var element = audioObject.specification;
|
nicholas@2224
|
2437 this.audioObject = audioObject;
|
nicholas@2224
|
2438 this.id = audioObject.id;
|
nicholas@2224
|
2439 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
2440 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2441 this.trackComment = document.createElement('div');
|
nicholas@2224
|
2442 this.trackComment.className = 'comment-div';
|
nicholas@2498
|
2443 this.trackComment.id = 'comment-div-' + audioObject.id;
|
nicholas@2224
|
2444 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2445 this.trackString = document.createElement('span');
|
nicholas@2498
|
2446 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
2447 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2448 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
2449 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
2450 this.trackCommentBox.cols = '100';
|
nicholas@2498
|
2451 this.trackCommentBox.name = 'trackComment' + audioObject.id;
|
nicholas@2224
|
2452 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
2453 var br = document.createElement('br');
|
nicholas@2224
|
2454 // Add to the holder.
|
nicholas@2224
|
2455 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
2456 this.trackComment.appendChild(br);
|
nicholas@2224
|
2457 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
2458
|
nicholas@2498
|
2459 this.exportXMLDOM = function () {
|
nicholas@2224
|
2460 var root = document.createElement('comment');
|
nicholas@2224
|
2461 var question = document.createElement('question');
|
nicholas@2224
|
2462 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2463 var response = document.createElement('response');
|
nicholas@2224
|
2464 response.textContent = this.trackCommentBox.value;
|
nicholas@2498
|
2465 console.log("Comment frag-" + this.id + ": " + response.textContent);
|
nicholas@2224
|
2466 root.appendChild(question);
|
nicholas@2224
|
2467 root.appendChild(response);
|
nicholas@2224
|
2468 return root;
|
nicholas@2224
|
2469 };
|
nicholas@2498
|
2470 this.resize = function () {
|
nicholas@2498
|
2471 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2472 if (boxwidth >= 600) {
|
nicholas@2224
|
2473 boxwidth = 600;
|
nicholas@2498
|
2474 } else if (boxwidth < 400) {
|
nicholas@2224
|
2475 boxwidth = 400;
|
nicholas@2224
|
2476 }
|
nicholas@2498
|
2477 this.trackComment.style.width = boxwidth + "px";
|
nicholas@2498
|
2478 this.trackCommentBox.style.width = boxwidth - 6 + "px";
|
nicholas@2224
|
2479 };
|
nicholas@2224
|
2480 this.resize();
|
nicholas@2224
|
2481 };
|
nicholas@2498
|
2482 this.createCommentBox = function (audioObject) {
|
nicholas@2224
|
2483 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2484 this.boxes.push(node);
|
nicholas@2224
|
2485 audioObject.commentDOM = node;
|
nicholas@2224
|
2486 return node;
|
nicholas@2224
|
2487 };
|
nicholas@2498
|
2488 this.sortCommentBoxes = function () {
|
nicholas@2498
|
2489 this.boxes.sort(function (a, b) {
|
nicholas@2498
|
2490 return a.id - b.id;
|
nicholas@2498
|
2491 });
|
nicholas@2224
|
2492 };
|
nicholas@2224
|
2493
|
nicholas@2498
|
2494 this.showCommentBoxes = function (inject, sort) {
|
nicholas@2224
|
2495 this.injectPoint = inject;
|
nicholas@2498
|
2496 if (sort) {
|
nicholas@2498
|
2497 this.sortCommentBoxes();
|
nicholas@2498
|
2498 }
|
nicholas@2682
|
2499 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2500 inject.appendChild(box.trackComment);
|
nicholas@2682
|
2501 });
|
nicholas@2224
|
2502 };
|
nicholas@2224
|
2503
|
nicholas@2498
|
2504 this.deleteCommentBoxes = function () {
|
nicholas@2678
|
2505 if (this.injectPoint !== null) {
|
nicholas@2682
|
2506 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2507 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2682
|
2508 }, this);
|
nicholas@2224
|
2509 this.injectPoint = null;
|
nicholas@2224
|
2510 }
|
nicholas@2224
|
2511 this.boxes = [];
|
nicholas@2224
|
2512 };
|
nicholas@2678
|
2513 };
|
nicholas@2498
|
2514
|
nicholas@2498
|
2515 this.commentQuestions = [];
|
nicholas@2498
|
2516
|
nicholas@2498
|
2517 this.commentBox = function (commentQuestion) {
|
nicholas@2498
|
2518 this.specification = commentQuestion;
|
nicholas@2498
|
2519 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2520 this.holder = document.createElement('div');
|
nicholas@2498
|
2521 this.holder.className = 'comment-div';
|
nicholas@2498
|
2522 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2523 this.string = document.createElement('span');
|
nicholas@2498
|
2524 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2525 // Create the HTML5 comment box 'textarea'
|
nicholas@2498
|
2526 this.textArea = document.createElement('textarea');
|
nicholas@2498
|
2527 this.textArea.rows = '4';
|
nicholas@2498
|
2528 this.textArea.cols = '100';
|
nicholas@2498
|
2529 this.textArea.className = 'trackComment';
|
nicholas@2498
|
2530 var br = document.createElement('br');
|
nicholas@2498
|
2531 // Add to the holder.
|
nicholas@2498
|
2532 this.holder.appendChild(this.string);
|
nicholas@2498
|
2533 this.holder.appendChild(br);
|
nicholas@2498
|
2534 this.holder.appendChild(this.textArea);
|
nicholas@2498
|
2535
|
nicholas@2498
|
2536 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2537 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2538 root.id = this.specification.id;
|
nicholas@2498
|
2539 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2540 console.log("Question: " + this.string.textContent);
|
nicholas@2498
|
2541 console.log("Response: " + root.textContent);
|
nicholas@2224
|
2542 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2543 question.textContent = this.string.textContent;
|
nicholas@2224
|
2544 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2545 response.textContent = this.textArea.value;
|
nicholas@2224
|
2546 root.appendChild(question);
|
nicholas@2224
|
2547 root.appendChild(response);
|
nicholas@2224
|
2548 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2549 return root;
|
nicholas@2498
|
2550 };
|
nicholas@2498
|
2551 this.resize = function () {
|
nicholas@2498
|
2552 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2553 if (boxwidth >= 600) {
|
nicholas@2498
|
2554 boxwidth = 600;
|
nicholas@2498
|
2555 } else if (boxwidth < 400) {
|
nicholas@2498
|
2556 boxwidth = 400;
|
nicholas@2498
|
2557 }
|
nicholas@2498
|
2558 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2559 this.textArea.style.width = boxwidth - 6 + "px";
|
nicholas@2498
|
2560 };
|
nicholas@2498
|
2561 this.resize();
|
nicholas@2498
|
2562 };
|
nicholas@2498
|
2563
|
nicholas@2498
|
2564 this.radioBox = function (commentQuestion) {
|
nicholas@2498
|
2565 this.specification = commentQuestion;
|
nicholas@2498
|
2566 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2567 this.holder = document.createElement('div');
|
nicholas@2498
|
2568 this.holder.className = 'comment-div';
|
nicholas@2498
|
2569 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2570 this.string = document.createElement('span');
|
nicholas@2498
|
2571 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2572 // Add to the holder.
|
nicholas@2498
|
2573 this.holder.appendChild(this.string);
|
nicholas@2498
|
2574 this.options = [];
|
nicholas@2498
|
2575 this.inputs = document.createElement('div');
|
nicholas@2711
|
2576 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2577
|
nicholas@2498
|
2578 var optCount = commentQuestion.options.length;
|
nicholas@2711
|
2579 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2580 var div = document.createElement('div');
|
nicholas@2711
|
2581 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2711
|
2582
|
nicholas@2711
|
2583 var span = document.createElement('span');
|
nicholas@2711
|
2584 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2585 span.className = 'comment-radio-span';
|
nicholas@2711
|
2586 div.appendChild(span);
|
nicholas@2711
|
2587
|
nicholas@2498
|
2588 var input = document.createElement('input');
|
nicholas@2498
|
2589 input.type = 'radio';
|
nicholas@2498
|
2590 input.name = commentQuestion.id;
|
nicholas@2711
|
2591 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2592 input.className = 'comment-radio';
|
nicholas@2498
|
2593 div.appendChild(input);
|
nicholas@2711
|
2594
|
nicholas@2498
|
2595 this.inputs.appendChild(div);
|
nicholas@2498
|
2596 this.options.push(input);
|
nicholas@2711
|
2597 }
|
nicholas@2498
|
2598 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2599
|
nicholas@2498
|
2600 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2601 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2602 root.id = this.specification.id;
|
nicholas@2498
|
2603 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2604 var question = document.createElement('question');
|
nicholas@2498
|
2605 question.textContent = this.string.textContent;
|
nicholas@2498
|
2606 var response = document.createElement('response');
|
nicholas@2498
|
2607 var i = 0;
|
nicholas@2678
|
2608 while (this.options[i].checked === false) {
|
nicholas@2498
|
2609 i++;
|
nicholas@2498
|
2610 if (i >= this.options.length) {
|
nicholas@2498
|
2611 break;
|
nicholas@2498
|
2612 }
|
nicholas@2498
|
2613 }
|
nicholas@2498
|
2614 if (i >= this.options.length) {
|
nicholas@2498
|
2615 response.textContent = 'null';
|
nicholas@2498
|
2616 } else {
|
nicholas@2498
|
2617 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2498
|
2618 response.setAttribute('number', i);
|
nicholas@2498
|
2619 }
|
nicholas@2498
|
2620 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2621 console.log('Response: ' + response.textContent);
|
nicholas@2498
|
2622 root.appendChild(question);
|
nicholas@2498
|
2623 root.appendChild(response);
|
nicholas@2224
|
2624 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2625 return root;
|
nicholas@2498
|
2626 };
|
nicholas@2498
|
2627 this.resize = function () {
|
nicholas@2498
|
2628 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2629 if (boxwidth >= 600) {
|
nicholas@2498
|
2630 boxwidth = 600;
|
nicholas@2498
|
2631 } else if (boxwidth < 400) {
|
nicholas@2498
|
2632 boxwidth = 400;
|
nicholas@2498
|
2633 }
|
nicholas@2498
|
2634 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2635 };
|
nicholas@2498
|
2636 this.resize();
|
nicholas@2498
|
2637 };
|
nicholas@2498
|
2638
|
nicholas@2498
|
2639 this.checkboxBox = function (commentQuestion) {
|
nicholas@2498
|
2640 this.specification = commentQuestion;
|
nicholas@2498
|
2641 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2642 this.holder = document.createElement('div');
|
nicholas@2498
|
2643 this.holder.className = 'comment-div';
|
nicholas@2498
|
2644 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2645 this.string = document.createElement('span');
|
nicholas@2498
|
2646 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2647 // Add to the holder.
|
nicholas@2498
|
2648 this.holder.appendChild(this.string);
|
nicholas@2498
|
2649 this.options = [];
|
nicholas@2498
|
2650 this.inputs = document.createElement('div');
|
nicholas@2294
|
2651 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2652
|
nicholas@2498
|
2653 var optCount = commentQuestion.options.length;
|
nicholas@2498
|
2654 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2655 var div = document.createElement('div');
|
nicholas@2711
|
2656 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2711
|
2657
|
nicholas@2711
|
2658 var span = document.createElement('span');
|
nicholas@2711
|
2659 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2660 span.className = 'comment-radio-span';
|
nicholas@2711
|
2661 div.appendChild(span);
|
nicholas@2711
|
2662
|
nicholas@2498
|
2663 var input = document.createElement('input');
|
nicholas@2498
|
2664 input.type = 'checkbox';
|
nicholas@2498
|
2665 input.name = commentQuestion.id;
|
nicholas@2498
|
2666 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2667 input.className = 'comment-radio';
|
nicholas@2498
|
2668 div.appendChild(input);
|
nicholas@2711
|
2669
|
nicholas@2498
|
2670 this.inputs.appendChild(div);
|
nicholas@2498
|
2671 this.options.push(input);
|
nicholas@2498
|
2672 }
|
nicholas@2498
|
2673 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2674
|
nicholas@2498
|
2675 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2676 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2677 root.id = this.specification.id;
|
nicholas@2498
|
2678 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2679 var question = document.createElement('question');
|
nicholas@2498
|
2680 question.textContent = this.string.textContent;
|
nicholas@2498
|
2681 root.appendChild(question);
|
nicholas@2498
|
2682 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2683 for (var i = 0; i < this.options.length; i++) {
|
nicholas@2498
|
2684 var response = document.createElement('response');
|
nicholas@2498
|
2685 response.textContent = this.options[i].checked;
|
nicholas@2498
|
2686 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
|
nicholas@2498
|
2687 root.appendChild(response);
|
nicholas@2498
|
2688 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
|
nicholas@2498
|
2689 }
|
nicholas@2224
|
2690 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2691 return root;
|
nicholas@2498
|
2692 };
|
nicholas@2498
|
2693 this.resize = function () {
|
nicholas@2498
|
2694 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2695 if (boxwidth >= 600) {
|
nicholas@2498
|
2696 boxwidth = 600;
|
nicholas@2498
|
2697 } else if (boxwidth < 400) {
|
nicholas@2498
|
2698 boxwidth = 400;
|
nicholas@2498
|
2699 }
|
nicholas@2498
|
2700 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2701 };
|
nicholas@2498
|
2702 this.resize();
|
nicholas@2498
|
2703 };
|
nicholas@2498
|
2704
|
n@2579
|
2705 this.sliderBox = function (commentQuestion) {
|
n@2579
|
2706 this.specification = commentQuestion;
|
n@2579
|
2707 this.holder = document.createElement("div");
|
n@2579
|
2708 this.holder.className = 'comment-div';
|
n@2579
|
2709 this.string = document.createElement("span");
|
n@2579
|
2710 this.string.innerHTML = commentQuestion.statement;
|
n@2579
|
2711 this.slider = document.createElement("input");
|
n@2579
|
2712 this.slider.type = "range";
|
n@2579
|
2713 this.slider.min = commentQuestion.min;
|
n@2579
|
2714 this.slider.max = commentQuestion.max;
|
n@2579
|
2715 this.slider.step = commentQuestion.step;
|
n@2579
|
2716 this.slider.value = commentQuestion.value;
|
n@2579
|
2717 var br = document.createElement('br');
|
n@2579
|
2718
|
n@2580
|
2719 var textHolder = document.createElement("div");
|
n@2580
|
2720 textHolder.className = "comment-slider-text-holder";
|
n@2580
|
2721
|
n@2580
|
2722 this.leftText = document.createElement("span");
|
n@2580
|
2723 this.leftText.textContent = commentQuestion.leftText;
|
n@2580
|
2724 this.rightText = document.createElement("span");
|
n@2580
|
2725 this.rightText.textContent = commentQuestion.rightText;
|
n@2580
|
2726 textHolder.appendChild(this.leftText);
|
n@2580
|
2727 textHolder.appendChild(this.rightText);
|
n@2580
|
2728
|
n@2579
|
2729 this.holder.appendChild(this.string);
|
n@2579
|
2730 this.holder.appendChild(br);
|
n@2579
|
2731 this.holder.appendChild(this.slider);
|
n@2580
|
2732 this.holder.appendChild(textHolder);
|
n@2579
|
2733
|
n@2579
|
2734 this.exportXMLDOM = function (storePoint) {
|
n@2579
|
2735 var root = storePoint.parent.document.createElement('comment');
|
n@2579
|
2736 root.id = this.specification.id;
|
n@2579
|
2737 root.setAttribute('type', this.specification.type);
|
n@2579
|
2738 console.log("Question: " + this.string.textContent);
|
n@2579
|
2739 console.log("Response: " + this.slider.value);
|
n@2579
|
2740 var question = storePoint.parent.document.createElement('question');
|
n@2579
|
2741 question.textContent = this.string.textContent;
|
n@2579
|
2742 var response = storePoint.parent.document.createElement('response');
|
n@2579
|
2743 response.textContent = this.slider.value;
|
n@2579
|
2744 root.appendChild(question);
|
n@2579
|
2745 root.appendChild(response);
|
n@2579
|
2746 storePoint.XMLDOM.appendChild(root);
|
n@2579
|
2747 return root;
|
n@2579
|
2748 };
|
n@2579
|
2749 this.resize = function () {
|
n@2579
|
2750 var boxwidth = (window.innerWidth - 100) / 2;
|
n@2579
|
2751 if (boxwidth >= 600) {
|
n@2579
|
2752 boxwidth = 600;
|
n@2579
|
2753 } else if (boxwidth < 400) {
|
n@2579
|
2754 boxwidth = 400;
|
n@2579
|
2755 }
|
n@2579
|
2756 this.holder.style.width = boxwidth + "px";
|
n@2579
|
2757 this.slider.style.width = boxwidth - 24 + "px";
|
n@2579
|
2758 };
|
n@2579
|
2759 this.resize();
|
n@2579
|
2760 };
|
n@2579
|
2761
|
nicholas@2498
|
2762 this.createCommentQuestion = function (element) {
|
nicholas@2498
|
2763 var node;
|
nicholas@2498
|
2764 if (element.type == 'question') {
|
nicholas@2498
|
2765 node = new this.commentBox(element);
|
nicholas@2498
|
2766 } else if (element.type == 'radio') {
|
nicholas@2498
|
2767 node = new this.radioBox(element);
|
nicholas@2498
|
2768 } else if (element.type == 'checkbox') {
|
nicholas@2498
|
2769 node = new this.checkboxBox(element);
|
n@2579
|
2770 } else if (element.type == 'slider') {
|
n@2579
|
2771 node = new this.sliderBox(element);
|
nicholas@2498
|
2772 }
|
nicholas@2498
|
2773 this.commentQuestions.push(node);
|
nicholas@2498
|
2774 return node;
|
nicholas@2498
|
2775 };
|
nicholas@2498
|
2776
|
nicholas@2498
|
2777 this.deleteCommentQuestions = function () {
|
nicholas@2498
|
2778 this.commentQuestions = [];
|
nicholas@2498
|
2779 };
|
nicholas@2498
|
2780
|
nicholas@2498
|
2781 this.outsideReferenceDOM = function (audioObject, index, inject) {
|
nicholas@2224
|
2782 this.parent = audioObject;
|
nicholas@2224
|
2783 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2784 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2498
|
2785 this.outsideReferenceHolder.setAttribute('track-id', index);
|
nicholas@2409
|
2786 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2787 this.outsideReferenceHolder.disabled = true;
|
nicholas@2701
|
2788 this.handleEvent = function (event) {
|
nicholas@2700
|
2789 audioEngineContext.play(this.parent.id);
|
nicholas@2224
|
2790 };
|
nicholas@2700
|
2791 this.outsideReferenceHolder.addEventListener("click", this);
|
nicholas@2224
|
2792 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2498
|
2793 this.enable = function () {
|
nicholas@2498
|
2794 if (this.parent.state == 1) {
|
nicholas@2224
|
2795 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2796 }
|
nicholas@2224
|
2797 };
|
nicholas@2498
|
2798 this.updateLoading = function (progress) {
|
nicholas@2498
|
2799 if (progress != 100) {
|
nicholas@2224
|
2800 progress = String(progress);
|
nicholas@2224
|
2801 progress = progress.split('.')[0];
|
nicholas@2498
|
2802 this.outsideReferenceHolder.textContent = progress + '%';
|
nicholas@2224
|
2803 } else {
|
nicholas@2409
|
2804 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2805 }
|
nicholas@2224
|
2806 };
|
nicholas@2498
|
2807 this.startPlayback = function () {
|
nicholas@2224
|
2808 // Called when playback has begun
|
nicholas@2224
|
2809 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2810 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2811 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2812 };
|
nicholas@2498
|
2813 this.stopPlayback = function () {
|
nicholas@2224
|
2814 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2815 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2816 };
|
nicholas@2498
|
2817 this.exportXMLDOM = function (audioObject) {
|
nicholas@2224
|
2818 return null;
|
nicholas@2224
|
2819 };
|
nicholas@2498
|
2820 this.getValue = function () {
|
nicholas@2224
|
2821 return 0;
|
nicholas@2224
|
2822 };
|
nicholas@2498
|
2823 this.getPresentedId = function () {
|
nicholas@2409
|
2824 return this.parent.specification.label || "Reference";
|
nicholas@2224
|
2825 };
|
nicholas@2498
|
2826 this.canMove = function () {
|
nicholas@2224
|
2827 return false;
|
nicholas@2224
|
2828 };
|
nicholas@2498
|
2829 this.error = function () {
|
nicholas@2498
|
2830 // audioObject has an error!!
|
nicholas@2224
|
2831 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2832 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2678
|
2833 };
|
nicholas@2678
|
2834 };
|
nicholas@2498
|
2835
|
nicholas@2498
|
2836 this.playhead = new function () {
|
nicholas@2498
|
2837 this.object = document.createElement('div');
|
nicholas@2498
|
2838 this.object.className = 'playhead';
|
nicholas@2498
|
2839 this.object.align = 'left';
|
nicholas@2498
|
2840 var curTime = document.createElement('div');
|
nicholas@2498
|
2841 curTime.style.width = '50px';
|
nicholas@2498
|
2842 this.curTimeSpan = document.createElement('span');
|
nicholas@2498
|
2843 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2844 curTime.appendChild(this.curTimeSpan);
|
nicholas@2498
|
2845 this.object.appendChild(curTime);
|
nicholas@2498
|
2846 this.scrubberTrack = document.createElement('div');
|
nicholas@2498
|
2847 this.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2498
|
2848
|
nicholas@2498
|
2849 this.scrubberHead = document.createElement('div');
|
nicholas@2498
|
2850 this.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2498
|
2851 this.scrubberTrack.appendChild(this.scrubberHead);
|
nicholas@2498
|
2852 this.object.appendChild(this.scrubberTrack);
|
nicholas@2498
|
2853
|
nicholas@2498
|
2854 this.timePerPixel = 0;
|
nicholas@2498
|
2855 this.maxTime = 0;
|
nicholas@2498
|
2856
|
nicholas@2682
|
2857 this.playbackObject = undefined;
|
nicholas@2498
|
2858
|
nicholas@2498
|
2859 this.setTimePerPixel = function (audioObject) {
|
nicholas@2498
|
2860 //maxTime must be in seconds
|
nicholas@2498
|
2861 this.playbackObject = audioObject;
|
nicholas@2498
|
2862 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2498
|
2863 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2498
|
2864 this.timePerPixel = this.maxTime / 490;
|
nicholas@2498
|
2865 if (this.maxTime < 60) {
|
nicholas@2498
|
2866 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
2867 } else {
|
nicholas@2498
|
2868 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2869 }
|
nicholas@2498
|
2870 };
|
nicholas@2498
|
2871
|
nicholas@2498
|
2872 this.update = function () {
|
nicholas@2498
|
2873 // Update the playhead position, startPlay must be called
|
nicholas@2498
|
2874 if (this.timePerPixel > 0) {
|
nicholas@2498
|
2875 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2498
|
2876 if (time > 0 && time < this.maxTime) {
|
nicholas@2498
|
2877 var width = 490;
|
nicholas@2498
|
2878 var pix = Math.floor(time / this.timePerPixel);
|
nicholas@2498
|
2879 this.scrubberHead.style.left = pix + 'px';
|
nicholas@2498
|
2880 if (this.maxTime > 60.0) {
|
nicholas@2498
|
2881 var secs = time % 60;
|
nicholas@2498
|
2882 var mins = Math.floor((time - secs) / 60);
|
nicholas@2498
|
2883 secs = secs.toString();
|
nicholas@2498
|
2884 secs = secs.substr(0, 2);
|
nicholas@2498
|
2885 mins = mins.toString();
|
nicholas@2498
|
2886 this.curTimeSpan.textContent = mins + ':' + secs;
|
nicholas@2498
|
2887 } else {
|
nicholas@2498
|
2888 time = time.toString();
|
nicholas@2498
|
2889 this.curTimeSpan.textContent = time.substr(0, 4);
|
nicholas@2498
|
2890 }
|
nicholas@2498
|
2891 } else {
|
nicholas@2498
|
2892 this.scrubberHead.style.left = '0px';
|
nicholas@2498
|
2893 if (this.maxTime < 60) {
|
nicholas@2498
|
2894 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
2895 } else {
|
nicholas@2498
|
2896 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2897 }
|
nicholas@2498
|
2898 }
|
nicholas@2498
|
2899 }
|
nicholas@2498
|
2900 };
|
nicholas@2498
|
2901
|
nicholas@2498
|
2902 this.interval = undefined;
|
nicholas@2498
|
2903
|
nicholas@2498
|
2904 this.start = function () {
|
nicholas@2678
|
2905 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2498
|
2906 if (this.maxTime < 60) {
|
nicholas@2682
|
2907 this.interval = window.setInterval(function () {
|
nicholas@2498
|
2908 interfaceContext.playhead.update();
|
nicholas@2498
|
2909 }, 10);
|
nicholas@2498
|
2910 } else {
|
nicholas@2682
|
2911 this.interval = window.setInterval(function () {
|
nicholas@2498
|
2912 interfaceContext.playhead.update();
|
nicholas@2498
|
2913 }, 100);
|
nicholas@2498
|
2914 }
|
nicholas@2498
|
2915 }
|
nicholas@2498
|
2916 };
|
nicholas@2498
|
2917 this.stop = function () {
|
nicholas@2682
|
2918 window.clearInterval(this.interval);
|
nicholas@2498
|
2919 this.interval = undefined;
|
nicholas@2224
|
2920 this.scrubberHead.style.left = '0px';
|
nicholas@2498
|
2921 if (this.maxTime < 60) {
|
nicholas@2498
|
2922 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
2923 } else {
|
nicholas@2498
|
2924 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2925 }
|
nicholas@2498
|
2926 };
|
nicholas@2498
|
2927 };
|
nicholas@2498
|
2928
|
nicholas@2498
|
2929 this.volume = new function () {
|
nicholas@2224
|
2930 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
2931 // Includes trackers on page-by-page data
|
nicholas@2224
|
2932 // Volume does NOT reset to 0dB on each page load
|
nicholas@2224
|
2933 this.valueLin = 1.0;
|
nicholas@2224
|
2934 this.valueDB = 0.0;
|
nicholas@2352
|
2935 this.root = document.createElement('div');
|
nicholas@2352
|
2936 this.root.id = 'master-volume-root';
|
nicholas@2224
|
2937 this.object = document.createElement('div');
|
nicholas@2352
|
2938 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
2939 this.object.appendChild(this.root);
|
nicholas@2224
|
2940 this.slider = document.createElement('input');
|
nicholas@2224
|
2941 this.slider.id = 'master-volume-control';
|
nicholas@2224
|
2942 this.slider.type = 'range';
|
nicholas@2224
|
2943 this.valueText = document.createElement('span');
|
nicholas@2224
|
2944 this.valueText.id = 'master-volume-feedback';
|
nicholas@2224
|
2945 this.valueText.textContent = '0dB';
|
nicholas@2498
|
2946
|
nicholas@2224
|
2947 this.slider.min = -60;
|
nicholas@2224
|
2948 this.slider.max = 12;
|
nicholas@2224
|
2949 this.slider.value = 0;
|
nicholas@2224
|
2950 this.slider.step = 1;
|
nicholas@2669
|
2951 this.handleEvent = function (event) {
|
nicholas@2669
|
2952 if (event.type == "mousemove") {
|
nicholas@2669
|
2953 this.valueDB = Number(this.slider.value);
|
nicholas@2669
|
2954 this.valueLin = decibelToLinear(this.valueDB);
|
nicholas@2669
|
2955 this.valueText.textContent = this.valueDB + 'dB';
|
nicholas@2669
|
2956 audioEngineContext.outputGain.gain.value = this.valueLin;
|
nicholas@2669
|
2957 } else if (event.type == "mouseup") {
|
nicholas@2669
|
2958 this.onmouseup();
|
nicholas@2669
|
2959 }
|
nicholas@2669
|
2960 this.slider.value = this.valueDB;
|
nicholas@2669
|
2961
|
nicholas@2669
|
2962 if (event.stopPropagation) {
|
nicholas@2669
|
2963 event.stopPropagation();
|
nicholas@2669
|
2964 }
|
nicholas@2711
|
2965 };
|
nicholas@2669
|
2966 this.onmouseup = function () {
|
nicholas@2224
|
2967 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2678
|
2968 if (storePoint.length === 0) {
|
nicholas@2224
|
2969 storePoint = storage.document.createElement('metricresult');
|
nicholas@2498
|
2970 storePoint.setAttribute('name', 'volumeTracker');
|
nicholas@2224
|
2971 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2498
|
2972 } else {
|
nicholas@2224
|
2973 storePoint = storePoint[0];
|
nicholas@2224
|
2974 }
|
nicholas@2224
|
2975 var node = storage.document.createElement('movement');
|
nicholas@2498
|
2976 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
|
nicholas@2669
|
2977 node.setAttribute('volume', this.valueDB);
|
nicholas@2498
|
2978 node.setAttribute('format', 'dBFS');
|
nicholas@2224
|
2979 storePoint.appendChild(node);
|
nicholas@2711
|
2980 };
|
nicholas@2669
|
2981 this.slider.addEventListener("mousemove", this);
|
nicholas@2669
|
2982 this.root.addEventListener("mouseup", this);
|
nicholas@2498
|
2983
|
nicholas@2224
|
2984 var title = document.createElement('div');
|
nicholas@2224
|
2985 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
2986 title.style.fontSize = '0.75em';
|
nicholas@2224
|
2987 title.style.width = "100%";
|
nicholas@2224
|
2988 title.align = 'center';
|
nicholas@2352
|
2989 this.root.appendChild(title);
|
nicholas@2498
|
2990
|
nicholas@2352
|
2991 this.root.appendChild(this.slider);
|
nicholas@2352
|
2992 this.root.appendChild(this.valueText);
|
nicholas@2498
|
2993
|
nicholas@2498
|
2994 this.resize = function (event) {
|
nicholas@2352
|
2995 if (window.innerWidth < 1000) {
|
nicholas@2678
|
2996 this.object.className = "master-volume-holder-inline";
|
nicholas@2352
|
2997 } else {
|
nicholas@2352
|
2998 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
2999 }
|
nicholas@2678
|
3000 };
|
nicholas@2678
|
3001 };
|
nicholas@2498
|
3002
|
nicholas@2224
|
3003 this.calibrationModuleObject = null;
|
nicholas@2498
|
3004 this.calibrationModule = function () {
|
nicholas@2224
|
3005 // This creates an on-page calibration module
|
nicholas@2224
|
3006 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
3007 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
3008 // The calibration is a fixed state module
|
nicholas@2224
|
3009 this.calibrationNodes = [];
|
nicholas@2224
|
3010 this.holder = null;
|
nicholas@2498
|
3011 this.build = function (inject) {
|
nicholas@2224
|
3012 var f0 = 62.5;
|
nicholas@2224
|
3013 this.holder = document.createElement("div");
|
nicholas@2224
|
3014 this.holder.className = "calibration-holder";
|
nicholas@2224
|
3015 this.calibrationNodes = [];
|
nicholas@2498
|
3016 while (f0 < 20000) {
|
nicholas@2224
|
3017 var obj = {
|
nicholas@2224
|
3018 root: document.createElement("div"),
|
nicholas@2224
|
3019 input: document.createElement("input"),
|
nicholas@2224
|
3020 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
3021 gain: audioContext.createGain(),
|
nicholas@2224
|
3022 f: f0,
|
nicholas@2224
|
3023 parent: this,
|
nicholas@2498
|
3024 handleEvent: function (event) {
|
nicholas@2498
|
3025 switch (event.type) {
|
nicholas@2224
|
3026 case "mouseenter":
|
nicholas@2224
|
3027 this.oscillator.start(0);
|
nicholas@2224
|
3028 break;
|
nicholas@2224
|
3029 case "mouseleave":
|
nicholas@2224
|
3030 this.oscillator.stop(0);
|
nicholas@2224
|
3031 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
3032 this.oscillator.connect(this.gain);
|
nicholas@2224
|
3033 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
3034 break;
|
nicholas@2224
|
3035 case "mousemove":
|
nicholas@2498
|
3036 var value = Math.pow(10, this.input.value / 20);
|
nicholas@2224
|
3037 if (this.f == 1000) {
|
nicholas@2224
|
3038 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
3039 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
3040 } else {
|
nicholas@2678
|
3041 this.gain.gain.value = value;
|
nicholas@2224
|
3042 }
|
nicholas@2224
|
3043 break;
|
nicholas@2224
|
3044 }
|
nicholas@2224
|
3045 },
|
nicholas@2498
|
3046 disconnect: function () {
|
nicholas@2224
|
3047 this.gain.disconnect();
|
nicholas@2224
|
3048 }
|
nicholas@2678
|
3049 };
|
nicholas@2224
|
3050 obj.root.className = "calibration-slider";
|
nicholas@2224
|
3051 obj.root.appendChild(obj.input);
|
nicholas@2224
|
3052 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
3053 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2498
|
3054 obj.gain.gain.value = Math.random() * 2;
|
nicholas@2224
|
3055 obj.input.value = obj.gain.gain.value;
|
nicholas@2498
|
3056 obj.input.setAttribute('orient', 'vertical');
|
nicholas@2224
|
3057 obj.input.type = "range";
|
nicholas@2593
|
3058 obj.input.min = -12;
|
nicholas@2593
|
3059 obj.input.max = 0;
|
nicholas@2224
|
3060 obj.input.step = 0.25;
|
nicholas@2224
|
3061 if (f0 != 1000) {
|
nicholas@2498
|
3062 obj.input.value = (Math.random() * 12) - 6;
|
nicholas@2224
|
3063 } else {
|
nicholas@2224
|
3064 obj.input.value = 0;
|
nicholas@2498
|
3065 obj.root.style.backgroundColor = "rgb(255,125,125)";
|
nicholas@2224
|
3066 }
|
nicholas@2498
|
3067 obj.input.addEventListener("mousemove", obj);
|
nicholas@2498
|
3068 obj.input.addEventListener("mouseenter", obj);
|
nicholas@2498
|
3069 obj.input.addEventListener("mouseleave", obj);
|
nicholas@2498
|
3070 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
|
nicholas@2224
|
3071 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
3072 this.calibrationNodes.push(obj);
|
nicholas@2224
|
3073 this.holder.appendChild(obj.root);
|
nicholas@2224
|
3074 f0 *= 2;
|
nicholas@2224
|
3075 }
|
nicholas@2224
|
3076 inject.appendChild(this.holder);
|
nicholas@2678
|
3077 };
|
nicholas@2498
|
3078 this.collect = function () {
|
nicholas@2682
|
3079 this.calibrationNodes.forEach(function (obj) {
|
nicholas@2224
|
3080 var node = storage.document.createElement("calibrationresult");
|
nicholas@2498
|
3081 node.setAttribute("frequency", obj.f);
|
nicholas@2498
|
3082 node.setAttribute("range-min", obj.input.min);
|
nicholas@2498
|
3083 node.setAttribute("range-max", obj.input.max);
|
nicholas@2498
|
3084 node.setAttribute("gain-lin", obj.gain.gain.value);
|
nicholas@2224
|
3085 this.storeDOM.appendChild(node);
|
nicholas@2682
|
3086 }, this);
|
nicholas@2678
|
3087 };
|
nicholas@2678
|
3088 };
|
nicholas@2498
|
3089
|
nicholas@2498
|
3090
|
nicholas@2498
|
3091 // Global Checkers
|
nicholas@2498
|
3092 // These functions will help enforce the checkers
|
nicholas@2498
|
3093 this.checkHiddenAnchor = function () {
|
nicholas@2697
|
3094 var anchors = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2697
|
3095 return ao.specification.type === "anchor";
|
nicholas@2697
|
3096 });
|
nicholas@2697
|
3097 var state = anchors.some(function (ao) {
|
nicholas@2697
|
3098 return (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2697
|
3099 });
|
nicholas@2697
|
3100 if (state) {
|
nicholas@2697
|
3101 console.log('Anchor node not below marker value');
|
nicholas@2697
|
3102 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
nicholas@2697
|
3103 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2697
|
3104 return false;
|
nicholas@2498
|
3105 }
|
nicholas@2498
|
3106 return true;
|
nicholas@2498
|
3107 };
|
nicholas@2498
|
3108
|
nicholas@2498
|
3109 this.checkHiddenReference = function () {
|
nicholas@2697
|
3110 var references = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2698
|
3111 return ao.specification.type === "reference";
|
nicholas@2697
|
3112 });
|
nicholas@2697
|
3113 var state = references.some(function (ao) {
|
nicholas@2697
|
3114 return (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2697
|
3115 });
|
nicholas@2697
|
3116 if (state) {
|
nicholas@2697
|
3117 console.log('Reference node not below marker value');
|
nicholas@2697
|
3118 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
nicholas@2697
|
3119 this.storeErrorNode('Reference node not below marker value');
|
nicholas@2697
|
3120 return false;
|
nicholas@2498
|
3121 }
|
nicholas@2498
|
3122 return true;
|
nicholas@2498
|
3123 };
|
nicholas@2498
|
3124
|
nicholas@2498
|
3125 this.checkFragmentsFullyPlayed = function () {
|
nicholas@2498
|
3126 // Checks the entire file has been played back
|
nicholas@2498
|
3127 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2498
|
3128 if (audioEngineContext.loopPlayback) {
|
nicholas@2498
|
3129 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2498
|
3130 return true;
|
nicholas@2498
|
3131 }
|
nicholas@2498
|
3132 var check_pass = true;
|
nicholas@2682
|
3133 var error_obj = [],
|
nicholas@2682
|
3134 i;
|
nicholas@2682
|
3135 for (i = 0; i < audioEngineContext.audioObjects.length; i++) {
|
nicholas@2498
|
3136 var object = audioEngineContext.audioObjects[i];
|
nicholas@2498
|
3137 var time = object.buffer.buffer.duration;
|
nicholas@2498
|
3138 var metric = object.metric;
|
nicholas@2498
|
3139 var passed = false;
|
nicholas@2498
|
3140 for (var j = 0; j < metric.listenTracker.length; j++) {
|
nicholas@2498
|
3141 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
|
nicholas@2498
|
3142 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2498
|
3143 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2498
|
3144 var delta = stop_time - start_time;
|
nicholas@2498
|
3145 if (delta >= time) {
|
nicholas@2498
|
3146 passed = true;
|
nicholas@2498
|
3147 break;
|
nicholas@2498
|
3148 }
|
nicholas@2498
|
3149 }
|
nicholas@2678
|
3150 if (passed === false) {
|
nicholas@2498
|
3151 check_pass = false;
|
nicholas@2498
|
3152 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3153 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3154 }
|
nicholas@2498
|
3155 }
|
nicholas@2678
|
3156 if (check_pass === false) {
|
nicholas@2498
|
3157 var str_start = "You have not completely listened to fragments ";
|
nicholas@2682
|
3158 for (i = 0; i < error_obj.length; i++) {
|
nicholas@2498
|
3159 str_start += error_obj[i];
|
nicholas@2498
|
3160 if (i != error_obj.length - 1) {
|
nicholas@2498
|
3161 str_start += ', ';
|
nicholas@2498
|
3162 }
|
nicholas@2498
|
3163 }
|
nicholas@2498
|
3164 str_start += ". Please keep listening";
|
nicholas@2498
|
3165 console.log("[ALERT]: " + str_start);
|
nicholas@2498
|
3166 this.storeErrorNode("[ALERT]: " + str_start);
|
nicholas@2498
|
3167 interfaceContext.lightbox.post("Error", str_start);
|
nicholas@2444
|
3168 return false;
|
nicholas@2498
|
3169 }
|
nicholas@2444
|
3170 return true;
|
nicholas@2498
|
3171 };
|
nicholas@2498
|
3172 this.checkAllMoved = function () {
|
nicholas@2498
|
3173 var str = "You have not moved ";
|
nicholas@2498
|
3174 var failed = [];
|
nicholas@2682
|
3175 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2678
|
3176 if (ao.metric.wasMoved === false && ao.interfaceDOM.canMove() === true) {
|
nicholas@2498
|
3177 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3178 }
|
nicholas@2682
|
3179 }, this);
|
nicholas@2678
|
3180 if (failed.length === 0) {
|
nicholas@2498
|
3181 return true;
|
nicholas@2498
|
3182 } else if (failed.length == 1) {
|
nicholas@2498
|
3183 str += 'track ' + failed[0];
|
nicholas@2498
|
3184 } else {
|
nicholas@2498
|
3185 str += 'tracks ';
|
nicholas@2498
|
3186 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3187 str += failed[i] + ', ';
|
nicholas@2498
|
3188 }
|
nicholas@2498
|
3189 str += 'and ' + failed[i];
|
nicholas@2498
|
3190 }
|
nicholas@2498
|
3191 str += '.';
|
nicholas@2498
|
3192 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3193 console.log(str);
|
nicholas@2224
|
3194 this.storeErrorNode(str);
|
nicholas@2498
|
3195 return false;
|
nicholas@2498
|
3196 };
|
nicholas@2498
|
3197 this.checkAllPlayed = function () {
|
nicholas@2498
|
3198 var str = "You have not played ";
|
nicholas@2498
|
3199 var failed = [];
|
nicholas@2682
|
3200 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2678
|
3201 if (ao.metric.wasListenedTo === false) {
|
nicholas@2498
|
3202 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3203 }
|
nicholas@2682
|
3204 }, this);
|
nicholas@2678
|
3205 if (failed.length === 0) {
|
nicholas@2498
|
3206 return true;
|
nicholas@2498
|
3207 } else if (failed.length == 1) {
|
nicholas@2498
|
3208 str += 'track ' + failed[0];
|
nicholas@2498
|
3209 } else {
|
nicholas@2498
|
3210 str += 'tracks ';
|
nicholas@2498
|
3211 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3212 str += failed[i] + ', ';
|
nicholas@2498
|
3213 }
|
nicholas@2498
|
3214 str += 'and ' + failed[i];
|
nicholas@2498
|
3215 }
|
nicholas@2498
|
3216 str += '.';
|
nicholas@2498
|
3217 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3218 console.log(str);
|
nicholas@2224
|
3219 this.storeErrorNode(str);
|
nicholas@2498
|
3220 return false;
|
nicholas@2498
|
3221 };
|
nicholas@2540
|
3222 this.checkAllCommented = function () {
|
nicholas@2540
|
3223 var str = "You have not commented on all the fragments.";
|
nicholas@2540
|
3224 var cont = true,
|
nicholas@2540
|
3225 boxes = this.commentBoxes.boxes,
|
nicholas@2540
|
3226 numBoxes = boxes.length,
|
nicholas@2540
|
3227 i;
|
nicholas@2540
|
3228 for (i = 0; i < numBoxes; i++) {
|
nicholas@2540
|
3229 if (boxes[i].trackCommentBox.value === "") {
|
nicholas@2540
|
3230 interfaceContext.lightbox.post("Error", str);
|
nicholas@2540
|
3231 console.log(str);
|
nicholas@2540
|
3232 this.storeErrorNode(str);
|
nicholas@2540
|
3233 return false;
|
nicholas@2540
|
3234 }
|
nicholas@2540
|
3235 }
|
nicholas@2540
|
3236 return true;
|
nicholas@2678
|
3237 };
|
nicholas@2704
|
3238 this.checkScaleRange = function () {
|
nicholas@2310
|
3239 var page = testState.getCurrentTestPage();
|
nicholas@2704
|
3240 var interfaceObject = page.interfaces;
|
nicholas@2310
|
3241 var state = true;
|
nicholas@2310
|
3242 var str = "Please keep listening. ";
|
nicholas@2704
|
3243 if (interfaceObject === undefined) {
|
nicholas@2704
|
3244 return true;
|
nicholas@2704
|
3245 }
|
nicholas@2704
|
3246 interfaceObject = interfaceObject[0];
|
nicholas@2704
|
3247 var scales = (function () {
|
nicholas@2704
|
3248 var scaleRange = interfaceObject.options.find(function (a) {
|
nicholas@2704
|
3249 return a.name == "scalerange";
|
nicholas@2704
|
3250 });
|
nicholas@2704
|
3251 return {
|
nicholas@2704
|
3252 min: scaleRange.min,
|
nicholas@2704
|
3253 max: scaleRange.max
|
nicholas@2704
|
3254 };
|
nicholas@2704
|
3255 })();
|
nicholas@2704
|
3256 var range = audioEngineContext.audioObjects.reduce(function (a, b) {
|
nicholas@2704
|
3257 var v = b.interfaceDOM.getValue();
|
nicholas@2704
|
3258 return {
|
nicholas@2704
|
3259 min: Math.min(a.min, v),
|
nicholas@2704
|
3260 max: Math.max(a.max, v)
|
nicholas@2498
|
3261 }
|
nicholas@2704
|
3262 }, {
|
nicholas@2704
|
3263 min: 100,
|
nicholas@2704
|
3264 max: 0
|
nicholas@2704
|
3265 });
|
nicholas@2704
|
3266 if (range.min > scales.min) {
|
nicholas@2704
|
3267 str += "At least one fragment must be below the " + range.min + " mark.";
|
nicholas@2704
|
3268 state = false;
|
nicholas@2704
|
3269 } else if (range.max < sacles.max) {
|
nicholas@2704
|
3270 str += "At least one fragment must be above the " + range.max + " mark.";
|
nicholas@2310
|
3271 state = false;
|
nicholas@2310
|
3272 }
|
nicholas@2704
|
3273 if (state === false) {
|
nicholas@2310
|
3274 console.log(str);
|
nicholas@2310
|
3275 this.storeErrorNode(str);
|
nicholas@2498
|
3276 interfaceContext.lightbox.post("Error", str);
|
nicholas@2310
|
3277 }
|
nicholas@2310
|
3278 return state;
|
nicholas@2678
|
3279 };
|
nicholas@2498
|
3280
|
nicholas@2498
|
3281 this.storeErrorNode = function (errorMessage) {
|
nicholas@2224
|
3282 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
3283 var node = storage.document.createElement('error');
|
nicholas@2498
|
3284 node.setAttribute('time', time);
|
nicholas@2224
|
3285 node.textContent = errorMessage;
|
nicholas@2224
|
3286 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
3287 };
|
nicholas@2595
|
3288
|
nicholas@2595
|
3289 this.getLabel = function (labelType, index, labelStart) {
|
nicholas@2595
|
3290 /*
|
nicholas@2595
|
3291 Get the correct label based on type, index and offset
|
nicholas@2595
|
3292 */
|
nicholas@2595
|
3293
|
nicholas@2595
|
3294 function calculateLabel(labelType, index, offset) {
|
nicholas@2595
|
3295 if (labelType == "none") {
|
nicholas@2595
|
3296 return "";
|
nicholas@2595
|
3297 }
|
nicholas@2595
|
3298 switch (labelType) {
|
nicholas@2595
|
3299 case "letter":
|
nicholas@2596
|
3300 return String.fromCharCode((index + offset) % 26 + 97);
|
nicholas@2595
|
3301 case "capital":
|
nicholas@2607
|
3302 return String.fromCharCode((index + offset) % 26 + 65);
|
nicholas@2625
|
3303 case "samediff":
|
nicholas@2678
|
3304 if (index === 0) {
|
nicholas@2625
|
3305 return "Same";
|
nicholas@2625
|
3306 } else if (index == 1) {
|
nicholas@2625
|
3307 return "Difference";
|
nicholas@2625
|
3308 }
|
nicholas@2678
|
3309 return "";
|
nicholas@2595
|
3310 case "number":
|
nicholas@2595
|
3311 return String(index + offset);
|
nicholas@2595
|
3312 default:
|
nicholas@2595
|
3313 return "";
|
nicholas@2595
|
3314 }
|
nicholas@2595
|
3315 }
|
nicholas@2595
|
3316
|
nicholas@2678
|
3317 if (typeof labelStart !== "string" || labelStart.length === 0) {
|
nicholas@2595
|
3318 labelStart = String.fromCharCode(0);
|
nicholas@2595
|
3319 }
|
nicholas@2595
|
3320
|
nicholas@2595
|
3321 switch (labelType) {
|
nicholas@2595
|
3322 case "letter":
|
nicholas@2595
|
3323 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3324 if (labelStart < 97 || labelStart > 122) {
|
nicholas@2595
|
3325 labelStart = 97;
|
nicholas@2595
|
3326 }
|
nicholas@2595
|
3327 labelStart -= 97;
|
nicholas@2595
|
3328 break;
|
nicholas@2595
|
3329 case "capital":
|
nicholas@2595
|
3330 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3331 if (labelStart < 65 || labelStart > 90) {
|
nicholas@2595
|
3332 labelStart = 65;
|
nicholas@2595
|
3333 }
|
nicholas@2595
|
3334 labelStart -= 65;
|
nicholas@2595
|
3335 break;
|
nicholas@2595
|
3336 case "number":
|
nicholas@2608
|
3337 labelStart = Number(labelStart);
|
nicholas@2608
|
3338 if (!isFinite(labelStart)) {
|
nicholas@2595
|
3339 labelStart = 1;
|
nicholas@2595
|
3340 }
|
nicholas@2595
|
3341 break;
|
nicholas@2595
|
3342 default:
|
nicholas@2596
|
3343 labelStart = 0;
|
nicholas@2595
|
3344 }
|
nicholas@2595
|
3345 if (typeof index == "number") {
|
nicholas@2595
|
3346 return calculateLabel(labelType, index, labelStart);
|
nicholas@2595
|
3347 } else if (index.length && index.length > 0) {
|
nicholas@2595
|
3348 var a = [],
|
nicholas@2595
|
3349 l = index.length,
|
nicholas@2595
|
3350 i;
|
nicholas@2595
|
3351 for (i = 0; i < l; i++) {
|
nicholas@2595
|
3352 a[i] = calculateLabel(labelType, index[i], labelStart);
|
nicholas@2595
|
3353 }
|
nicholas@2595
|
3354 return a;
|
nicholas@2595
|
3355 } else {
|
nicholas@2595
|
3356 throw ("Invalid arguments");
|
nicholas@2595
|
3357 }
|
nicholas@2678
|
3358 };
|
nicholas@2649
|
3359
|
nicholas@2649
|
3360 this.getCombinedInterfaces = function (page) {
|
nicholas@2649
|
3361 // Combine the interfaces with the global interface nodes
|
nicholas@2649
|
3362 var global = specification.interfaces,
|
nicholas@2649
|
3363 local = page.interfaces;
|
nicholas@2649
|
3364 local.forEach(function (locInt) {
|
nicholas@2649
|
3365 // Iterate through the options nodes
|
nicholas@2649
|
3366 var addList = [];
|
nicholas@2649
|
3367 global.options.forEach(function (gopt) {
|
nicholas@2649
|
3368 var lopt = locInt.options.find(function (lopt) {
|
nicholas@2649
|
3369 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
|
nicholas@2649
|
3370 });
|
nicholas@2649
|
3371 if (!lopt) {
|
nicholas@2649
|
3372 // Global option doesn't exist locally
|
nicholas@2649
|
3373 addList.push(gopt);
|
nicholas@2649
|
3374 }
|
nicholas@2649
|
3375 });
|
nicholas@2649
|
3376 locInt.options = locInt.options.concat(addList);
|
nicholas@2649
|
3377 if (!locInt.scales && global.scales) {
|
nicholas@2649
|
3378 // Use the global default scales
|
nicholas@2649
|
3379 locInt.scales = global.scales;
|
nicholas@2649
|
3380 }
|
nicholas@2649
|
3381 });
|
nicholas@2649
|
3382 return local;
|
nicholas@2678
|
3383 };
|
nicholas@2224
|
3384 }
|
nicholas@2224
|
3385
|
nicholas@2498
|
3386 function Storage() {
|
nicholas@2498
|
3387 // Holds results in XML format until ready for collection
|
nicholas@2498
|
3388 this.globalPreTest = null;
|
nicholas@2498
|
3389 this.globalPostTest = null;
|
nicholas@2498
|
3390 this.testPages = [];
|
nicholas@2498
|
3391 this.document = null;
|
nicholas@2498
|
3392 this.root = null;
|
nicholas@2498
|
3393 this.state = 0;
|
nicholas@2498
|
3394
|
nicholas@2498
|
3395 this.initialise = function (existingStore) {
|
nicholas@2678
|
3396 if (existingStore === undefined) {
|
nicholas@2224
|
3397 // We need to get the sessionKey
|
nicholas@2510
|
3398 this.SessionKey.requestKey();
|
nicholas@2498
|
3399 this.document = document.implementation.createDocument(null, "waetresult", null);
|
nicholas@2224
|
3400 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3401 var projectDocument = specification.projectXML;
|
nicholas@2682
|
3402 projectDocument.setAttribute('file-name', specification.url);
|
nicholas@2682
|
3403 projectDocument.setAttribute('url', qualifyURL(specification.url));
|
nicholas@2224
|
3404 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3405 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3406 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3407 } else {
|
nicholas@2224
|
3408 this.document = existingStore;
|
nicholas@2294
|
3409 this.root = existingStore.firstChild;
|
nicholas@2224
|
3410 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3411 }
|
nicholas@2678
|
3412 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3413 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
|
nicholas@2498
|
3414 }
|
nicholas@2678
|
3415 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3416 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
|
nicholas@2498
|
3417 }
|
nicholas@2498
|
3418 };
|
nicholas@2498
|
3419
|
nicholas@2224
|
3420 this.SessionKey = {
|
nicholas@2224
|
3421 key: null,
|
nicholas@2224
|
3422 request: new XMLHttpRequest(),
|
nicholas@2224
|
3423 parent: this,
|
nicholas@2498
|
3424 handleEvent: function () {
|
nicholas@2224
|
3425 var parse = new DOMParser();
|
nicholas@2498
|
3426 var xml = parse.parseFromString(this.request.response, "text/xml");
|
nicholas@2678
|
3427 if (this.request.response.length === 0) {
|
nicholas@2515
|
3428 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2376
|
3429 return;
|
nicholas@2376
|
3430 }
|
nicholas@2498
|
3431 if (xml.getElementsByTagName("state").length > 0) {
|
nicholas@2498
|
3432 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
nicholas@2498
|
3433 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
nicholas@2498
|
3434 this.parent.root.setAttribute("key", this.key);
|
nicholas@2498
|
3435 this.parent.root.setAttribute("state", "empty");
|
nicholas@2516
|
3436 this.update();
|
nicholas@2515
|
3437 return;
|
nicholas@2514
|
3438 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
|
nicholas@2515
|
3439 this.key = null;
|
nicholas@2514
|
3440 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
|
nicholas@2515
|
3441 return;
|
nicholas@2498
|
3442 }
|
nicholas@2498
|
3443 }
|
nicholas@2515
|
3444 this.key = null;
|
nicholas@2515
|
3445 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2224
|
3446 },
|
nicholas@2510
|
3447 requestKey: function () {
|
nicholas@2510
|
3448 // For new servers, request a new key from the server
|
nicholas@2510
|
3449 var returnURL = "";
|
nicholas@2510
|
3450 if (typeof specification.projectReturn == "string") {
|
nicholas@2510
|
3451 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2510
|
3452 returnURL = specification.projectReturn;
|
nicholas@2510
|
3453 }
|
nicholas@2510
|
3454 }
|
nicholas@2510
|
3455 this.request.open("GET", returnURL + "php/requestKey.php", true);
|
nicholas@2510
|
3456 this.request.addEventListener("load", this);
|
nicholas@2510
|
3457 this.request.send();
|
nicholas@2510
|
3458 },
|
nicholas@2498
|
3459 update: function () {
|
nicholas@2678
|
3460 if (this.key === null) {
|
nicholas@2357
|
3461 console.log("Cannot save as key == null");
|
nicholas@2357
|
3462 return;
|
nicholas@2357
|
3463 }
|
nicholas@2498
|
3464 this.parent.root.setAttribute("state", "update");
|
nicholas@2224
|
3465 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3466 var returnURL = "";
|
nicholas@2302
|
3467 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
3468 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2302
|
3469 returnURL = specification.projectReturn;
|
nicholas@2302
|
3470 }
|
nicholas@2302
|
3471 }
|
nicholas@2498
|
3472 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key);
|
nicholas@2224
|
3473 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2498
|
3474 xmlhttp.onerror = function () {
|
nicholas@2224
|
3475 console.log('Error updating file to server!');
|
nicholas@2224
|
3476 };
|
nicholas@2224
|
3477 var hold = document.createElement("div");
|
nicholas@2224
|
3478 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3479 hold.appendChild(clone);
|
nicholas@2498
|
3480 xmlhttp.onload = function () {
|
nicholas@2224
|
3481 if (this.status >= 300) {
|
nicholas@2224
|
3482 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3483 } else {
|
nicholas@2224
|
3484 var parser = new DOMParser();
|
nicholas@2224
|
3485 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3486 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3487 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3488 var file = response.getElementsByTagName("file")[0];
|
nicholas@2498
|
3489 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2224
|
3490 } else {
|
nicholas@2224
|
3491 var message = response.getElementsByTagName("message");
|
nicholas@2498
|
3492 console.log("Intermediate save: Error! " + message.textContent);
|
nicholas@2224
|
3493 }
|
nicholas@2224
|
3494 }
|
nicholas@2678
|
3495 };
|
nicholas@2224
|
3496 xmlhttp.send([hold.innerHTML]);
|
nicholas@2224
|
3497 }
|
nicholas@2678
|
3498 };
|
nicholas@2498
|
3499
|
nicholas@2498
|
3500 this.createTestPageStore = function (specification) {
|
nicholas@2498
|
3501 var store = new this.pageNode(this, specification);
|
nicholas@2498
|
3502 this.testPages.push(store);
|
nicholas@2498
|
3503 return this.testPages[this.testPages.length - 1];
|
nicholas@2498
|
3504 };
|
nicholas@2498
|
3505
|
nicholas@2498
|
3506 this.surveyNode = function (parent, root, specification) {
|
nicholas@2498
|
3507 this.specification = specification;
|
nicholas@2498
|
3508 this.parent = parent;
|
nicholas@2224
|
3509 this.state = "empty";
|
nicholas@2498
|
3510 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2498
|
3511 this.XMLDOM.setAttribute('location', this.specification.location);
|
nicholas@2498
|
3512 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2682
|
3513 this.specification.options.forEach(function (optNode) {
|
nicholas@2498
|
3514 if (optNode.type != 'statement') {
|
nicholas@2498
|
3515 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2498
|
3516 node.setAttribute("ref", optNode.id);
|
nicholas@2498
|
3517 node.setAttribute('type', optNode.type);
|
nicholas@2498
|
3518 this.XMLDOM.appendChild(node);
|
nicholas@2498
|
3519 }
|
nicholas@2682
|
3520 }, this);
|
nicholas@2498
|
3521 root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3522
|
nicholas@2498
|
3523 this.postResult = function (node) {
|
nicholas@2682
|
3524 function postNumber(doc, value) {
|
nicholas@2682
|
3525 var child = doc.createElement("response");
|
nicholas@2682
|
3526 child.textContent = value;
|
nicholas@2682
|
3527 return child;
|
nicholas@2682
|
3528 }
|
nicholas@2682
|
3529
|
nicholas@2682
|
3530 function postRadio(doc, node) {
|
nicholas@2682
|
3531 var child = doc.createElement('response');
|
nicholas@2682
|
3532 if (node.response !== null) {
|
nicholas@2682
|
3533 child.setAttribute('name', node.response.name);
|
nicholas@2682
|
3534 child.textContent = node.response.text;
|
nicholas@2682
|
3535 }
|
nicholas@2682
|
3536 return child;
|
nicholas@2682
|
3537 }
|
nicholas@2682
|
3538
|
nicholas@2682
|
3539 function postCheckbox(doc, node) {
|
nicholas@2682
|
3540 var checkNode = doc.createElement('response');
|
nicholas@2682
|
3541 checkNode.setAttribute('name', node.name);
|
nicholas@2682
|
3542 checkNode.setAttribute('checked', node.checked);
|
nicholas@2682
|
3543 return checkNode;
|
nicholas@2682
|
3544 }
|
nicholas@2498
|
3545 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2498
|
3546 // ID is the position
|
nicholas@2498
|
3547 if (node.specification.type == 'statement') {
|
nicholas@2498
|
3548 return;
|
nicholas@2498
|
3549 }
|
nicholas@2498
|
3550 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2678
|
3551 while (surveyresult !== null) {
|
nicholas@2498
|
3552 if (surveyresult.getAttribute("ref") == node.specification.id) {
|
nicholas@2224
|
3553 break;
|
nicholas@2224
|
3554 }
|
nicholas@2224
|
3555 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3556 }
|
nicholas@2498
|
3557 switch (node.specification.type) {
|
nicholas@2498
|
3558 case "number":
|
nicholas@2498
|
3559 case "question":
|
n@2583
|
3560 case "slider":
|
nicholas@2682
|
3561 surveyresult.appendChild(postNumber(this.parent.document, node.response));
|
nicholas@2464
|
3562 break;
|
nicholas@2498
|
3563 case "radio":
|
nicholas@2682
|
3564 surveyresult.appendChild(postRadio(this.parent.document, node));
|
nicholas@2498
|
3565 break;
|
nicholas@2498
|
3566 case "checkbox":
|
nicholas@2678
|
3567 if (node.response === undefined) {
|
nicholas@2498
|
3568 surveyresult.appendChild(this.parent.document.createElement('response'));
|
nicholas@2498
|
3569 break;
|
nicholas@2498
|
3570 }
|
nicholas@2498
|
3571 for (var i = 0; i < node.response.length; i++) {
|
nicholas@2682
|
3572 surveyresult.appendChild(postCheckbox(this.parent.document, node.response[i]));
|
nicholas@2498
|
3573 }
|
nicholas@2498
|
3574 break;
|
nicholas@2498
|
3575 }
|
nicholas@2498
|
3576 };
|
nicholas@2498
|
3577 this.complete = function () {
|
nicholas@2498
|
3578 this.state = "complete";
|
nicholas@2498
|
3579 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2678
|
3580 };
|
nicholas@2498
|
3581 };
|
nicholas@2498
|
3582
|
nicholas@2498
|
3583 this.pageNode = function (parent, specification) {
|
nicholas@2498
|
3584 // Create one store per test page
|
nicholas@2498
|
3585 this.specification = specification;
|
nicholas@2498
|
3586 this.parent = parent;
|
nicholas@2498
|
3587 this.state = "empty";
|
nicholas@2498
|
3588 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2498
|
3589 this.XMLDOM.setAttribute('ref', specification.id);
|
nicholas@2498
|
3590 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
|
nicholas@2498
|
3591 this.XMLDOM.setAttribute("state", this.state);
|
n@2694
|
3592 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3593 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
|
nicholas@2498
|
3594 }
|
n@2694
|
3595 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3596 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
|
nicholas@2498
|
3597 }
|
nicholas@2498
|
3598
|
nicholas@2498
|
3599 // Add any page metrics
|
nicholas@2498
|
3600 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3601 this.XMLDOM.appendChild(page_metric);
|
nicholas@2498
|
3602
|
nicholas@2498
|
3603 // Add the audioelement
|
nicholas@2682
|
3604 this.specification.audioElements.forEach(function (element) {
|
nicholas@2498
|
3605 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2498
|
3606 aeNode.setAttribute('ref', element.id);
|
nicholas@2678
|
3607 if (element.name !== undefined) {
|
nicholas@2678
|
3608 aeNode.setAttribute('name', element.name);
|
nicholas@2678
|
3609 }
|
nicholas@2498
|
3610 aeNode.setAttribute('type', element.type);
|
nicholas@2498
|
3611 aeNode.setAttribute('url', element.url);
|
nicholas@2498
|
3612 aeNode.setAttribute('fqurl', qualifyURL(element.url));
|
nicholas@2498
|
3613 aeNode.setAttribute('gain', element.gain);
|
nicholas@2498
|
3614 if (element.type == 'anchor' || element.type == 'reference') {
|
nicholas@2498
|
3615 if (element.marker > 0) {
|
nicholas@2498
|
3616 aeNode.setAttribute('marker', element.marker);
|
nicholas@2464
|
3617 }
|
nicholas@2498
|
3618 }
|
nicholas@2498
|
3619 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3620 aeNode.appendChild(ae_metric);
|
nicholas@2498
|
3621 this.XMLDOM.appendChild(aeNode);
|
nicholas@2682
|
3622 }, this);
|
nicholas@2498
|
3623
|
nicholas@2498
|
3624 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3625
|
nicholas@2498
|
3626 this.complete = function () {
|
nicholas@2224
|
3627 this.state = "complete";
|
nicholas@2498
|
3628 this.XMLDOM.setAttribute("state", "complete");
|
nicholas@2678
|
3629 };
|
nicholas@2498
|
3630 };
|
nicholas@2498
|
3631 this.update = function () {
|
nicholas@2224
|
3632 this.SessionKey.update();
|
nicholas@2678
|
3633 };
|
nicholas@2498
|
3634 this.finish = function () {
|
nicholas@2678
|
3635 if (this.state === 0) {
|
nicholas@2224
|
3636 this.update();
|
nicholas@2498
|
3637 }
|
nicholas@2498
|
3638 this.state = 1;
|
nicholas@2498
|
3639 this.root.setAttribute("state", "complete");
|
nicholas@2498
|
3640 return this.root;
|
nicholas@2498
|
3641 };
|
nicholas@2224
|
3642 }
|
nicholas@2384
|
3643
|
nicholas@2401
|
3644 var window_depedancy_callback;
|
nicholas@2498
|
3645 window_depedancy_callback = window.setInterval(function () {
|
nicholas@2401
|
3646 if (check_dependancies()) {
|
nicholas@2401
|
3647 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
3648 onload();
|
nicholas@2401
|
3649 } else {
|
nicholas@2401
|
3650 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
3651 }
|
nicholas@2498
|
3652 }, 100);
|