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