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