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