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