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