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@3101
|
149 // Create the storage object
|
nicholas@3101
|
150 storage = new Storage();
|
nicholas@3101
|
151
|
nicholas@2498
|
152 // Create the interface object
|
nicholas@2498
|
153 interfaceContext = new Interface(specification);
|
nicholas@2498
|
154
|
nicholas@2498
|
155 // Define window callbacks for interface
|
nicholas@2498
|
156 window.onresize = function (event) {
|
nicholas@2498
|
157 interfaceContext.resizeWindow(event);
|
nicholas@2498
|
158 };
|
nicholas@2498
|
159
|
nicholas@2708
|
160 if (window.location.search.length !== 0) {
|
nicholas@2319
|
161 var search = window.location.search.split('?')[1];
|
nicholas@2319
|
162 // Now split the requests into pairs
|
nicholas@2319
|
163 var searchQueries = search.split('&');
|
nicholas@2708
|
164 var url;
|
nicholas@2498
|
165 for (var i in searchQueries) {
|
giuliomoro@2331
|
166 // Split each key-value pair
|
nicholas@2319
|
167 searchQueries[i] = searchQueries[i].split('=');
|
giuliomoro@2331
|
168 var key = searchQueries[i][0];
|
giuliomoro@2331
|
169 var value = decodeURIComponent(searchQueries[i][1]);
|
nicholas@2498
|
170 switch (key) {
|
nicholas@2498
|
171 case "url":
|
nicholas@2498
|
172 url = value;
|
nicholas@2708
|
173 specification.url = url;
|
nicholas@2498
|
174 break;
|
nicholas@2498
|
175 case "returnURL":
|
nicholas@2498
|
176 gReturnURL = value;
|
nicholas@2498
|
177 break;
|
nicholas@2498
|
178 case "saveFilenamePrefix":
|
nicholas@2722
|
179 storage.filenamePrefix = value;
|
nicholas@2498
|
180 break;
|
nicholas@2319
|
181 }
|
nicholas@2319
|
182 }
|
nicholas@2319
|
183 loadProjectSpec(url);
|
nicholas@2498
|
184 window.onbeforeunload = function () {
|
nicholas@2319
|
185 return "Please only leave this page once you have completed the tests. Are you sure you have completed all testing?";
|
nicholas@2319
|
186 };
|
nicholas@2319
|
187 }
|
nicholas@2360
|
188 interfaceContext.lightbox.resize();
|
nicholas@2224
|
189 };
|
nicholas@2224
|
190
|
nicholas@2224
|
191 function loadProjectSpec(url) {
|
nicholas@2498
|
192 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
|
nicholas@2498
|
193 // If url is null, request client to upload project XML document
|
nicholas@2498
|
194 var xmlhttp = new XMLHttpRequest();
|
nicholas@2498
|
195 xmlhttp.open("GET", 'xml/test-schema.xsd', true);
|
nicholas@2498
|
196 xmlhttp.onload = function () {
|
nicholas@2708
|
197 specification.processSchema(xmlhttp.response);
|
nicholas@2498
|
198 var r = new XMLHttpRequest();
|
nicholas@2498
|
199 r.open('GET', url, true);
|
nicholas@2498
|
200 r.onload = function () {
|
nicholas@2498
|
201 loadProjectSpecCallback(r.response);
|
nicholas@2498
|
202 };
|
nicholas@2498
|
203 r.onerror = function () {
|
nicholas@2224
|
204 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2224
|
205 var msg = document.createElement("h3");
|
nicholas@2224
|
206 msg.textContent = "FATAL ERROR";
|
nicholas@2224
|
207 var span = document.createElement("p");
|
nicholas@2224
|
208 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
|
209 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2224
|
210 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2708
|
211 };
|
nicholas@2498
|
212 r.send();
|
nicholas@2498
|
213 };
|
nicholas@2498
|
214 xmlhttp.send();
|
nicholas@2708
|
215 }
|
nicholas@2224
|
216
|
nicholas@2224
|
217 function loadProjectSpecCallback(response) {
|
nicholas@2498
|
218 // Function called after asynchronous download of XML project specification
|
nicholas@2498
|
219 //var decode = $.parseXML(response);
|
nicholas@2498
|
220 //projectXML = $(decode);
|
nicholas@2498
|
221
|
nicholas@2224
|
222 // Check if XML is new or a resumption
|
nicholas@2224
|
223 var parse = new DOMParser();
|
nicholas@2498
|
224 var responseDocument = parse.parseFromString(response, 'text/xml');
|
nicholas@2224
|
225 var errorNode = responseDocument.getElementsByTagName('parsererror');
|
nicholas@2708
|
226 var msg, span;
|
nicholas@2498
|
227 if (errorNode.length >= 1) {
|
nicholas@2708
|
228 msg = document.createElement("h3");
|
nicholas@2498
|
229 msg.textContent = "FATAL ERROR";
|
nicholas@2708
|
230 span = document.createElement("span");
|
nicholas@2498
|
231 span.textContent = "The XML parser returned the following errors when decoding your XML file";
|
nicholas@2498
|
232 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2498
|
233 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2498
|
234 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2498
|
235 document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
|
nicholas@2498
|
236 return;
|
nicholas@2498
|
237 }
|
nicholas@2708
|
238 if (responseDocument === undefined || responseDocument.firstChild === undefined) {
|
nicholas@2708
|
239 msg = document.createElement("h3");
|
nicholas@2498
|
240 msg.textContent = "FATAL ERROR";
|
nicholas@2708
|
241 span = document.createElement("span");
|
nicholas@2498
|
242 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
|
243 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2498
|
244 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2498
|
245 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2498
|
246 return;
|
nicholas@2224
|
247 }
|
nicholas@2247
|
248 if (responseDocument.firstChild.nodeName == "waet") {
|
nicholas@2224
|
249 // document is a specification
|
nicholas@2498
|
250
|
nicholas@2224
|
251 // Perform XML schema validation
|
nicholas@2224
|
252 var Module = {
|
nicholas@2224
|
253 xml: response,
|
nicholas@2708
|
254 schema: specification.getSchemaString(),
|
nicholas@2498
|
255 arguments: ["--noout", "--schema", 'test-schema.xsd', 'document.xml']
|
nicholas@2224
|
256 };
|
nicholas@2498
|
257 projectXML = responseDocument;
|
nicholas@2224
|
258 var xmllint = validateXML(Module);
|
nicholas@2224
|
259 console.log(xmllint);
|
nicholas@2498
|
260 if (xmllint != 'document.xml validates\n') {
|
nicholas@2224
|
261 document.getElementsByTagName('body')[0].innerHTML = null;
|
nicholas@2708
|
262 msg = document.createElement("h3");
|
nicholas@2224
|
263 msg.textContent = "FATAL ERROR";
|
nicholas@2708
|
264 span = document.createElement("h4");
|
nicholas@2224
|
265 span.textContent = "The XML validator returned the following errors when decoding your XML file";
|
nicholas@2224
|
266 document.getElementsByTagName('body')[0].appendChild(msg);
|
nicholas@2224
|
267 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
268 xmllint = xmllint.split('\n');
|
nicholas@2498
|
269 for (var i in xmllint) {
|
nicholas@2224
|
270 document.getElementsByTagName('body')[0].appendChild(document.createElement('br'));
|
nicholas@2708
|
271 span = document.createElement("span");
|
nicholas@2224
|
272 span.textContent = xmllint[i];
|
nicholas@2224
|
273 document.getElementsByTagName('body')[0].appendChild(span);
|
nicholas@2224
|
274 }
|
nicholas@2224
|
275 return;
|
nicholas@2224
|
276 }
|
nicholas@2224
|
277 // Build the specification
|
nicholas@2498
|
278 specification.decode(projectXML);
|
nicholas@2224
|
279 // Generate the session-key
|
nicholas@2224
|
280 storage.initialise();
|
nicholas@2498
|
281
|
nicholas@2247
|
282 } else if (responseDocument.firstChild.nodeName == "waetresult") {
|
nicholas@2224
|
283 // document is a result
|
nicholas@2498
|
284 projectXML = document.implementation.createDocument(null, "waet");
|
nicholas@2294
|
285 projectXML.firstChild.appendChild(responseDocument.getElementsByTagName('waet')[0].getElementsByTagName("setup")[0].cloneNode(true));
|
nicholas@2708
|
286 var child = responseDocument.firstChild.firstChild,
|
nicholas@2708
|
287 copy;
|
nicholas@2708
|
288 while (child !== null) {
|
nicholas@2224
|
289 if (child.nodeName == "survey") {
|
nicholas@2224
|
290 // One of the global survey elements
|
nicholas@2224
|
291 if (child.getAttribute("state") == "complete") {
|
nicholas@2224
|
292 // We need to remove this survey from <setup>
|
nicholas@2224
|
293 var location = child.getAttribute("location");
|
nicholas@2224
|
294 var globalSurveys = projectXML.getElementsByTagName("setup")[0].getElementsByTagName("survey")[0];
|
nicholas@2708
|
295 while (globalSurveys !== null) {
|
nicholas@2224
|
296 if (location == "pre" || location == "before") {
|
nicholas@2224
|
297 if (globalSurveys.getAttribute("location") == "pre" || globalSurveys.getAttribute("location") == "before") {
|
nicholas@2224
|
298 projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
|
nicholas@2224
|
299 break;
|
nicholas@2224
|
300 }
|
nicholas@2224
|
301 } else {
|
nicholas@2224
|
302 if (globalSurveys.getAttribute("location") == "post" || globalSurveys.getAttribute("location") == "after") {
|
nicholas@2224
|
303 projectXML.getElementsByTagName("setup")[0].removeChild(globalSurveys);
|
nicholas@2224
|
304 break;
|
nicholas@2224
|
305 }
|
nicholas@2224
|
306 }
|
nicholas@2224
|
307 globalSurveys = globalSurveys.nextElementSibling;
|
nicholas@2224
|
308 }
|
nicholas@2224
|
309 } else {
|
nicholas@2224
|
310 // We need to complete this, so it must be regenerated by store
|
nicholas@2708
|
311 copy = child;
|
nicholas@2224
|
312 child = child.previousElementSibling;
|
nicholas@2294
|
313 responseDocument.firstChild.removeChild(copy);
|
nicholas@2224
|
314 }
|
nicholas@2224
|
315 } else if (child.nodeName == "page") {
|
nicholas@2224
|
316 if (child.getAttribute("state") == "empty") {
|
nicholas@2224
|
317 // We need to complete this page
|
nicholas@2294
|
318 projectXML.firstChild.appendChild(responseDocument.getElementById(child.getAttribute("ref")).cloneNode(true));
|
nicholas@2708
|
319 copy = child;
|
nicholas@2224
|
320 child = child.previousElementSibling;
|
nicholas@2294
|
321 responseDocument.firstChild.removeChild(copy);
|
nicholas@2224
|
322 }
|
nicholas@2224
|
323 }
|
nicholas@2224
|
324 child = child.nextElementSibling;
|
nicholas@2224
|
325 }
|
nicholas@2224
|
326 // Build the specification
|
nicholas@2498
|
327 specification.decode(projectXML);
|
nicholas@2224
|
328 // Use the original
|
nicholas@2224
|
329 storage.initialise(responseDocument);
|
nicholas@2224
|
330 }
|
nicholas@2498
|
331 /// CHECK FOR SAMPLE RATE COMPATIBILITY
|
nicholas@2708
|
332 if (isFinite(specification.sampleRate)) {
|
nicholas@2498
|
333 if (Number(specification.sampleRate) != audioContext.sampleRate) {
|
nicholas@2498
|
334 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
|
335 interfaceContext.lightbox.post("Error", errStr);
|
nicholas@2498
|
336 return;
|
nicholas@2498
|
337 }
|
nicholas@2498
|
338 }
|
nicholas@2498
|
339
|
nicholas@2624
|
340 var getInterfaces = new XMLHttpRequest();
|
nicholas@2624
|
341 getInterfaces.open("GET", "interfaces/interfaces.json");
|
nicholas@2624
|
342 getInterfaces.onerror = function (e) {
|
nicholas@2624
|
343 throw (e);
|
nicholas@2708
|
344 };
|
nicholas@2624
|
345 getInterfaces.onload = function () {
|
nicholas@2624
|
346 if (getInterfaces.status !== 200) {
|
nicholas@2624
|
347 throw (new Error(getInterfaces.status));
|
nicholas@2624
|
348 }
|
nicholas@2624
|
349 // Get the current interface
|
nicholas@2624
|
350 var name = specification.interface,
|
nicholas@2624
|
351 head = document.getElementsByTagName("head")[0],
|
nicholas@2624
|
352 data = JSON.parse(getInterfaces.responseText),
|
nicholas@2624
|
353 interfaceObject = data.interfaces.find(function (e) {
|
nicholas@2624
|
354 return e.name == name;
|
nicholas@2624
|
355 });
|
nicholas@2624
|
356 if (!interfaceObject) {
|
nicholas@2624
|
357 throw ("Cannot load desired interface");
|
nicholas@2624
|
358 }
|
nicholas@2624
|
359 interfaceObject.scripts.forEach(function (v) {
|
nicholas@2624
|
360 var script = document.createElement("script");
|
nicholas@2624
|
361 script.setAttribute("type", "text/javascript");
|
nicholas@2624
|
362 script.setAttribute("src", v);
|
nicholas@2624
|
363 head.appendChild(script);
|
nicholas@2624
|
364 });
|
nicholas@2624
|
365 interfaceObject.css.forEach(function (v) {
|
nicholas@2624
|
366 var css = document.createElement("link");
|
nicholas@2624
|
367 css.setAttribute("rel", "stylesheet");
|
nicholas@2624
|
368 css.setAttribute("type", "text/css");
|
nicholas@2624
|
369 css.setAttribute("href", v);
|
nicholas@2624
|
370 head.appendChild(css);
|
nicholas@2624
|
371 });
|
nicholas@2708
|
372 };
|
nicholas@2624
|
373 getInterfaces.send();
|
nicholas@2498
|
374
|
nicholas@2708
|
375 if (gReturnURL !== undefined) {
|
nicholas@2498
|
376 console.log("returnURL Overide from " + specification.returnURL + " to " + gReturnURL);
|
nicholas@2329
|
377 specification.returnURL = gReturnURL;
|
nicholas@2329
|
378 }
|
nicholas@2708
|
379 if (gSaveFilenamePrefix !== undefined) {
|
giuliomoro@2337
|
380 specification.saveFilenamePrefix = gSaveFilenamePrefix;
|
giuliomoro@2337
|
381 }
|
nicholas@2498
|
382
|
nicholas@2498
|
383 // Create the audio engine object
|
nicholas@2498
|
384 audioEngineContext = new AudioEngine(specification);
|
nicholas@2224
|
385 }
|
nicholas@2224
|
386
|
nicholas@2224
|
387 function createProjectSave(destURL) {
|
nicholas@2224
|
388 // Clear the window.onbeforeunload
|
nicholas@2224
|
389 window.onbeforeunload = null;
|
nicholas@2498
|
390 // Save the data from interface into XML and send to destURL
|
nicholas@2498
|
391 // If destURL is null then download XML in client
|
nicholas@2498
|
392 // Now time to render file locally
|
nicholas@2733
|
393 var xmlDoc = storage.finish();
|
nicholas@2498
|
394 var parent = document.createElement("div");
|
nicholas@2498
|
395 parent.appendChild(xmlDoc);
|
nicholas@2498
|
396 var file = [parent.innerHTML];
|
nicholas@2498
|
397 if (destURL == "local") {
|
nicholas@2498
|
398 var bb = new Blob(file, {
|
nicholas@2498
|
399 type: 'application/xml'
|
nicholas@2498
|
400 });
|
nicholas@2498
|
401 var dnlk = window.URL.createObjectURL(bb);
|
nicholas@2498
|
402 var a = document.createElement("a");
|
nicholas@2498
|
403 a.hidden = '';
|
nicholas@2498
|
404 a.href = dnlk;
|
nicholas@2498
|
405 a.download = "save.xml";
|
nicholas@2498
|
406 a.textContent = "Save File";
|
nicholas@2498
|
407
|
nicholas@2498
|
408 popup.showPopup();
|
nicholas@2498
|
409 popup.popupContent.innerHTML = "<span>Please save the file below to give to your test supervisor</span><br>";
|
nicholas@2498
|
410 popup.popupContent.appendChild(a);
|
nicholas@2498
|
411 } else {
|
nicholas@2498
|
412 var projectReturn = "";
|
nicholas@2498
|
413 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
414 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2498
|
415 projectReturn = specification.projectReturn;
|
nicholas@2498
|
416 }
|
nicholas@2498
|
417 }
|
nicholas@2723
|
418 storage.SessionKey.finish().then(function (resolved) {
|
nicholas@2723
|
419 if (typeof specification.returnURL == "string" && specification.returnURL.length > 0) {
|
nicholas@2723
|
420 window.location = specification.returnURL;
|
nicholas@2723
|
421 } else {
|
nicholas@2723
|
422 popup.popupContent.textContent = 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@3101
|
1148 if (testState.stateIndex === -1 && specification.calibration) {
|
nicholas@2224
|
1149 advanceState();
|
nicholas@2224
|
1150 return;
|
nicholas@2224
|
1151 }
|
nicholas@2708
|
1152 var node = this.popupOptions[this.currentIndex],
|
nicholas@2774
|
1153 pass = true,
|
nicholas@2778
|
1154 timeDelta = (new Date() - lastNodeStart) / 1000.0;
|
nicholas@2774
|
1155 if (timeDelta < node.specification.minWait) {
|
nicholas@2778
|
1156 interfaceContext.lightbox.post("Error", "Not enough time has elapsed, please wait " + (node.specification.minWait - timeDelta).toFixed(0) + " seconds");
|
nicholas@2774
|
1157 return;
|
nicholas@2774
|
1158 }
|
nicholas@2775
|
1159 node.elapsedTime = timeDelta;
|
nicholas@2498
|
1160 if (node.specification.type == 'question') {
|
nicholas@2498
|
1161 // Must extract the question data
|
nicholas@2708
|
1162 pass = processQuestion.call(this, node);
|
nicholas@2498
|
1163 } else if (node.specification.type == 'checkbox') {
|
nicholas@2498
|
1164 // Must extract checkbox data
|
nicholas@2708
|
1165 pass = processCheckbox.call(this, node);
|
nicholas@2708
|
1166 } else if (node.specification.type == "radio") {
|
nicholas@2464
|
1167 // Perform the conditional
|
nicholas@2708
|
1168 pass = processRadio.call(this, node);
|
nicholas@2708
|
1169 } else if (node.specification.type == "number") {
|
nicholas@2464
|
1170 // Perform the conditional
|
nicholas@2708
|
1171 pass = processNumber.call(this, node);
|
n@2583
|
1172 } else if (node.specification.type == 'slider') {
|
nicholas@2708
|
1173 pass = processSlider.call(this, node);
|
nicholas@2708
|
1174 }
|
nicholas@2708
|
1175 if (pass === false) {
|
nicholas@2708
|
1176 return;
|
nicholas@2498
|
1177 }
|
nicholas@2498
|
1178 this.currentIndex++;
|
nicholas@2498
|
1179 if (this.currentIndex < this.popupOptions.length) {
|
nicholas@2498
|
1180 this.postNode();
|
nicholas@2498
|
1181 } else {
|
nicholas@2498
|
1182 // Reached the end of the popupOptions
|
nicholas@2645
|
1183 this.popupTitle.innerHTML = "";
|
nicholas@2498
|
1184 this.popupResponse.innerHTML = "";
|
nicholas@2498
|
1185 this.hidePopup();
|
nicholas@2708
|
1186 this.popupOptions.forEach(function (node) {
|
nicholas@2498
|
1187 this.store.postResult(node);
|
nicholas@2708
|
1188 }, this);
|
nicholas@2224
|
1189 this.store.complete();
|
nicholas@2498
|
1190 advanceState();
|
nicholas@2498
|
1191 }
|
nicholas@2498
|
1192 };
|
nicholas@2498
|
1193
|
nicholas@2498
|
1194 this.previousClick = function () {
|
nicholas@2498
|
1195 // Triggered when the 'Back' button is clicked in the survey
|
nicholas@2498
|
1196 if (this.currentIndex > 0) {
|
nicholas@2498
|
1197 this.currentIndex--;
|
nicholas@2498
|
1198 this.postNode();
|
nicholas@2498
|
1199 }
|
nicholas@2498
|
1200 };
|
nicholas@2498
|
1201
|
nicholas@2498
|
1202 this.resize = function (event) {
|
nicholas@2498
|
1203 // Called on window resize;
|
nicholas@2708
|
1204 if (this.popup !== null) {
|
nicholas@2498
|
1205 this.popup.style.left = (window.innerWidth / 2) - 250 + 'px';
|
nicholas@2498
|
1206 this.popup.style.top = (window.innerHeight / 2) - 125 + 'px';
|
nicholas@2498
|
1207 var blank = document.getElementsByClassName('testHalt')[0];
|
nicholas@2498
|
1208 blank.style.width = window.innerWidth;
|
nicholas@2498
|
1209 blank.style.height = window.innerHeight;
|
nicholas@2498
|
1210 }
|
nicholas@2498
|
1211 };
|
nicholas@2498
|
1212 this.hideNextButton = function () {
|
nicholas@2224
|
1213 this.buttonProceed.style.visibility = "hidden";
|
nicholas@2708
|
1214 };
|
nicholas@2498
|
1215 this.hidePreviousButton = function () {
|
nicholas@2224
|
1216 this.buttonPrevious.style.visibility = "hidden";
|
nicholas@2708
|
1217 };
|
nicholas@2498
|
1218 this.showNextButton = function () {
|
nicholas@2224
|
1219 this.buttonProceed.style.visibility = "visible";
|
nicholas@2708
|
1220 };
|
nicholas@2498
|
1221 this.showPreviousButton = function () {
|
nicholas@2224
|
1222 this.buttonPrevious.style.visibility = "visible";
|
nicholas@2708
|
1223 };
|
nicholas@2224
|
1224 }
|
nicholas@2224
|
1225
|
nicholas@2498
|
1226 function advanceState() {
|
nicholas@2498
|
1227 // Just for complete clarity
|
nicholas@2498
|
1228 testState.advanceState();
|
nicholas@2224
|
1229 }
|
nicholas@2224
|
1230
|
nicholas@2498
|
1231 function stateMachine() {
|
nicholas@2498
|
1232 // Object prototype for tracking and managing the test state
|
nicholas@2722
|
1233
|
n@2716
|
1234 function pickSubPool(pool, numElements) {
|
n@2716
|
1235 // Assumes each element of pool has function "alwaysInclude"
|
n@2716
|
1236
|
n@2716
|
1237 // First extract those excluded from picking process
|
n@2716
|
1238 var picked = [];
|
nicholas@2833
|
1239 pool.forEach(function (e, i) {
|
n@2716
|
1240 if (e.alwaysInclude) {
|
nicholas@2833
|
1241 picked.push(pool.splice(i, 1)[0]);
|
n@2716
|
1242 }
|
n@2716
|
1243 });
|
n@2716
|
1244
|
n@2716
|
1245 return picked.concat(randomSubArray(pool, numElements - picked.length));
|
n@2716
|
1246 }
|
nicholas@2722
|
1247
|
nicholas@2498
|
1248 this.stateMap = [];
|
nicholas@2498
|
1249 this.preTestSurvey = null;
|
nicholas@2498
|
1250 this.postTestSurvey = null;
|
nicholas@2498
|
1251 this.stateIndex = null;
|
nicholas@2498
|
1252 this.currentStateMap = null;
|
nicholas@2498
|
1253 this.currentStatePosition = null;
|
nicholas@2224
|
1254 this.currentStore = null;
|
nicholas@2498
|
1255 this.initialise = function () {
|
nicholas@2498
|
1256
|
n@2909
|
1257 function randomiseElements(page) {
|
n@2909
|
1258 // Get the elements which are fixed / labelled
|
n@2909
|
1259 var fixed = [],
|
n@2909
|
1260 or = [],
|
n@2909
|
1261 remainder = [];
|
n@2909
|
1262 page.audioElements.forEach(function (a) {
|
n@2909
|
1263 if (a.label.length > 0 || a.postion !== undefined) {
|
n@2909
|
1264 fixed.push(a);
|
n@2909
|
1265 } else if (a.type === "outside-reference") {
|
n@2909
|
1266 or.push(a);
|
n@2909
|
1267 } else {
|
n@2909
|
1268 remainder.push(a);
|
n@2909
|
1269 }
|
n@2979
|
1270 });
|
n@2909
|
1271 if (page.poolSize > 0 || page.randomiseOrder) {
|
n@2909
|
1272 page.randomiseOrder = true;
|
n@2909
|
1273 if (page.poolSize === 0) {
|
n@2909
|
1274 page.poolSize = page.audioElements.length;
|
n@2909
|
1275 }
|
n@2909
|
1276 page.poolSize -= fixed.length;
|
n@2909
|
1277 remainder = pickSubPool(remainder, page.poolSize);
|
n@2909
|
1278 }
|
n@2909
|
1279 // Randomise the remainders
|
n@2909
|
1280 if (page.randomiseOrder) {
|
n@2909
|
1281 remainder = randomiseOrder(remainder);
|
n@2909
|
1282 }
|
n@2909
|
1283 fixed = fixed.concat(remainder);
|
n@2909
|
1284 page.audioElements = fixed.concat(or);
|
n@2909
|
1285 page.audioElements.forEach(function (a, i) {
|
n@2909
|
1286 a.position = i;
|
n@2909
|
1287 });
|
n@2909
|
1288 }
|
n@2909
|
1289
|
nicholas@2498
|
1290 // Get the data from Specification
|
nicholas@2498
|
1291 var pagePool = [];
|
nicholas@2722
|
1292 specification.pages.forEach(function (page) {
|
n@2716
|
1293 if (page.position !== null || page.alwaysInclude) {
|
n@2716
|
1294 page.alwaysInclude = true;
|
n@2716
|
1295 }
|
n@2716
|
1296 pagePool.push(page);
|
n@2717
|
1297 });
|
n@2716
|
1298 if (specification.numPages > 0) {
|
n@2716
|
1299 specification.randomiseOrder = true;
|
n@2716
|
1300 pagePool = pickSubPool(pagePool, specification.numPages);
|
n@2716
|
1301 }
|
n@2716
|
1302
|
n@2716
|
1303 // Now get the order of pages
|
n@2716
|
1304 var fixed = [];
|
nicholas@2722
|
1305 pagePool.forEach(function (page) {
|
nicholas@2748
|
1306 if (page.position !== undefined) {
|
n@2716
|
1307 fixed.push(page);
|
n@2716
|
1308 var i = pagePool.indexOf(page);
|
n@2716
|
1309 pagePool.splice(i, 1);
|
nicholas@2224
|
1310 }
|
n@2717
|
1311 });
|
nicholas@2498
|
1312
|
n@2716
|
1313 if (specification.randomiseOrder) {
|
n@2716
|
1314 pagePool = randomiseOrder(pagePool);
|
nicholas@2224
|
1315 }
|
nicholas@2498
|
1316
|
n@2716
|
1317 // Place in the correct order
|
nicholas@2722
|
1318 fixed.forEach(function (page) {
|
n@2716
|
1319 pagePool.splice(page.position, 0, page);
|
n@2717
|
1320 });
|
n@2716
|
1321
|
n@2716
|
1322 // Now process the pages
|
n@2716
|
1323 pagePool.forEach(function (page, i) {
|
n@2716
|
1324 page.presentedId = i;
|
n@2716
|
1325 this.stateMap.push(page);
|
n@2716
|
1326 var elements = page.audioElements;
|
n@2909
|
1327 randomiseElements(page);
|
n@2716
|
1328 storage.createTestPageStore(page);
|
n@2716
|
1329 audioEngineContext.loadPageData(page);
|
n@2716
|
1330 }, this);
|
nicholas@2674
|
1331
|
nicholas@2708
|
1332 if (specification.preTest !== null) {
|
nicholas@2498
|
1333 this.preTestSurvey = specification.preTest;
|
nicholas@2498
|
1334 }
|
nicholas@2708
|
1335 if (specification.postTest !== null) {
|
nicholas@2498
|
1336 this.postTestSurvey = specification.postTest;
|
nicholas@2498
|
1337 }
|
nicholas@2498
|
1338
|
nicholas@2498
|
1339 if (this.stateMap.length > 0) {
|
nicholas@2708
|
1340 if (this.stateIndex !== null) {
|
nicholas@2498
|
1341 console.log('NOTE - State already initialise');
|
nicholas@2498
|
1342 }
|
nicholas@2498
|
1343 this.stateIndex = -2;
|
nicholas@2224
|
1344 console.log('Starting test...');
|
nicholas@2498
|
1345 } else {
|
nicholas@2498
|
1346 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
|
nicholas@2498
|
1347 }
|
nicholas@2498
|
1348 };
|
nicholas@2498
|
1349 this.advanceState = function () {
|
nicholas@2708
|
1350 if (this.stateIndex === null) {
|
nicholas@2498
|
1351 this.initialise();
|
nicholas@2498
|
1352 }
|
nicholas@2357
|
1353 if (this.stateIndex > -2) {
|
nicholas@2357
|
1354 storage.update();
|
nicholas@2357
|
1355 }
|
nicholas@2498
|
1356 if (this.stateIndex == -2) {
|
nicholas@2224
|
1357 this.stateIndex++;
|
nicholas@2708
|
1358 if (this.preTestSurvey !== undefined) {
|
nicholas@2498
|
1359 popup.initState(this.preTestSurvey, storage.globalPreTest);
|
nicholas@2498
|
1360 } else {
|
nicholas@2498
|
1361 this.advanceState();
|
nicholas@2498
|
1362 }
|
nicholas@2498
|
1363 } else if (this.stateIndex == -1) {
|
nicholas@3101
|
1364 if (interfaceContext.calibrationTests.checkFrequencies) {
|
nicholas@2224
|
1365 popup.showPopup();
|
nicholas@3101
|
1366 popup.popupTitle.textContent = "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@3101
|
1367 interfaceContext.calibrationTests.performFrequencyCheck(popup.popupResponse);
|
nicholas@3101
|
1368 popup.hidePreviousButton();
|
nicholas@3101
|
1369 } else if (interfaceContext.calibrationTests.checkChannels) {
|
nicholas@3101
|
1370 popup.showPopup();
|
nicholas@3101
|
1371 popup.popupTitle.textContent = "Click play to start the audio, the click the button corresponding to where the sound appears to be coming from.";
|
nicholas@3101
|
1372 interfaceContext.calibrationTests.performChannelCheck(popup.popupResponse);
|
nicholas@2224
|
1373 popup.hidePreviousButton();
|
nicholas@2224
|
1374 } else {
|
nicholas@3101
|
1375 this.stateIndex++;
|
nicholas@2224
|
1376 this.advanceState();
|
nicholas@2224
|
1377 }
|
nicholas@2498
|
1378 } else if (this.stateIndex == this.stateMap.length) {
|
nicholas@2498
|
1379 // All test pages complete, post test
|
nicholas@2498
|
1380 console.log('Ending test ...');
|
nicholas@2498
|
1381 this.stateIndex++;
|
nicholas@2708
|
1382 if (this.postTestSurvey === undefined) {
|
nicholas@2498
|
1383 this.advanceState();
|
nicholas@2498
|
1384 } else {
|
nicholas@2498
|
1385 popup.initState(this.postTestSurvey, storage.globalPostTest);
|
nicholas@2498
|
1386 }
|
nicholas@2498
|
1387 } else if (this.stateIndex > this.stateMap.length) {
|
nicholas@2498
|
1388 createProjectSave(specification.projectReturn);
|
nicholas@2498
|
1389 } else {
|
nicholas@2224
|
1390 popup.hidePopup();
|
nicholas@2708
|
1391 if (this.currentStateMap === null) {
|
nicholas@2498
|
1392 this.currentStateMap = this.stateMap[this.stateIndex];
|
nicholas@2498
|
1393
|
nicholas@2224
|
1394 this.currentStore = storage.testPages[this.stateIndex];
|
nicholas@2708
|
1395 if (this.currentStateMap.preTest !== undefined) {
|
nicholas@2498
|
1396 this.currentStatePosition = 'pre';
|
nicholas@2498
|
1397 popup.initState(this.currentStateMap.preTest, storage.testPages[this.stateIndex].preTest);
|
nicholas@2498
|
1398 } else {
|
nicholas@2498
|
1399 this.currentStatePosition = 'test';
|
nicholas@2498
|
1400 }
|
nicholas@2498
|
1401 interfaceContext.newPage(this.currentStateMap, storage.testPages[this.stateIndex]);
|
nicholas@2498
|
1402 return;
|
nicholas@2498
|
1403 }
|
nicholas@2498
|
1404 switch (this.currentStatePosition) {
|
nicholas@2498
|
1405 case 'pre':
|
nicholas@2498
|
1406 this.currentStatePosition = 'test';
|
nicholas@2498
|
1407 break;
|
nicholas@2498
|
1408 case 'test':
|
nicholas@2498
|
1409 this.currentStatePosition = 'post';
|
nicholas@2498
|
1410 // Save the data
|
nicholas@2498
|
1411 this.testPageCompleted();
|
nicholas@2708
|
1412 if (this.currentStateMap.postTest === undefined) {
|
nicholas@2498
|
1413 this.advanceState();
|
nicholas@2498
|
1414 return;
|
nicholas@2498
|
1415 } else {
|
nicholas@2498
|
1416 popup.initState(this.currentStateMap.postTest, storage.testPages[this.stateIndex].postTest);
|
nicholas@2498
|
1417 }
|
nicholas@2498
|
1418 break;
|
nicholas@2498
|
1419 case 'post':
|
nicholas@2498
|
1420 this.stateIndex++;
|
nicholas@2498
|
1421 this.currentStateMap = null;
|
nicholas@2498
|
1422 this.advanceState();
|
nicholas@2498
|
1423 break;
|
nicholas@2708
|
1424 }
|
nicholas@2498
|
1425 }
|
nicholas@2498
|
1426 };
|
nicholas@2498
|
1427
|
nicholas@2498
|
1428 this.testPageCompleted = function () {
|
nicholas@2498
|
1429 // Function called each time a test page has been completed
|
nicholas@2498
|
1430 var storePoint = storage.testPages[this.stateIndex];
|
nicholas@2498
|
1431 // First get the test metric
|
nicholas@2498
|
1432
|
nicholas@2498
|
1433 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
|
nicholas@2498
|
1434 if (audioEngineContext.metric.enableTestTimer) {
|
nicholas@2498
|
1435 var testTime = storePoint.parent.document.createElement('metricresult');
|
nicholas@2498
|
1436 testTime.id = 'testTime';
|
nicholas@2498
|
1437 testTime.textContent = audioEngineContext.timer.testDuration;
|
nicholas@2498
|
1438 metric.appendChild(testTime);
|
nicholas@2498
|
1439 }
|
nicholas@2498
|
1440
|
nicholas@2498
|
1441 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2708
|
1442 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2498
|
1443 ao.exportXMLDOM();
|
nicholas@2708
|
1444 });
|
nicholas@2708
|
1445 interfaceContext.commentQuestions.forEach(function (element) {
|
nicholas@2498
|
1446 element.exportXMLDOM(storePoint);
|
nicholas@2708
|
1447 });
|
nicholas@2498
|
1448 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
|
nicholas@2224
|
1449 storePoint.complete();
|
nicholas@2498
|
1450 };
|
nicholas@2498
|
1451
|
nicholas@2498
|
1452 this.getCurrentTestPage = function () {
|
nicholas@2498
|
1453 if (this.stateIndex >= 0 && this.stateIndex < this.stateMap.length) {
|
nicholas@2310
|
1454 return this.currentStateMap;
|
nicholas@2310
|
1455 } else {
|
nicholas@2310
|
1456 return null;
|
nicholas@2310
|
1457 }
|
nicholas@2708
|
1458 };
|
nicholas@2498
|
1459 this.getCurrentTestPageStore = function () {
|
nicholas@2498
|
1460 if (this.stateIndex >= 0 && this.stateIndex < this.stateMap.length) {
|
nicholas@2312
|
1461 return this.currentStore;
|
nicholas@2312
|
1462 } else {
|
nicholas@2312
|
1463 return null;
|
nicholas@2312
|
1464 }
|
nicholas@2708
|
1465 };
|
nicholas@2224
|
1466 }
|
nicholas@2224
|
1467
|
nicholas@2224
|
1468 function AudioEngine(specification) {
|
nicholas@2498
|
1469
|
nicholas@2498
|
1470 // Create two output paths, the main outputGain and fooGain.
|
nicholas@2498
|
1471 // Output gain is default to 1 and any items for playback route here
|
nicholas@2498
|
1472 // Foo gain is used for analysis to ensure paths get processed, but are not heard
|
nicholas@2498
|
1473 // because web audio will optimise and any route which does not go to the destination gets ignored.
|
nicholas@2498
|
1474 this.outputGain = audioContext.createGain();
|
nicholas@2498
|
1475 this.fooGain = audioContext.createGain();
|
nicholas@2508
|
1476 this.fooGain.gain.value = 0;
|
nicholas@2498
|
1477
|
nicholas@2498
|
1478 // Use this to detect playback state: 0 - stopped, 1 - playing
|
nicholas@2498
|
1479 this.status = 0;
|
nicholas@2498
|
1480
|
nicholas@2498
|
1481 // Connect both gains to output
|
nicholas@2498
|
1482 this.outputGain.connect(audioContext.destination);
|
nicholas@2498
|
1483 this.fooGain.connect(audioContext.destination);
|
nicholas@2498
|
1484
|
nicholas@2498
|
1485 // Create the timer Object
|
nicholas@2498
|
1486 this.timer = new timer();
|
nicholas@2498
|
1487 // Create session metrics
|
nicholas@2498
|
1488 this.metric = new sessionMetrics(this, specification);
|
nicholas@2498
|
1489
|
nicholas@2498
|
1490 this.loopPlayback = false;
|
nicholas@2351
|
1491 this.synchPlayback = false;
|
nicholas@2351
|
1492 this.pageSpecification = null;
|
nicholas@2498
|
1493
|
nicholas@2498
|
1494 this.pageStore = null;
|
nicholas@2498
|
1495
|
nicholas@2508
|
1496 // Chrome 53+ Error solution
|
nicholas@2508
|
1497 // Empty buffer for keep-alive
|
nicholas@2508
|
1498 var nullBuffer = audioContext.createBuffer(1, audioContext.sampleRate, audioContext.sampleRate);
|
nicholas@2508
|
1499 this.nullBufferSource = audioContext.createBufferSource();
|
nicholas@2508
|
1500 this.nullBufferSource.buffer = nullBuffer;
|
nicholas@2508
|
1501 this.nullBufferSource.loop = true;
|
nicholas@2508
|
1502 this.nullBufferSource.start(0);
|
nicholas@2508
|
1503
|
nicholas@2498
|
1504 // Create store for new audioObjects
|
nicholas@2498
|
1505 this.audioObjects = [];
|
nicholas@2498
|
1506
|
nicholas@2498
|
1507 this.buffers = [];
|
nicholas@2498
|
1508 this.bufferObj = function () {
|
nicholas@2617
|
1509 var urls = [];
|
nicholas@2498
|
1510 this.buffer = null;
|
nicholas@2498
|
1511 this.users = [];
|
nicholas@2224
|
1512 this.progress = 0;
|
nicholas@2224
|
1513 this.status = 0;
|
nicholas@2498
|
1514 this.ready = function () {
|
nicholas@2498
|
1515 if (this.status >= 2) {
|
nicholas@2224
|
1516 this.status = 3;
|
nicholas@2224
|
1517 }
|
nicholas@2498
|
1518 for (var i = 0; i < this.users.length; i++) {
|
nicholas@2498
|
1519 this.users[i].state = 1;
|
nicholas@2708
|
1520 if (this.users[i].interfaceDOM !== null) {
|
nicholas@2498
|
1521 this.users[i].bufferLoaded(this);
|
nicholas@2498
|
1522 }
|
nicholas@2498
|
1523 }
|
nicholas@2498
|
1524 };
|
nicholas@2617
|
1525 this.setUrls = function (obj) {
|
nicholas@2617
|
1526 // Obj must be an array of pairs:
|
nicholas@2617
|
1527 // [{sampleRate, url}]
|
nicholas@2617
|
1528 var localFs = audioContext.sampleRate,
|
nicholas@2617
|
1529 list = [],
|
nicholas@2617
|
1530 i;
|
nicholas@2617
|
1531 for (i = 0; i < obj.length; i++) {
|
nicholas@2617
|
1532 if (obj[i].sampleRate == localFs) {
|
nicholas@2617
|
1533 list.push(obj.splice(i, 1)[0]);
|
nicholas@2617
|
1534 }
|
nicholas@2617
|
1535 }
|
nicholas@2617
|
1536 list = list.concat(obj);
|
nicholas@2617
|
1537 urls = list;
|
nicholas@2617
|
1538 };
|
nicholas@2617
|
1539 this.hasUrl = function (checkUrl) {
|
nicholas@2617
|
1540 var l = urls.length,
|
nicholas@2617
|
1541 i;
|
nicholas@2617
|
1542 for (i = 0; i < l; i++) {
|
nicholas@2617
|
1543 if (urls[i].url == checkUrl) {
|
nicholas@2617
|
1544 return true;
|
nicholas@2617
|
1545 }
|
nicholas@2617
|
1546 }
|
nicholas@2617
|
1547 return false;
|
nicholas@2708
|
1548 };
|
nicholas@2617
|
1549 this.getMedia = function () {
|
nicholas@2615
|
1550 var self = this;
|
nicholas@2616
|
1551 var currentUrlIndex = 0;
|
nicholas@2498
|
1552
|
nicholas@2615
|
1553 function get(fqurl) {
|
nicholas@2615
|
1554 return new Promise(function (resolve, reject) {
|
nicholas@2615
|
1555 var req = new XMLHttpRequest();
|
nicholas@2615
|
1556 req.open('GET', fqurl, true);
|
nicholas@2615
|
1557 req.responseType = 'arraybuffer';
|
nicholas@2615
|
1558 req.onload = function () {
|
nicholas@2615
|
1559 if (req.status == 200) {
|
nicholas@2615
|
1560 resolve(req.response);
|
nicholas@2615
|
1561 }
|
nicholas@2615
|
1562 };
|
nicholas@2615
|
1563 req.onerror = function () {
|
nicholas@2615
|
1564 reject(new Error(req.statusText));
|
nicholas@2615
|
1565 };
|
nicholas@2615
|
1566
|
nicholas@2615
|
1567 req.addEventListener("progress", progressCallback.bind(self));
|
nicholas@2615
|
1568 req.send();
|
nicholas@2615
|
1569 });
|
nicholas@2615
|
1570 }
|
nicholas@2615
|
1571
|
nicholas@2615
|
1572 function getNextURL() {
|
nicholas@2615
|
1573 currentUrlIndex++;
|
nicholas@2615
|
1574 var self = this;
|
nicholas@2617
|
1575 if (currentUrlIndex >= urls.length) {
|
nicholas@2615
|
1576 processError();
|
nicholas@2615
|
1577 } else {
|
nicholas@2617
|
1578 return get(urls[currentUrlIndex].url).then(processAudio.bind(self)).catch(getNextURL.bind(self));
|
nicholas@2615
|
1579 }
|
nicholas@2615
|
1580 }
|
nicholas@2498
|
1581
|
nicholas@2498
|
1582 // Create callback to decode the data asynchronously
|
nicholas@2615
|
1583 function processAudio(response) {
|
nicholas@2615
|
1584 var self = this;
|
nicholas@2615
|
1585 return audioContext.decodeAudioData(response, function (decodedData) {
|
nicholas@2615
|
1586 self.buffer = decodedData;
|
nicholas@2615
|
1587 self.status = 2;
|
nicholas@2615
|
1588 calculateLoudness(self, "I");
|
nicholas@2615
|
1589 return true;
|
nicholas@2498
|
1590 }, function (e) {
|
nicholas@2403
|
1591 var waveObj = new WAVE();
|
nicholas@2708
|
1592 if (waveObj.open(response) === 0) {
|
nicholas@2615
|
1593 self.buffer = audioContext.createBuffer(waveObj.num_channels, waveObj.num_samples, waveObj.sample_rate);
|
nicholas@2498
|
1594 for (var c = 0; c < waveObj.num_channels; c++) {
|
nicholas@2615
|
1595 var buffer_ptr = self.buffer.getChannelData(c);
|
nicholas@2498
|
1596 for (var n = 0; n < waveObj.num_samples; n++) {
|
nicholas@2403
|
1597 buffer_ptr[n] = waveObj.decoded_data[c][n];
|
nicholas@2224
|
1598 }
|
nicholas@2224
|
1599 }
|
nicholas@2403
|
1600 }
|
nicholas@2708
|
1601 if (self.buffer !== undefined) {
|
nicholas@2615
|
1602 self.status = 2;
|
nicholas@2615
|
1603 calculateLoudness(self, "I");
|
nicholas@2615
|
1604 return true;
|
nicholas@2403
|
1605 }
|
nicholas@2708
|
1606 waveObj = undefined;
|
nicholas@2615
|
1607 return false;
|
nicholas@2403
|
1608 });
|
nicholas@2615
|
1609 }
|
nicholas@2498
|
1610
|
nicholas@2224
|
1611 // Create callback for any error in loading
|
nicholas@2615
|
1612 function processError() {
|
nicholas@2615
|
1613 this.status = -1;
|
nicholas@2615
|
1614 for (var i = 0; i < this.users.length; i++) {
|
nicholas@2615
|
1615 this.users[i].state = -1;
|
nicholas@2708
|
1616 if (this.users[i].interfaceDOM !== null) {
|
nicholas@2615
|
1617 this.users[i].bufferLoaded(this);
|
nicholas@2224
|
1618 }
|
nicholas@2224
|
1619 }
|
nicholas@2617
|
1620 interfaceContext.lightbox.post("Error", "Could not load resource " + urls[currentUrlIndex].url);
|
nicholas@2224
|
1621 }
|
nicholas@2498
|
1622
|
nicholas@2615
|
1623 function progressCallback(event) {
|
nicholas@2498
|
1624 if (event.lengthComputable) {
|
nicholas@2615
|
1625 this.progress = event.loaded / event.total;
|
nicholas@2615
|
1626 for (var i = 0; i < this.users.length; i++) {
|
nicholas@2708
|
1627 if (this.users[i].interfaceDOM !== null) {
|
nicholas@2615
|
1628 if (typeof this.users[i].interfaceDOM.updateLoading === "function") {
|
nicholas@2615
|
1629 this.users[i].interfaceDOM.updateLoading(this.progress * 100);
|
nicholas@2498
|
1630 }
|
nicholas@2498
|
1631 }
|
nicholas@2498
|
1632 }
|
nicholas@2498
|
1633 }
|
nicholas@2708
|
1634 }
|
nicholas@2615
|
1635
|
nicholas@2615
|
1636 this.progress = 0;
|
nicholas@2224
|
1637 this.status = 1;
|
nicholas@2617
|
1638 currentUrlIndex = 0;
|
nicholas@2617
|
1639 get(urls[0].url).then(processAudio.bind(self)).catch(getNextURL.bind(self));
|
nicholas@2498
|
1640 };
|
nicholas@2498
|
1641
|
nicholas@2498
|
1642 this.registerAudioObject = function (audioObject) {
|
nicholas@2224
|
1643 // Called by an audioObject to register to the buffer for use
|
nicholas@2224
|
1644 // First check if already in the register pool
|
nicholas@2708
|
1645 this.users.forEach(function (object) {
|
nicholas@2708
|
1646 if (audioObject.id == object.id) {
|
nicholas@2498
|
1647 return 0;
|
nicholas@2498
|
1648 }
|
nicholas@2708
|
1649 });
|
nicholas@2224
|
1650 this.users.push(audioObject);
|
nicholas@2498
|
1651 if (this.status == 3 || this.status == -1) {
|
nicholas@2224
|
1652 // The buffer is already ready, trigger bufferLoaded
|
nicholas@2224
|
1653 audioObject.bufferLoaded(this);
|
nicholas@2224
|
1654 }
|
nicholas@2224
|
1655 };
|
nicholas@2498
|
1656
|
nicholas@2498
|
1657 this.copyBuffer = function (preSilenceTime, postSilenceTime) {
|
nicholas@2224
|
1658 // Copies the entire bufferObj.
|
nicholas@2708
|
1659 if (preSilenceTime === undefined) {
|
nicholas@2498
|
1660 preSilenceTime = 0;
|
nicholas@2498
|
1661 }
|
nicholas@2708
|
1662 if (postSilenceTime === undefined) {
|
nicholas@2498
|
1663 postSilenceTime = 0;
|
nicholas@2498
|
1664 }
|
nicholas@2498
|
1665 var preSilenceSamples = secondsToSamples(preSilenceTime, this.buffer.sampleRate);
|
nicholas@2498
|
1666 var postSilenceSamples = secondsToSamples(postSilenceTime, this.buffer.sampleRate);
|
nicholas@2498
|
1667 var newLength = this.buffer.length + preSilenceSamples + postSilenceSamples;
|
nicholas@2460
|
1668 var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
|
nicholas@2708
|
1669 var c;
|
nicholas@2224
|
1670 // Now we can use some efficient background copy schemes if we are just padding the end
|
nicholas@2708
|
1671 if (preSilenceSamples === 0 && typeof copybuffer.copyToChannel === "function") {
|
nicholas@2708
|
1672 for (c = 0; c < this.buffer.numberOfChannels; c++) {
|
nicholas@2498
|
1673 copybuffer.copyToChannel(this.buffer.getChannelData(c), c);
|
nicholas@2224
|
1674 }
|
nicholas@2224
|
1675 } else {
|
nicholas@2708
|
1676 for (c = 0; c < this.buffer.numberOfChannels; c++) {
|
nicholas@2224
|
1677 var src = this.buffer.getChannelData(c);
|
nicholas@2460
|
1678 var dst = copybuffer.getChannelData(c);
|
nicholas@2498
|
1679 for (var n = 0; n < src.length; n++)
|
nicholas@2498
|
1680 dst[n + preSilenceSamples] = src[n];
|
nicholas@2224
|
1681 }
|
nicholas@2224
|
1682 }
|
nicholas@2224
|
1683 // Copy in the rest of the buffer information
|
nicholas@2460
|
1684 copybuffer.lufs = this.buffer.lufs;
|
nicholas@2460
|
1685 copybuffer.playbackGain = this.buffer.playbackGain;
|
nicholas@2460
|
1686 return copybuffer;
|
nicholas@2708
|
1687 };
|
nicholas@2498
|
1688
|
nicholas@2498
|
1689 this.cropBuffer = function (startTime, stopTime) {
|
nicholas@2460
|
1690 // Copy and return the cropped buffer
|
nicholas@2498
|
1691 var start_sample = Math.floor(startTime * this.buffer.sampleRate);
|
nicholas@2498
|
1692 var stop_sample = Math.floor(stopTime * this.buffer.sampleRate);
|
nicholas@2460
|
1693 var newLength = stop_sample - start_sample;
|
nicholas@2460
|
1694 var copybuffer = audioContext.createBuffer(this.buffer.numberOfChannels, newLength, this.buffer.sampleRate);
|
nicholas@2460
|
1695 // Now we can use some efficient background copy schemes if we are just padding the end
|
nicholas@2498
|
1696 for (var c = 0; c < this.buffer.numberOfChannels; c++) {
|
nicholas@2460
|
1697 var buffer = this.buffer.getChannelData(c);
|
nicholas@2498
|
1698 var sub_frame = buffer.subarray(start_sample, stop_sample);
|
nicholas@2460
|
1699 if (typeof copybuffer.copyToChannel == "function") {
|
nicholas@2498
|
1700 copybuffer.copyToChannel(sub_frame, c);
|
nicholas@2460
|
1701 } else {
|
nicholas@2460
|
1702 var dst = copybuffer.getChannelData(c);
|
nicholas@2498
|
1703 for (var n = 0; n < newLength; n++)
|
nicholas@2505
|
1704 dst[n] = buffer[n + start_sample];
|
nicholas@2460
|
1705 }
|
nicholas@2460
|
1706 }
|
nicholas@2460
|
1707 return copybuffer;
|
nicholas@2708
|
1708 };
|
nicholas@2498
|
1709 };
|
nicholas@2498
|
1710
|
nicholas@2498
|
1711 this.loadPageData = function (page) {
|
nicholas@2224
|
1712 // Load the URL from pages
|
nicholas@2708
|
1713 function loadAudioElementData(element) {
|
nicholas@2224
|
1714 var URL = page.hostURL + element.url;
|
nicholas@2708
|
1715 var buffer = this.buffers.find(function (buffObj) {
|
nicholas@2708
|
1716 return buffObj.hasUrl(URL);
|
nicholas@2708
|
1717 });
|
nicholas@2708
|
1718 if (buffer === undefined) {
|
nicholas@2224
|
1719 buffer = new this.bufferObj();
|
nicholas@2617
|
1720 var urls = [{
|
nicholas@2617
|
1721 url: URL,
|
nicholas@2617
|
1722 sampleRate: element.sampleRate
|
nicholas@2617
|
1723 }];
|
nicholas@2615
|
1724 element.alternatives.forEach(function (e) {
|
nicholas@2617
|
1725 urls.push({
|
nicholas@2617
|
1726 url: e.url,
|
nicholas@2617
|
1727 sampleRate: e.sampleRate
|
nicholas@2617
|
1728 });
|
nicholas@2615
|
1729 });
|
nicholas@2617
|
1730 buffer.setUrls(urls);
|
nicholas@2617
|
1731 buffer.getMedia();
|
nicholas@2224
|
1732 this.buffers.push(buffer);
|
nicholas@2224
|
1733 }
|
nicholas@2224
|
1734 }
|
nicholas@2708
|
1735 page.audioElements.forEach(loadAudioElementData, this);
|
nicholas@2224
|
1736 };
|
nicholas@2498
|
1737
|
nicholas@2708
|
1738 function playNormal(id) {
|
nicholas@2708
|
1739 var playTime = audioContext.currentTime + 0.1;
|
nicholas@2708
|
1740 var stopTime = playTime + specification.crossFade;
|
nicholas@2708
|
1741 this.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
1742 if (ao.id === id) {
|
nicholas@2942
|
1743 ao.setupPlayback();
|
nicholas@2942
|
1744 ao.bufferStart(playTime);
|
nicholas@2942
|
1745 ao.listenStart(playTime);
|
nicholas@2708
|
1746 } else {
|
nicholas@2942
|
1747 ao.listenStop(playTime);
|
nicholas@2942
|
1748 ao.bufferStop(stopTime);
|
nicholas@2708
|
1749 }
|
nicholas@2708
|
1750 });
|
nicholas@2708
|
1751 }
|
nicholas@2708
|
1752
|
nicholas@2942
|
1753 function playSync(id) {
|
nicholas@2708
|
1754 var playTime = audioContext.currentTime + 0.1;
|
nicholas@2708
|
1755 var stopTime = playTime + specification.crossFade;
|
nicholas@2708
|
1756 this.audioObjects.forEach(function (ao) {
|
nicholas@2942
|
1757 ao.setupPlayback();
|
nicholas@2942
|
1758 ao.bufferStart(playTime);
|
nicholas@2708
|
1759 if (ao.id === id) {
|
nicholas@2942
|
1760 ao.listenStart(playTime);
|
nicholas@2708
|
1761 } else {
|
nicholas@2942
|
1762 ao.listenStop(playTime);
|
nicholas@2708
|
1763 }
|
nicholas@2708
|
1764 });
|
nicholas@2708
|
1765 }
|
nicholas@2708
|
1766
|
nicholas@2498
|
1767 this.play = function (id) {
|
nicholas@2498
|
1768 // Start the timer and set the audioEngine state to playing (1)
|
nicholas@2708
|
1769 if (typeof id !== "number" || id < 0 || id > this.audioObjects.length) {
|
nicholas@2708
|
1770 throw ('FATAL - Passed id was undefined - AudioEngineContext.play(id)');
|
nicholas@2498
|
1771 }
|
nicholas@2823
|
1772 var maxPlays = this.audioObjects[id].specification.maxNumberPlays || this.audioObjects[id].specification.parent.maxNumberPlays || specification.maxNumberPlays;
|
nicholas@2823
|
1773 if (maxPlays !== undefined && this.audioObjects[id].numberOfPlays >= maxPlays) {
|
nicholas@2823
|
1774 interfaceContext.lightbox.post("Error", "Cannot play this fragment more than " + maxPlays + " times");
|
nicholas@2823
|
1775 return;
|
nicholas@2823
|
1776 }
|
nicholas@2708
|
1777 if (this.status === 1) {
|
nicholas@2498
|
1778 this.timer.startTest();
|
nicholas@2708
|
1779 interfaceContext.playhead.setTimePerPixel(this.audioObjects[id]);
|
nicholas@2942
|
1780 if (this.synchPlayback) {
|
nicholas@2351
|
1781 // Traditional looped playback
|
nicholas@2942
|
1782 playSync.call(this, id);
|
nicholas@2708
|
1783 } else {
|
nicholas@2708
|
1784 if (this.bufferReady(id) === false) {
|
nicholas@2708
|
1785 console.log("Cannot play. Buffer not ready");
|
nicholas@2708
|
1786 return;
|
nicholas@2498
|
1787 }
|
nicholas@2708
|
1788 playNormal.call(this, id);
|
nicholas@2498
|
1789 }
|
nicholas@2498
|
1790 interfaceContext.playhead.start();
|
nicholas@2498
|
1791 }
|
nicholas@2498
|
1792 };
|
nicholas@2224
|
1793
|
nicholas@2498
|
1794 this.stop = function () {
|
nicholas@2498
|
1795 // Send stop and reset command to all playback buffers
|
nicholas@2498
|
1796 if (this.status == 1) {
|
nicholas@2498
|
1797 var setTime = audioContext.currentTime + 0.1;
|
nicholas@2708
|
1798 this.audioObjects.forEach(function (a) {
|
nicholas@2942
|
1799 a.listenStop(setTime);
|
nicholas@2942
|
1800 a.bufferStop(setTime);
|
nicholas@2708
|
1801 });
|
nicholas@2498
|
1802 interfaceContext.playhead.stop();
|
nicholas@2498
|
1803 }
|
nicholas@2498
|
1804 };
|
nicholas@2498
|
1805
|
nicholas@2498
|
1806 this.newTrack = function (element) {
|
nicholas@2498
|
1807 // Pull data from given URL into new audio buffer
|
nicholas@2498
|
1808 // URLs must either be from the same source OR be setup to 'Access-Control-Allow-Origin'
|
nicholas@2498
|
1809
|
nicholas@2498
|
1810 // Create the audioObject with ID of the new track length;
|
nicholas@2708
|
1811 var audioObjectId = this.audioObjects.length;
|
nicholas@2498
|
1812 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
|
nicholas@2498
|
1813
|
nicholas@2498
|
1814 // Check if audioObject buffer is currently stored by full URL
|
nicholas@2498
|
1815 var URL = testState.currentStateMap.hostURL + element.url;
|
nicholas@2708
|
1816 var buffer = this.buffers.find(function (buffObj) {
|
nicholas@2708
|
1817 return buffObj.hasUrl(URL);
|
nicholas@2708
|
1818 });
|
nicholas@2708
|
1819 if (buffer === undefined) {
|
nicholas@2498
|
1820 console.log("[WARN]: Buffer was not loaded in pre-test! " + URL);
|
nicholas@2498
|
1821 buffer = new this.bufferObj();
|
nicholas@2224
|
1822 this.buffers.push(buffer);
|
nicholas@2498
|
1823 buffer.getMedia(URL);
|
nicholas@2498
|
1824 }
|
nicholas@2498
|
1825 this.audioObjects[audioObjectId].specification = element;
|
nicholas@2498
|
1826 this.audioObjects[audioObjectId].url = URL;
|
nicholas@2498
|
1827 // Obtain store node
|
nicholas@2498
|
1828 var aeNodes = this.pageStore.XMLDOM.getElementsByTagName('audioelement');
|
nicholas@2498
|
1829 for (var i = 0; i < aeNodes.length; i++) {
|
nicholas@2498
|
1830 if (aeNodes[i].getAttribute("ref") == element.id) {
|
nicholas@2498
|
1831 this.audioObjects[audioObjectId].storeDOM = aeNodes[i];
|
nicholas@2498
|
1832 break;
|
nicholas@2498
|
1833 }
|
nicholas@2498
|
1834 }
|
nicholas@2224
|
1835 buffer.registerAudioObject(this.audioObjects[audioObjectId]);
|
nicholas@2498
|
1836 return this.audioObjects[audioObjectId];
|
nicholas@2498
|
1837 };
|
nicholas@2498
|
1838
|
nicholas@2498
|
1839 this.newTestPage = function (audioHolderObject, store) {
|
nicholas@2498
|
1840 this.pageStore = store;
|
nicholas@2351
|
1841 this.pageSpecification = audioHolderObject;
|
nicholas@2498
|
1842 this.status = 0;
|
nicholas@2498
|
1843 this.audioObjectsReady = false;
|
nicholas@2498
|
1844 this.metric.reset();
|
nicholas@2708
|
1845 this.buffers.forEach(function (buffer) {
|
nicholas@2708
|
1846 buffer.users = [];
|
nicholas@2708
|
1847 });
|
nicholas@2498
|
1848 this.audioObjects = [];
|
nicholas@2224
|
1849 this.timer = new timer();
|
nicholas@2224
|
1850 this.loopPlayback = audioHolderObject.loop;
|
nicholas@2351
|
1851 this.synchPlayback = audioHolderObject.synchronous;
|
nicholas@2955
|
1852 interfaceContext.keyboardInterface.resetKeyBindings();
|
nicholas@2498
|
1853 };
|
nicholas@2498
|
1854
|
nicholas@2498
|
1855 this.checkAllPlayed = function () {
|
nicholas@2708
|
1856 var arr = [];
|
nicholas@2498
|
1857 for (var id = 0; id < this.audioObjects.length; id++) {
|
nicholas@2708
|
1858 if (this.audioObjects[id].metric.wasListenedTo === false) {
|
nicholas@2498
|
1859 arr.push(this.audioObjects[id].id);
|
nicholas@2498
|
1860 }
|
nicholas@2498
|
1861 }
|
nicholas@2498
|
1862 return arr;
|
nicholas@2498
|
1863 };
|
nicholas@2498
|
1864
|
nicholas@2498
|
1865 this.checkAllReady = function () {
|
nicholas@2498
|
1866 var ready = true;
|
nicholas@2498
|
1867 for (var i = 0; i < this.audioObjects.length; i++) {
|
nicholas@2708
|
1868 if (this.audioObjects[i].state === 0) {
|
nicholas@2498
|
1869 // Track not ready
|
nicholas@2498
|
1870 console.log('WAIT -- audioObject ' + i + ' not ready yet!');
|
nicholas@2498
|
1871 ready = false;
|
nicholas@2708
|
1872 }
|
nicholas@2498
|
1873 }
|
nicholas@2498
|
1874 return ready;
|
nicholas@2498
|
1875 };
|
nicholas@2498
|
1876
|
nicholas@2498
|
1877 this.setSynchronousLoop = function () {
|
nicholas@2570
|
1878 // Pads the signals so they are all exactly the same duration
|
nicholas@2570
|
1879 // Get the duration of the longest signal.
|
nicholas@2570
|
1880 var duration = 0;
|
nicholas@2498
|
1881 var maxId;
|
nicholas@2498
|
1882 for (var i = 0; i < this.audioObjects.length; i++) {
|
nicholas@2570
|
1883 if (duration < this.audioObjects[i].buffer.buffer.duration) {
|
nicholas@2570
|
1884 duration = this.audioObjects[i].buffer.buffer.duration;
|
nicholas@2498
|
1885 maxId = i;
|
nicholas@2498
|
1886 }
|
nicholas@2498
|
1887 }
|
nicholas@2498
|
1888 // Extract the audio and zero-pad
|
nicholas@2708
|
1889 this.audioObjects.forEach(function (ao) {
|
nicholas@2570
|
1890 if (ao.buffer.buffer.duration !== duration) {
|
nicholas@2570
|
1891 ao.buffer.buffer = ao.buffer.copyBuffer(0, duration - ao.buffer.buffer.duration);
|
nicholas@2500
|
1892 }
|
nicholas@2708
|
1893 });
|
nicholas@2498
|
1894 };
|
nicholas@2498
|
1895
|
nicholas@2498
|
1896 this.bufferReady = function (id) {
|
nicholas@2498
|
1897 if (this.checkAllReady()) {
|
nicholas@2498
|
1898 if (this.synchPlayback) {
|
nicholas@2498
|
1899 this.setSynchronousLoop();
|
nicholas@2498
|
1900 }
|
nicholas@2460
|
1901 this.status = 1;
|
nicholas@2460
|
1902 return true;
|
nicholas@2460
|
1903 }
|
nicholas@2460
|
1904 return false;
|
nicholas@2224
|
1905 };
|
nicholas@2498
|
1906
|
nicholas@2224
|
1907 }
|
nicholas@2224
|
1908
|
nicholas@2224
|
1909 function audioObject(id) {
|
nicholas@2498
|
1910 // The main buffer object with common control nodes to the AudioEngine
|
nicholas@2498
|
1911
|
nicholas@2823
|
1912 var playCounter = 0;
|
nicholas@2823
|
1913
|
nicholas@2708
|
1914 this.specification = undefined;
|
nicholas@2498
|
1915 this.id = id;
|
nicholas@2498
|
1916 this.state = 0; // 0 - no data, 1 - ready
|
nicholas@2498
|
1917 this.url = null; // Hold the URL given for the output back to the results.
|
nicholas@2498
|
1918 this.metric = new metricTracker(this);
|
nicholas@2498
|
1919 this.storeDOM = null;
|
nicholas@2949
|
1920 this.playing = false;
|
nicholas@2498
|
1921
|
nicholas@2498
|
1922 // Bindings for GUI
|
nicholas@2498
|
1923 this.interfaceDOM = null;
|
nicholas@2498
|
1924 this.commentDOM = null;
|
nicholas@2498
|
1925
|
nicholas@2498
|
1926 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
|
nicholas@2498
|
1927 this.bufferNode = undefined;
|
nicholas@2498
|
1928 this.outputGain = audioContext.createGain();
|
nicholas@2942
|
1929 this.outputGain.gain.value = 0.0;
|
nicholas@2498
|
1930
|
nicholas@2498
|
1931 this.onplayGain = 1.0;
|
nicholas@2498
|
1932
|
nicholas@2498
|
1933 // Connect buffer to the audio graph
|
nicholas@2498
|
1934 this.outputGain.connect(audioEngineContext.outputGain);
|
nicholas@2508
|
1935 audioEngineContext.nullBufferSource.connect(this.outputGain);
|
nicholas@2498
|
1936
|
nicholas@2498
|
1937 // the audiobuffer is not designed for multi-start playback
|
nicholas@2498
|
1938 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
|
nicholas@2708
|
1939 this.buffer = undefined;
|
nicholas@2498
|
1940
|
nicholas@2498
|
1941 this.bufferLoaded = function (callee) {
|
nicholas@2498
|
1942 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
|
nicholas@2498
|
1943 // audioObject and trigger the interfaceDOM.enable() function for user feedback
|
nicholas@2224
|
1944 if (callee.status == -1) {
|
nicholas@2224
|
1945 // ERROR
|
nicholas@2224
|
1946 this.state = -1;
|
nicholas@2708
|
1947 if (this.interfaceDOM !== null) {
|
nicholas@2498
|
1948 this.interfaceDOM.error();
|
nicholas@2498
|
1949 }
|
nicholas@2224
|
1950 this.buffer = callee;
|
nicholas@2224
|
1951 return;
|
nicholas@2224
|
1952 }
|
nicholas@2224
|
1953 this.buffer = callee;
|
nicholas@2224
|
1954 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
|
nicholas@2224
|
1955 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
|
nicholas@2460
|
1956 var startTime = this.specification.startTime;
|
nicholas@2460
|
1957 var stopTime = this.specification.stopTime;
|
nicholas@2460
|
1958 var copybuffer = new callee.constructor();
|
nicholas@2500
|
1959
|
nicholas@2500
|
1960 copybuffer.buffer = callee.cropBuffer(startTime || 0, stopTime || callee.buffer.duration);
|
nicholas@2708
|
1961 if (preSilenceTime !== 0 || postSilenceTime !== 0) {
|
nicholas@2500
|
1962 copybuffer.buffer = copybuffer.copyBuffer(preSilenceTime, postSilenceTime);
|
nicholas@2460
|
1963 }
|
nicholas@2500
|
1964
|
nicholas@2660
|
1965 copybuffer.buffer.lufs = callee.buffer.lufs;
|
nicholas@2500
|
1966 this.buffer = copybuffer;
|
nicholas@2498
|
1967
|
nicholas@2661
|
1968 var targetLUFS = this.specification.loudness || this.specification.parent.loudness || specification.loudness;
|
nicholas@2498
|
1969 if (typeof targetLUFS === "number" && isFinite(targetLUFS)) {
|
nicholas@2498
|
1970 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
|
nicholas@2498
|
1971 } else {
|
nicholas@2498
|
1972 this.buffer.buffer.playbackGain = 1.0;
|
nicholas@2498
|
1973 }
|
nicholas@2708
|
1974 if (this.interfaceDOM !== null) {
|
nicholas@2498
|
1975 this.interfaceDOM.enable();
|
nicholas@2498
|
1976 }
|
nicholas@2498
|
1977 this.onplayGain = decibelToLinear(this.specification.gain) * (this.buffer.buffer.playbackGain || 1.0);
|
nicholas@2498
|
1978 this.storeDOM.setAttribute('playGain', linearToDecibel(this.onplayGain));
|
nicholas@2460
|
1979 this.state = 1;
|
nicholas@2460
|
1980 audioEngineContext.bufferReady(id);
|
nicholas@2498
|
1981 };
|
nicholas@2498
|
1982
|
nicholas@2498
|
1983 this.bindInterface = function (interfaceObject) {
|
nicholas@2498
|
1984 this.interfaceDOM = interfaceObject;
|
nicholas@2498
|
1985 this.metric.initialise(interfaceObject.getValue());
|
nicholas@2498
|
1986 if (this.state == 1) {
|
nicholas@2498
|
1987 this.interfaceDOM.enable();
|
nicholas@2498
|
1988 } else if (this.state == -1) {
|
nicholas@2224
|
1989 // ERROR
|
nicholas@2224
|
1990 this.interfaceDOM.error();
|
nicholas@2224
|
1991 return;
|
nicholas@2224
|
1992 }
|
nicholas@2955
|
1993 var presentedId = interfaceObject.getPresentedId();
|
nicholas@2955
|
1994 this.storeDOM.setAttribute('presentedId', presentedId);
|
nicholas@2955
|
1995
|
nicholas@2955
|
1996 // Key-bindings
|
nicholas@2955
|
1997 if (presentedId.length == 1) {
|
nicholas@2955
|
1998 interfaceContext.keyboardInterface.registerKeyBinding(presentedId, this);
|
nicholas@2955
|
1999 }
|
nicholas@2498
|
2000 };
|
nicholas@2498
|
2001
|
nicholas@2942
|
2002 this.listenStart = function (setTime) {
|
nicholas@2949
|
2003 if (this.playing === false) {
|
nicholas@2942
|
2004 playCounter++;
|
nicholas@2942
|
2005 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain, setTime);
|
nicholas@2942
|
2006 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2942
|
2007 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
|
nicholas@2942
|
2008 this.interfaceDOM.startPlayback();
|
nicholas@2949
|
2009 this.playing = true;
|
nicholas@2942
|
2010 }
|
nicholas@2498
|
2011 };
|
nicholas@2498
|
2012
|
nicholas@2942
|
2013 this.listenStop = function (setTime) {
|
nicholas@2949
|
2014 if (this.playing === true) {
|
nicholas@2498
|
2015 this.outputGain.gain.linearRampToValueAtTime(0.0, setTime);
|
nicholas@2942
|
2016 this.metric.stopListening(audioEngineContext.timer.getTestTime(), this.getCurrentPosition());
|
nicholas@2498
|
2017 }
|
nicholas@2224
|
2018 this.interfaceDOM.stopPlayback();
|
nicholas@2949
|
2019 this.playing = false;
|
nicholas@2498
|
2020 };
|
nicholas@2498
|
2021
|
nicholas@2942
|
2022 this.setupPlayback = function () {
|
nicholas@2708
|
2023 if (this.bufferNode === undefined && this.buffer.buffer !== undefined) {
|
nicholas@2498
|
2024 this.bufferNode = audioContext.createBufferSource();
|
nicholas@2498
|
2025 this.bufferNode.owner = this;
|
nicholas@2498
|
2026 this.bufferNode.connect(this.outputGain);
|
nicholas@2498
|
2027 this.bufferNode.buffer = this.buffer.buffer;
|
nicholas@2942
|
2028 if (audioEngineContext.loopPlayback) {
|
nicholas@2942
|
2029 this.bufferNode.loopStart = this.specification.startTime || 0;
|
nicholas@2942
|
2030 this.bufferNode.loopEnd = this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration;
|
nicholas@2942
|
2031 this.bufferNode.loop = true;
|
nicholas@2942
|
2032 }
|
nicholas@2498
|
2033 this.bufferNode.onended = function (event) {
|
nicholas@2498
|
2034 // Safari does not like using 'this' to reference the calling object!
|
nicholas@2498
|
2035 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
|
nicholas@2708
|
2036 if (event.currentTarget !== null) {
|
nicholas@2942
|
2037 event.currentTarget.owner.bufferStop(audioContext.currentTime + 0.1);
|
nicholas@2950
|
2038 event.currentTarget.owner.listenStop(audioContext.currentTime + 0.1);
|
nicholas@2224
|
2039 }
|
nicholas@2498
|
2040 };
|
nicholas@2942
|
2041 this.bufferNode.state = 0;
|
nicholas@2942
|
2042 }
|
nicholas@2942
|
2043 };
|
nicholas@2942
|
2044
|
nicholas@2942
|
2045 this.bufferStart = function (startTime) {
|
nicholas@2942
|
2046 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
n@2979
|
2047 if (this.bufferNode && this.bufferNode.state === 0) {
|
nicholas@2942
|
2048 this.bufferNode.state = 1;
|
n@2979
|
2049 if (this.bufferNode.loop === true) {
|
nicholas@2499
|
2050 this.bufferNode.start(startTime);
|
nicholas@2499
|
2051 } else {
|
nicholas@2499
|
2052 this.bufferNode.start(startTime, this.specification.startTime || 0, this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration);
|
nicholas@2499
|
2053 }
|
nicholas@2498
|
2054 }
|
nicholas@2498
|
2055 };
|
nicholas@2498
|
2056
|
nicholas@2942
|
2057 this.bufferStop = function (stopTime) {
|
nicholas@2224
|
2058 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
nicholas@2942
|
2059 if (this.bufferNode !== undefined && this.bufferNode.state > 0) {
|
nicholas@2498
|
2060 this.bufferNode.stop(stopTime);
|
nicholas@2498
|
2061 this.bufferNode = undefined;
|
nicholas@2498
|
2062 }
|
nicholas@2529
|
2063 this.outputGain.gain.linearRampToValueAtTime(0.0, stopTime);
|
nicholas@2224
|
2064 this.interfaceDOM.stopPlayback();
|
nicholas@2498
|
2065 };
|
nicholas@2498
|
2066
|
nicholas@2498
|
2067 this.getCurrentPosition = function () {
|
nicholas@2498
|
2068 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2708
|
2069 if (this.bufferNode !== undefined) {
|
nicholas@2498
|
2070 var position = (time - this.bufferNode.playbackStartTime) % this.buffer.buffer.duration;
|
nicholas@2498
|
2071 if (isNaN(position)) {
|
nicholas@2498
|
2072 return 0;
|
nicholas@2498
|
2073 }
|
nicholas@2224
|
2074 return position;
|
nicholas@2498
|
2075 } else {
|
nicholas@2498
|
2076 return 0;
|
nicholas@2498
|
2077 }
|
nicholas@2498
|
2078 };
|
nicholas@2498
|
2079
|
nicholas@2498
|
2080 this.exportXMLDOM = function () {
|
nicholas@2498
|
2081 var file = storage.document.createElement('file');
|
nicholas@2498
|
2082 file.setAttribute('sampleRate', this.buffer.buffer.sampleRate);
|
nicholas@2498
|
2083 file.setAttribute('channels', this.buffer.buffer.numberOfChannels);
|
nicholas@2498
|
2084 file.setAttribute('sampleCount', this.buffer.buffer.length);
|
nicholas@2498
|
2085 file.setAttribute('duration', this.buffer.buffer.duration);
|
nicholas@2498
|
2086 this.storeDOM.appendChild(file);
|
nicholas@2498
|
2087 if (this.specification.type != 'outside-reference') {
|
nicholas@2498
|
2088 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
nicholas@2708
|
2089 if (interfaceXML !== null) {
|
nicholas@2708
|
2090 if (interfaceXML.length === undefined) {
|
nicholas@2498
|
2091 this.storeDOM.appendChild(interfaceXML);
|
nicholas@2498
|
2092 } else {
|
nicholas@2498
|
2093 for (var i = 0; i < interfaceXML.length; i++) {
|
nicholas@2498
|
2094 this.storeDOM.appendChild(interfaceXML[i]);
|
nicholas@2498
|
2095 }
|
nicholas@2498
|
2096 }
|
nicholas@2498
|
2097 }
|
nicholas@2708
|
2098 if (this.commentDOM !== null) {
|
nicholas@2498
|
2099 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
nicholas@2498
|
2100 }
|
nicholas@2498
|
2101 }
|
nicholas@2708
|
2102 this.metric.exportXMLDOM(this.storeDOM.getElementsByTagName('metric')[0]);
|
nicholas@2498
|
2103 };
|
nicholas@2823
|
2104
|
nicholas@2823
|
2105 Object.defineProperties(this, {
|
nicholas@2823
|
2106 "numberOfPlays": {
|
nicholas@2823
|
2107 'get': function () {
|
nicholas@2823
|
2108 return playCounter;
|
nicholas@2823
|
2109 },
|
nicholas@2823
|
2110 'set': function () {
|
nicholas@2823
|
2111 return playCounter;
|
nicholas@2823
|
2112 }
|
nicholas@2823
|
2113 }
|
nicholas@2823
|
2114 });
|
nicholas@2224
|
2115 }
|
nicholas@2224
|
2116
|
nicholas@2498
|
2117 function timer() {
|
nicholas@2498
|
2118 /* Timer object used in audioEngine to keep track of session timings
|
nicholas@2498
|
2119 * Uses the timer of the web audio API, so sample resolution
|
nicholas@2498
|
2120 */
|
nicholas@2498
|
2121 this.testStarted = false;
|
nicholas@2498
|
2122 this.testStartTime = 0;
|
nicholas@2498
|
2123 this.testDuration = 0;
|
nicholas@2498
|
2124 this.minimumTestTime = 0; // No minimum test time
|
nicholas@2498
|
2125 this.startTest = function () {
|
nicholas@2708
|
2126 if (this.testStarted === false) {
|
nicholas@2498
|
2127 this.testStartTime = audioContext.currentTime;
|
nicholas@2498
|
2128 this.testStarted = true;
|
nicholas@2498
|
2129 this.updateTestTime();
|
nicholas@2498
|
2130 audioEngineContext.metric.initialiseTest();
|
nicholas@2498
|
2131 }
|
nicholas@2498
|
2132 };
|
nicholas@2498
|
2133 this.stopTest = function () {
|
nicholas@2498
|
2134 if (this.testStarted) {
|
nicholas@2498
|
2135 this.testDuration = this.getTestTime();
|
nicholas@2498
|
2136 this.testStarted = false;
|
nicholas@2498
|
2137 } else {
|
nicholas@2498
|
2138 console.log('ERR: Test tried to end before beginning');
|
nicholas@2498
|
2139 }
|
nicholas@2498
|
2140 };
|
nicholas@2498
|
2141 this.updateTestTime = function () {
|
nicholas@2498
|
2142 if (this.testStarted) {
|
nicholas@2498
|
2143 this.testDuration = audioContext.currentTime - this.testStartTime;
|
nicholas@2498
|
2144 }
|
nicholas@2498
|
2145 };
|
nicholas@2498
|
2146 this.getTestTime = function () {
|
nicholas@2498
|
2147 this.updateTestTime();
|
nicholas@2498
|
2148 return this.testDuration;
|
nicholas@2498
|
2149 };
|
nicholas@2224
|
2150 }
|
nicholas@2224
|
2151
|
nicholas@2498
|
2152 function sessionMetrics(engine, specification) {
|
nicholas@2498
|
2153 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
nicholas@2498
|
2154 */
|
nicholas@2498
|
2155 this.engine = engine;
|
nicholas@2498
|
2156 this.lastClicked = -1;
|
nicholas@2498
|
2157 this.data = -1;
|
nicholas@2498
|
2158 this.reset = function () {
|
nicholas@2498
|
2159 this.lastClicked = -1;
|
nicholas@2498
|
2160 this.data = -1;
|
nicholas@2498
|
2161 };
|
nicholas@2498
|
2162
|
nicholas@2498
|
2163 this.enableElementInitialPosition = false;
|
nicholas@2498
|
2164 this.enableElementListenTracker = false;
|
nicholas@2498
|
2165 this.enableElementTimer = false;
|
nicholas@2498
|
2166 this.enableElementTracker = false;
|
nicholas@2498
|
2167 this.enableFlagListenedTo = false;
|
nicholas@2498
|
2168 this.enableFlagMoved = false;
|
nicholas@2498
|
2169 this.enableTestTimer = false;
|
nicholas@2498
|
2170 // Obtain the metrics enabled
|
nicholas@2498
|
2171 for (var i = 0; i < specification.metrics.enabled.length; i++) {
|
nicholas@2498
|
2172 var node = specification.metrics.enabled[i];
|
nicholas@2498
|
2173 switch (node) {
|
nicholas@2498
|
2174 case 'testTimer':
|
nicholas@2498
|
2175 this.enableTestTimer = true;
|
nicholas@2498
|
2176 break;
|
nicholas@2498
|
2177 case 'elementTimer':
|
nicholas@2498
|
2178 this.enableElementTimer = true;
|
nicholas@2498
|
2179 break;
|
nicholas@2498
|
2180 case 'elementTracker':
|
nicholas@2498
|
2181 this.enableElementTracker = true;
|
nicholas@2498
|
2182 break;
|
nicholas@2498
|
2183 case 'elementListenTracker':
|
nicholas@2498
|
2184 this.enableElementListenTracker = true;
|
nicholas@2498
|
2185 break;
|
nicholas@2498
|
2186 case 'elementInitialPosition':
|
nicholas@2498
|
2187 this.enableElementInitialPosition = true;
|
nicholas@2498
|
2188 break;
|
nicholas@2498
|
2189 case 'elementFlagListenedTo':
|
nicholas@2498
|
2190 this.enableFlagListenedTo = true;
|
nicholas@2498
|
2191 break;
|
nicholas@2498
|
2192 case 'elementFlagMoved':
|
nicholas@2498
|
2193 this.enableFlagMoved = true;
|
nicholas@2498
|
2194 break;
|
nicholas@2498
|
2195 case 'elementFlagComments':
|
nicholas@2498
|
2196 this.enableFlagComments = true;
|
nicholas@2498
|
2197 break;
|
nicholas@2498
|
2198 }
|
nicholas@2498
|
2199 }
|
nicholas@2498
|
2200 this.initialiseTest = function () {};
|
nicholas@2224
|
2201 }
|
nicholas@2224
|
2202
|
nicholas@2498
|
2203 function metricTracker(caller) {
|
nicholas@2498
|
2204 /* Custom object to track and collect metric data
|
nicholas@2498
|
2205 * Used only inside the audioObjects object.
|
nicholas@2498
|
2206 */
|
nicholas@2498
|
2207
|
nicholas@2498
|
2208 this.listenedTimer = 0;
|
nicholas@2498
|
2209 this.listenStart = 0;
|
nicholas@2498
|
2210 this.listenHold = false;
|
nicholas@2498
|
2211 this.initialPosition = -1;
|
nicholas@2498
|
2212 this.movementTracker = [];
|
nicholas@2498
|
2213 this.listenTracker = [];
|
nicholas@2498
|
2214 this.wasListenedTo = false;
|
nicholas@2498
|
2215 this.wasMoved = false;
|
nicholas@2498
|
2216 this.hasComments = false;
|
nicholas@2498
|
2217 this.parent = caller;
|
nicholas@2498
|
2218
|
nicholas@2498
|
2219 this.initialise = function (position) {
|
nicholas@2498
|
2220 if (this.initialPosition == -1) {
|
nicholas@2498
|
2221 this.initialPosition = position;
|
nicholas@2498
|
2222 this.moved(0, position);
|
nicholas@2498
|
2223 }
|
nicholas@2498
|
2224 };
|
nicholas@2498
|
2225
|
nicholas@2498
|
2226 this.moved = function (time, position) {
|
nicholas@3064
|
2227 var last;
|
nicholas@2498
|
2228 if (time > 0) {
|
nicholas@2498
|
2229 this.wasMoved = true;
|
nicholas@2498
|
2230 }
|
nicholas@3064
|
2231 // Get the last entry
|
nicholas@3064
|
2232 if (this.movementTracker.length > 0) {
|
nicholas@3064
|
2233 last = this.movementTracker[this.movementTracker.length - 1];
|
nicholas@3064
|
2234 } else {
|
nicholas@3064
|
2235 last = -1;
|
nicholas@3064
|
2236 }
|
nicholas@3064
|
2237 if (position != last[1]) {
|
nicholas@3064
|
2238 this.movementTracker[this.movementTracker.length] = [time, position];
|
nicholas@3064
|
2239 }
|
nicholas@2498
|
2240 };
|
nicholas@2498
|
2241
|
nicholas@2498
|
2242 this.startListening = function (time) {
|
nicholas@2708
|
2243 if (this.listenHold === false) {
|
nicholas@2498
|
2244 this.wasListenedTo = true;
|
nicholas@2498
|
2245 this.listenStart = time;
|
nicholas@2498
|
2246 this.listenHold = true;
|
nicholas@2498
|
2247
|
nicholas@2498
|
2248 var evnt = document.createElement('event');
|
nicholas@2498
|
2249 var testTime = document.createElement('testTime');
|
nicholas@2498
|
2250 testTime.setAttribute('start', time);
|
nicholas@2498
|
2251 var bufferTime = document.createElement('bufferTime');
|
nicholas@2498
|
2252 bufferTime.setAttribute('start', this.parent.getCurrentPosition());
|
nicholas@2498
|
2253 evnt.appendChild(testTime);
|
nicholas@2498
|
2254 evnt.appendChild(bufferTime);
|
nicholas@2498
|
2255 this.listenTracker.push(evnt);
|
nicholas@2498
|
2256
|
nicholas@2498
|
2257 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2498
|
2258 }
|
nicholas@2498
|
2259 };
|
nicholas@2498
|
2260
|
nicholas@2498
|
2261 this.stopListening = function (time, bufferStopTime) {
|
nicholas@2708
|
2262 if (this.listenHold === true) {
|
nicholas@2498
|
2263 var diff = time - this.listenStart;
|
nicholas@2498
|
2264 this.listenedTimer += (diff);
|
nicholas@2498
|
2265 this.listenStart = 0;
|
nicholas@2498
|
2266 this.listenHold = false;
|
nicholas@2498
|
2267
|
nicholas@2498
|
2268 var evnt = this.listenTracker[this.listenTracker.length - 1];
|
nicholas@2498
|
2269 var testTime = evnt.getElementsByTagName('testTime')[0];
|
nicholas@2498
|
2270 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
nicholas@2498
|
2271 testTime.setAttribute('stop', time);
|
nicholas@2708
|
2272 if (bufferStopTime === undefined) {
|
nicholas@2498
|
2273 bufferTime.setAttribute('stop', this.parent.getCurrentPosition());
|
nicholas@2498
|
2274 } else {
|
nicholas@2498
|
2275 bufferTime.setAttribute('stop', bufferStopTime);
|
nicholas@2498
|
2276 }
|
nicholas@2498
|
2277 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2498
|
2278 }
|
nicholas@2498
|
2279 };
|
nicholas@2498
|
2280
|
nicholas@2708
|
2281 function exportElementTimer(parentElement) {
|
nicholas@2708
|
2282 var mElementTimer = storage.document.createElement('metricresult');
|
nicholas@2708
|
2283 mElementTimer.setAttribute('name', 'enableElementTimer');
|
nicholas@2708
|
2284 mElementTimer.textContent = this.listenedTimer;
|
nicholas@2708
|
2285 parentElement.appendChild(mElementTimer);
|
nicholas@2708
|
2286 return mElementTimer;
|
nicholas@2708
|
2287 }
|
nicholas@2708
|
2288
|
nicholas@2708
|
2289 function exportElementTrack(parentElement) {
|
nicholas@2708
|
2290 var elementTrackerFull = storage.document.createElement('metricresult');
|
nicholas@2708
|
2291 elementTrackerFull.setAttribute('name', 'elementTrackerFull');
|
nicholas@2708
|
2292 for (var k = 0; k < this.movementTracker.length; k++) {
|
nicholas@2708
|
2293 var timePos = storage.document.createElement('movement');
|
nicholas@2708
|
2294 timePos.setAttribute("time", this.movementTracker[k][0]);
|
nicholas@2708
|
2295 timePos.setAttribute("value", this.movementTracker[k][1]);
|
nicholas@2708
|
2296 elementTrackerFull.appendChild(timePos);
|
nicholas@2708
|
2297 }
|
nicholas@2708
|
2298 parentElement.appendChild(elementTrackerFull);
|
nicholas@2708
|
2299 return elementTrackerFull;
|
nicholas@2708
|
2300 }
|
nicholas@2708
|
2301
|
nicholas@2708
|
2302 function exportElementListenTracker(parentElement) {
|
nicholas@2708
|
2303 var elementListenTracker = storage.document.createElement('metricresult');
|
nicholas@2708
|
2304 elementListenTracker.setAttribute('name', 'elementListenTracker');
|
nicholas@2708
|
2305 for (var k = 0; k < this.listenTracker.length; k++) {
|
nicholas@2708
|
2306 elementListenTracker.appendChild(this.listenTracker[k]);
|
nicholas@2708
|
2307 }
|
nicholas@2708
|
2308 parentElement.appendChild(elementListenTracker);
|
nicholas@2708
|
2309 return elementListenTracker;
|
nicholas@2708
|
2310 }
|
nicholas@2708
|
2311
|
nicholas@2708
|
2312 function exportElementInitialPosition(parentElement) {
|
nicholas@2708
|
2313 var elementInitial = storage.document.createElement('metricresult');
|
nicholas@2708
|
2314 elementInitial.setAttribute('name', 'elementInitialPosition');
|
nicholas@2708
|
2315 elementInitial.textContent = this.initialPosition;
|
nicholas@2708
|
2316 parentElement.appendChild(elementInitial);
|
nicholas@2708
|
2317 return elementInitial;
|
nicholas@2708
|
2318 }
|
nicholas@2708
|
2319
|
nicholas@2708
|
2320 function exportFlagListenedTo(parentElement) {
|
nicholas@2708
|
2321 var flagListenedTo = storage.document.createElement('metricresult');
|
nicholas@2708
|
2322 flagListenedTo.setAttribute('name', 'elementFlagListenedTo');
|
nicholas@2708
|
2323 flagListenedTo.textContent = this.wasListenedTo;
|
nicholas@2708
|
2324 parentElement.appendChild(flagListenedTo);
|
nicholas@2708
|
2325 return flagListenedTo;
|
nicholas@2708
|
2326 }
|
nicholas@2708
|
2327
|
nicholas@2708
|
2328 function exportFlagMoved(parentElement) {
|
nicholas@2708
|
2329 var flagMoved = storage.document.createElement('metricresult');
|
nicholas@2708
|
2330 flagMoved.setAttribute('name', 'elementFlagMoved');
|
nicholas@2708
|
2331 flagMoved.textContent = this.wasMoved;
|
nicholas@2708
|
2332 parentElement.appendChild(flagMoved);
|
nicholas@2708
|
2333 return flagMoved;
|
nicholas@2708
|
2334 }
|
nicholas@2708
|
2335
|
nicholas@2708
|
2336 function exportFlagComments(parentElement) {
|
nicholas@2708
|
2337 var flagComments = storage.document.createElement('metricresult');
|
nicholas@2708
|
2338 flagComments.setAttribute('name', 'elementFlagComments');
|
nicholas@2708
|
2339 if (this.parent.commentDOM === null) {
|
nicholas@2708
|
2340 flagComments.textContent = 'false';
|
nicholas@2708
|
2341 } else if (this.parent.commentDOM.textContent.length === 0) {
|
nicholas@2708
|
2342 flagComments.textContent = 'false';
|
nicholas@2708
|
2343 } else {
|
nicholas@2708
|
2344 flagComments.textContet = 'true';
|
nicholas@2708
|
2345 }
|
nicholas@2708
|
2346 parentElement.appendChild(flagComments);
|
nicholas@2708
|
2347 return flagComments;
|
nicholas@2708
|
2348 }
|
nicholas@2708
|
2349
|
nicholas@2708
|
2350 this.exportXMLDOM = function (parentElement) {
|
nicholas@2708
|
2351 var elems = [];
|
nicholas@2498
|
2352 if (audioEngineContext.metric.enableElementTimer) {
|
nicholas@2708
|
2353 elems.push(exportElementTimer.call(this, parentElement));
|
nicholas@2498
|
2354 }
|
nicholas@2498
|
2355 if (audioEngineContext.metric.enableElementTracker) {
|
nicholas@2708
|
2356 elems.push(exportElementTrack.call(this, parentElement));
|
nicholas@2498
|
2357 }
|
nicholas@2498
|
2358 if (audioEngineContext.metric.enableElementListenTracker) {
|
nicholas@2708
|
2359 elems.push(exportElementListenTracker.call(this, parentElement));
|
nicholas@2498
|
2360 }
|
nicholas@2498
|
2361 if (audioEngineContext.metric.enableElementInitialPosition) {
|
nicholas@2708
|
2362 elems.push(exportElementInitialPosition.call(this, parentElement));
|
nicholas@2498
|
2363 }
|
nicholas@2498
|
2364 if (audioEngineContext.metric.enableFlagListenedTo) {
|
nicholas@2708
|
2365 elems.push(exportFlagListenedTo.call(this, parentElement));
|
nicholas@2498
|
2366 }
|
nicholas@2498
|
2367 if (audioEngineContext.metric.enableFlagMoved) {
|
nicholas@2708
|
2368 elems.push(exportFlagMoved.call(this, parentElement));
|
nicholas@2498
|
2369 }
|
nicholas@2498
|
2370 if (audioEngineContext.metric.enableFlagComments) {
|
nicholas@2708
|
2371 elems.push(exportFlagComments.call(this, parentElement));
|
nicholas@2498
|
2372 }
|
nicholas@2708
|
2373 return elems;
|
nicholas@2498
|
2374 };
|
nicholas@2224
|
2375 }
|
nicholas@2498
|
2376
|
nicholas@2224
|
2377 function Interface(specificationObject) {
|
nicholas@2498
|
2378 // This handles the bindings between the interface and the audioEngineContext;
|
nicholas@2498
|
2379 this.specification = specificationObject;
|
nicholas@2498
|
2380 this.insertPoint = document.getElementById("topLevelBody");
|
nicholas@2498
|
2381
|
nicholas@2498
|
2382 this.newPage = function (audioHolderObject, store) {
|
nicholas@2498
|
2383 audioEngineContext.newTestPage(audioHolderObject, store);
|
nicholas@2498
|
2384 interfaceContext.commentBoxes.deleteCommentBoxes();
|
nicholas@2498
|
2385 interfaceContext.deleteCommentQuestions();
|
nicholas@2498
|
2386 loadTest(audioHolderObject, store);
|
nicholas@2498
|
2387 };
|
nicholas@2498
|
2388
|
nicholas@2955
|
2389 this.keyboardInterface = (function () {
|
nicholas@2955
|
2390 var keyboardInterfaceController = {
|
nicholas@2955
|
2391 keys: [],
|
nicholas@2955
|
2392 registerKeyBinding: function (key, audioObject) {
|
nicholas@2955
|
2393 if (typeof key != "string" || key.length != 1) {
|
nicholas@2955
|
2394 throw ("Key must be a singular character");
|
nicholas@2955
|
2395 }
|
nicholas@2955
|
2396 var included = this.keys.findIndex(function (k) {
|
n@2979
|
2397 return k.key == key;
|
nicholas@2955
|
2398 }) >= 0;
|
nicholas@2955
|
2399 if (included) {
|
nicholas@2955
|
2400 throw ("Key " + key + " already bounded!");
|
nicholas@2955
|
2401 }
|
nicholas@2955
|
2402 this.keys.push({
|
nicholas@2955
|
2403 key: key,
|
nicholas@2955
|
2404 audioObject: audioObject
|
nicholas@2955
|
2405 });
|
nicholas@2955
|
2406 return true;
|
nicholas@2955
|
2407 },
|
nicholas@2955
|
2408 deregisterKeyBinding: function (key) {
|
nicholas@2955
|
2409 var index = this.keys.findIndex(function (k) {
|
nicholas@2955
|
2410 return k.key == key;
|
nicholas@2955
|
2411 });
|
nicholas@2955
|
2412 if (index == -1) {
|
nicholas@2955
|
2413 throw ("Key " + key + " not bounded!");
|
nicholas@2955
|
2414 }
|
nicholas@2955
|
2415 this.keys.splice(index, 1);
|
nicholas@2955
|
2416 return true;
|
nicholas@2955
|
2417 },
|
nicholas@2955
|
2418 resetKeyBindings: function () {
|
nicholas@2955
|
2419 this.keys = [];
|
nicholas@2955
|
2420 },
|
nicholas@2955
|
2421 handleEvent: function (e) {
|
nicholas@2955
|
2422 function isPlaying() {
|
nicholas@2955
|
2423 return audioEngineContext.audioObjects.some(function (a) {
|
nicholas@2955
|
2424 return a.playing;
|
nicholas@2955
|
2425 });
|
nicholas@2955
|
2426 }
|
nicholas@2955
|
2427
|
nicholas@2955
|
2428 function keypress(key) {
|
nicholas@2955
|
2429 var index = this.keys.findIndex(function (k) {
|
n@2979
|
2430 return k.key == key;
|
nicholas@2955
|
2431 });
|
nicholas@2955
|
2432 if (index >= 0) {
|
nicholas@2955
|
2433 audioEngineContext.play(this.keys[index].audioObject.id);
|
nicholas@2955
|
2434 }
|
nicholas@2955
|
2435 }
|
n@2965
|
2436
|
n@2965
|
2437 function trackCommentFocus() {
|
n@2965
|
2438 return document.activeElement.className.indexOf("trackComment") >= 0;
|
n@2965
|
2439 }
|
nicholas@2955
|
2440 if (e.key === " ") {
|
n@2979
|
2441 if (isPlaying() && trackCommentFocus() === false) {
|
n@2965
|
2442 e.preventDefault();
|
nicholas@2955
|
2443 audioEngineContext.stop();
|
nicholas@2955
|
2444 }
|
nicholas@2955
|
2445 } else {
|
nicholas@2955
|
2446 keypress.call(this, e.key);
|
nicholas@2955
|
2447 }
|
n@2965
|
2448 console.log(e);
|
nicholas@2955
|
2449 }
|
nicholas@2955
|
2450 };
|
nicholas@2955
|
2451 document.addEventListener("keydown", keyboardInterfaceController, false);
|
nicholas@2955
|
2452 return keyboardInterfaceController;
|
nicholas@2955
|
2453 })();
|
nicholas@2955
|
2454
|
nicholas@2498
|
2455 // Bounded by interface!!
|
nicholas@2498
|
2456 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
nicholas@2498
|
2457 // For example, APE returns the slider position normalised in a <value> tag.
|
nicholas@2498
|
2458 this.interfaceObjects = [];
|
nicholas@2498
|
2459 this.interfaceObject = function () {};
|
nicholas@2498
|
2460
|
nicholas@2498
|
2461 this.resizeWindow = function (event) {
|
nicholas@2498
|
2462 popup.resize(event);
|
nicholas@2352
|
2463 this.volume.resize();
|
nicholas@2360
|
2464 this.lightbox.resize();
|
n@2718
|
2465 this.commentBoxes.boxes.forEach(function (elem) {
|
nicholas@2708
|
2466 elem.resize();
|
nicholas@2708
|
2467 });
|
nicholas@2708
|
2468 this.commentQuestions.forEach(function (elem) {
|
nicholas@2708
|
2469 elem.resize();
|
nicholas@2708
|
2470 });
|
nicholas@2498
|
2471 try {
|
nicholas@2498
|
2472 resizeWindow(event);
|
nicholas@2498
|
2473 } catch (err) {
|
nicholas@2498
|
2474 console.log("Warning - Interface does not have Resize option");
|
nicholas@2498
|
2475 console.log(err);
|
nicholas@2498
|
2476 }
|
nicholas@2498
|
2477 };
|
nicholas@2498
|
2478
|
nicholas@2498
|
2479 this.returnNavigator = function () {
|
nicholas@2498
|
2480 var node = storage.document.createElement("navigator");
|
nicholas@2498
|
2481 var platform = storage.document.createElement("platform");
|
nicholas@2498
|
2482 platform.textContent = navigator.platform;
|
nicholas@2498
|
2483 var vendor = storage.document.createElement("vendor");
|
nicholas@2498
|
2484 vendor.textContent = navigator.vendor;
|
nicholas@2498
|
2485 var userAgent = storage.document.createElement("uagent");
|
nicholas@2498
|
2486 userAgent.textContent = navigator.userAgent;
|
nicholas@2224
|
2487 var screen = storage.document.createElement("window");
|
nicholas@2498
|
2488 screen.setAttribute('innerWidth', window.innerWidth);
|
nicholas@2498
|
2489 screen.setAttribute('innerHeight', window.innerHeight);
|
nicholas@2498
|
2490 node.appendChild(platform);
|
nicholas@2498
|
2491 node.appendChild(vendor);
|
nicholas@2498
|
2492 node.appendChild(userAgent);
|
nicholas@2224
|
2493 node.appendChild(screen);
|
nicholas@2498
|
2494 return node;
|
nicholas@2498
|
2495 };
|
nicholas@2498
|
2496
|
nicholas@2498
|
2497 this.returnDateNode = function () {
|
nicholas@2224
|
2498 // Create an XML Node for the Date and Time a test was conducted
|
nicholas@2224
|
2499 // Structure is
|
nicholas@2224
|
2500 // <datetime>
|
nicholas@2224
|
2501 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
nicholas@2224
|
2502 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
nicholas@2224
|
2503 // </datetime>
|
nicholas@2224
|
2504 var dateTime = new Date();
|
nicholas@2224
|
2505 var hold = storage.document.createElement("datetime");
|
nicholas@2224
|
2506 var date = storage.document.createElement("date");
|
nicholas@2224
|
2507 var time = storage.document.createElement("time");
|
nicholas@2498
|
2508 date.setAttribute('year', dateTime.getFullYear());
|
nicholas@2498
|
2509 date.setAttribute('month', dateTime.getMonth() + 1);
|
nicholas@2498
|
2510 date.setAttribute('day', dateTime.getDate());
|
nicholas@2498
|
2511 time.setAttribute('hour', dateTime.getHours());
|
nicholas@2498
|
2512 time.setAttribute('minute', dateTime.getMinutes());
|
nicholas@2498
|
2513 time.setAttribute('secs', dateTime.getSeconds());
|
nicholas@2498
|
2514
|
nicholas@2224
|
2515 hold.appendChild(date);
|
nicholas@2224
|
2516 hold.appendChild(time);
|
nicholas@2224
|
2517 return hold;
|
nicholas@2224
|
2518
|
nicholas@2708
|
2519 };
|
nicholas@2498
|
2520
|
nicholas@2360
|
2521 this.lightbox = {
|
nicholas@2360
|
2522 parent: this,
|
nicholas@2360
|
2523 root: document.createElement("div"),
|
nicholas@2360
|
2524 content: document.createElement("div"),
|
nicholas@2360
|
2525 accept: document.createElement("button"),
|
nicholas@2360
|
2526 blanker: document.createElement("div"),
|
nicholas@2498
|
2527 post: function (type, message) {
|
nicholas@2498
|
2528 switch (type) {
|
nicholas@2360
|
2529 case "Error":
|
nicholas@2360
|
2530 this.content.className = "lightbox-error";
|
nicholas@2360
|
2531 break;
|
nicholas@2360
|
2532 case "Warning":
|
nicholas@2360
|
2533 this.content.className = "lightbox-warning";
|
nicholas@2360
|
2534 break;
|
nicholas@2360
|
2535 default:
|
nicholas@2360
|
2536 this.content.className = "lightbox-message";
|
nicholas@2360
|
2537 break;
|
nicholas@2360
|
2538 }
|
nicholas@2360
|
2539 var msg = document.createElement("p");
|
nicholas@2360
|
2540 msg.textContent = message;
|
nicholas@2360
|
2541 this.content.appendChild(msg);
|
nicholas@2360
|
2542 this.show();
|
nicholas@2360
|
2543 },
|
nicholas@2498
|
2544 show: function () {
|
nicholas@2360
|
2545 this.root.style.visibility = "visible";
|
nicholas@2360
|
2546 this.blanker.style.visibility = "visible";
|
n@2914
|
2547 this.accept.focus();
|
nicholas@2360
|
2548 },
|
nicholas@2498
|
2549 clear: function () {
|
nicholas@2360
|
2550 this.root.style.visibility = "";
|
nicholas@2360
|
2551 this.blanker.style.visibility = "";
|
nicholas@2360
|
2552 this.content.textContent = "";
|
nicholas@2360
|
2553 },
|
nicholas@2498
|
2554 handleEvent: function (event) {
|
nicholas@2360
|
2555 if (event.currentTarget == this.accept) {
|
nicholas@2360
|
2556 this.clear();
|
nicholas@2360
|
2557 }
|
nicholas@2360
|
2558 },
|
nicholas@2498
|
2559 resize: function (event) {
|
nicholas@2498
|
2560 this.root.style.left = (window.innerWidth / 2) - 250 + 'px';
|
n@2915
|
2561 },
|
n@2915
|
2562 isVisible: function () {
|
n@2915
|
2563 return this.root.style.visibility == "visible";
|
nicholas@2360
|
2564 }
|
nicholas@2708
|
2565 };
|
nicholas@2498
|
2566
|
nicholas@2360
|
2567 this.lightbox.root.appendChild(this.lightbox.content);
|
nicholas@2360
|
2568 this.lightbox.root.appendChild(this.lightbox.accept);
|
nicholas@2360
|
2569 this.lightbox.root.className = "popupHolder";
|
nicholas@2360
|
2570 this.lightbox.root.id = "lightbox-root";
|
nicholas@2360
|
2571 this.lightbox.accept.className = "popupButton";
|
nicholas@2360
|
2572 this.lightbox.accept.style.bottom = "10px";
|
nicholas@2360
|
2573 this.lightbox.accept.textContent = "OK";
|
nicholas@2360
|
2574 this.lightbox.accept.style.left = "237.5px";
|
nicholas@2498
|
2575 this.lightbox.accept.addEventListener("click", this.lightbox);
|
nicholas@2360
|
2576 this.lightbox.blanker.className = "testHalt";
|
nicholas@2360
|
2577 this.lightbox.blanker.id = "lightbox-blanker";
|
nicholas@2360
|
2578 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
|
nicholas@2360
|
2579 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
|
nicholas@2498
|
2580
|
nicholas@2712
|
2581 this.commentBoxes = (function () {
|
nicholas@2712
|
2582 var commentBoxes = {};
|
nicholas@2712
|
2583 commentBoxes.boxes = [];
|
nicholas@2712
|
2584 commentBoxes.injectPoint = null;
|
nicholas@2712
|
2585 commentBoxes.elementCommentBox = function (audioObject) {
|
nicholas@2224
|
2586 var element = audioObject.specification;
|
nicholas@2224
|
2587 this.audioObject = audioObject;
|
nicholas@2224
|
2588 this.id = audioObject.id;
|
nicholas@2224
|
2589 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
2590 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2591 this.trackComment = document.createElement('div');
|
nicholas@2224
|
2592 this.trackComment.className = 'comment-div';
|
nicholas@2498
|
2593 this.trackComment.id = 'comment-div-' + audioObject.id;
|
nicholas@2224
|
2594 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2595 this.trackString = document.createElement('span');
|
nicholas@2498
|
2596 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
2597 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2598 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
2599 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
2600 this.trackCommentBox.cols = '100';
|
nicholas@2498
|
2601 this.trackCommentBox.name = 'trackComment' + audioObject.id;
|
nicholas@2224
|
2602 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
2603 var br = document.createElement('br');
|
nicholas@2224
|
2604 // Add to the holder.
|
nicholas@2224
|
2605 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
2606 this.trackComment.appendChild(br);
|
nicholas@2224
|
2607 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
2608
|
nicholas@2498
|
2609 this.exportXMLDOM = function () {
|
nicholas@2224
|
2610 var root = document.createElement('comment');
|
nicholas@2224
|
2611 var question = document.createElement('question');
|
nicholas@2224
|
2612 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2613 var response = document.createElement('response');
|
nicholas@2224
|
2614 response.textContent = this.trackCommentBox.value;
|
nicholas@2498
|
2615 console.log("Comment frag-" + this.id + ": " + response.textContent);
|
nicholas@2224
|
2616 root.appendChild(question);
|
nicholas@2224
|
2617 root.appendChild(response);
|
nicholas@2224
|
2618 return root;
|
nicholas@2224
|
2619 };
|
nicholas@2498
|
2620 this.resize = function () {
|
nicholas@2498
|
2621 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2622 if (boxwidth >= 600) {
|
nicholas@2224
|
2623 boxwidth = 600;
|
nicholas@2498
|
2624 } else if (boxwidth < 400) {
|
nicholas@2224
|
2625 boxwidth = 400;
|
nicholas@2224
|
2626 }
|
nicholas@2498
|
2627 this.trackComment.style.width = boxwidth + "px";
|
nicholas@2498
|
2628 this.trackCommentBox.style.width = boxwidth - 6 + "px";
|
nicholas@2224
|
2629 };
|
nicholas@2224
|
2630 this.resize();
|
nicholas@2725
|
2631 this.highlight = function (state) {
|
nicholas@2725
|
2632 if (state === true) {
|
nicholas@2725
|
2633 $(this.trackComment).addClass("comment-box-playing");
|
nicholas@2725
|
2634 } else {
|
nicholas@2725
|
2635 $(this.trackComment).removeClass("comment-box-playing");
|
nicholas@2725
|
2636 }
|
nicholas@2725
|
2637 };
|
nicholas@2224
|
2638 };
|
nicholas@2712
|
2639 commentBoxes.createCommentBox = function (audioObject) {
|
nicholas@2224
|
2640 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2641 this.boxes.push(node);
|
nicholas@2224
|
2642 audioObject.commentDOM = node;
|
nicholas@2224
|
2643 return node;
|
nicholas@2224
|
2644 };
|
nicholas@2712
|
2645 commentBoxes.sortCommentBoxes = function () {
|
nicholas@2498
|
2646 this.boxes.sort(function (a, b) {
|
nicholas@2498
|
2647 return a.id - b.id;
|
nicholas@2498
|
2648 });
|
nicholas@2224
|
2649 };
|
nicholas@2224
|
2650
|
nicholas@2712
|
2651 commentBoxes.showCommentBoxes = function (inject, sort) {
|
nicholas@2224
|
2652 this.injectPoint = inject;
|
nicholas@2498
|
2653 if (sort) {
|
nicholas@2498
|
2654 this.sortCommentBoxes();
|
nicholas@2498
|
2655 }
|
nicholas@2708
|
2656 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2657 inject.appendChild(box.trackComment);
|
nicholas@2708
|
2658 });
|
nicholas@2224
|
2659 };
|
nicholas@2224
|
2660
|
nicholas@2712
|
2661 commentBoxes.deleteCommentBoxes = function () {
|
nicholas@2708
|
2662 if (this.injectPoint !== null) {
|
nicholas@2708
|
2663 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2664 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2708
|
2665 }, this);
|
nicholas@2224
|
2666 this.injectPoint = null;
|
nicholas@2224
|
2667 }
|
nicholas@2224
|
2668 this.boxes = [];
|
nicholas@2224
|
2669 };
|
nicholas@2725
|
2670 commentBoxes.highlightById = function (id) {
|
nicholas@2725
|
2671 if (id === undefined || typeof id !== "number" || id >= this.boxes.length) {
|
nicholas@2725
|
2672 console.log("Error - Invalid id");
|
nicholas@2725
|
2673 id = -1;
|
nicholas@2725
|
2674 }
|
nicholas@2725
|
2675 this.boxes.forEach(function (a) {
|
nicholas@2725
|
2676 if (a.id === id) {
|
nicholas@2725
|
2677 a.highlight(true);
|
nicholas@2725
|
2678 } else {
|
nicholas@2725
|
2679 a.highlight(false);
|
nicholas@2725
|
2680 }
|
nicholas@2725
|
2681 });
|
nicholas@2725
|
2682 };
|
nicholas@2712
|
2683 return commentBoxes;
|
nicholas@2712
|
2684 })();
|
nicholas@2498
|
2685
|
nicholas@2498
|
2686 this.commentQuestions = [];
|
nicholas@2498
|
2687
|
nicholas@2498
|
2688 this.commentBox = function (commentQuestion) {
|
nicholas@2498
|
2689 this.specification = commentQuestion;
|
nicholas@2498
|
2690 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2691 this.holder = document.createElement('div');
|
nicholas@2498
|
2692 this.holder.className = 'comment-div';
|
nicholas@2498
|
2693 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2694 this.string = document.createElement('span');
|
nicholas@2498
|
2695 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2696 // Create the HTML5 comment box 'textarea'
|
nicholas@2498
|
2697 this.textArea = document.createElement('textarea');
|
nicholas@2498
|
2698 this.textArea.rows = '4';
|
nicholas@2498
|
2699 this.textArea.cols = '100';
|
nicholas@2498
|
2700 this.textArea.className = 'trackComment';
|
nicholas@2498
|
2701 var br = document.createElement('br');
|
nicholas@2498
|
2702 // Add to the holder.
|
nicholas@2498
|
2703 this.holder.appendChild(this.string);
|
nicholas@2498
|
2704 this.holder.appendChild(br);
|
nicholas@2498
|
2705 this.holder.appendChild(this.textArea);
|
nicholas@2498
|
2706
|
nicholas@2498
|
2707 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2708 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2709 root.id = this.specification.id;
|
nicholas@2498
|
2710 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2711 console.log("Question: " + this.string.textContent);
|
nicholas@2498
|
2712 console.log("Response: " + root.textContent);
|
nicholas@2224
|
2713 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2714 question.textContent = this.string.textContent;
|
nicholas@2224
|
2715 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2716 response.textContent = this.textArea.value;
|
nicholas@2224
|
2717 root.appendChild(question);
|
nicholas@2224
|
2718 root.appendChild(response);
|
nicholas@2224
|
2719 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2720 return root;
|
nicholas@2498
|
2721 };
|
nicholas@2498
|
2722 this.resize = function () {
|
nicholas@2498
|
2723 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2724 if (boxwidth >= 600) {
|
nicholas@2498
|
2725 boxwidth = 600;
|
nicholas@2498
|
2726 } else if (boxwidth < 400) {
|
nicholas@2498
|
2727 boxwidth = 400;
|
nicholas@2498
|
2728 }
|
nicholas@2498
|
2729 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2730 this.textArea.style.width = boxwidth - 6 + "px";
|
nicholas@2498
|
2731 };
|
nicholas@2498
|
2732 this.resize();
|
nicholas@3063
|
2733 this.check = function () {
|
n@3095
|
2734 if (this.specification.mandatory && this.textArea.value.length === 0) {
|
nicholas@3063
|
2735 return false;
|
nicholas@3063
|
2736 }
|
nicholas@3063
|
2737 return true;
|
n@3095
|
2738 };
|
nicholas@2498
|
2739 };
|
nicholas@2498
|
2740
|
nicholas@2498
|
2741 this.radioBox = function (commentQuestion) {
|
nicholas@2498
|
2742 this.specification = commentQuestion;
|
nicholas@2498
|
2743 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2744 this.holder = document.createElement('div');
|
nicholas@2498
|
2745 this.holder.className = 'comment-div';
|
nicholas@2498
|
2746 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2747 this.string = document.createElement('span');
|
nicholas@2498
|
2748 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2749 // Add to the holder.
|
nicholas@2498
|
2750 this.holder.appendChild(this.string);
|
nicholas@2498
|
2751 this.options = [];
|
nicholas@2498
|
2752 this.inputs = document.createElement('div');
|
nicholas@2711
|
2753 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2754
|
nicholas@2498
|
2755 var optCount = commentQuestion.options.length;
|
nicholas@2711
|
2756 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2757 var div = document.createElement('div');
|
nicholas@2711
|
2758 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2759
|
nicholas@2711
|
2760 var span = document.createElement('span');
|
nicholas@2711
|
2761 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2762 span.className = 'comment-radio-span';
|
nicholas@2711
|
2763 div.appendChild(span);
|
nicholas@2722
|
2764
|
nicholas@2498
|
2765 var input = document.createElement('input');
|
nicholas@2498
|
2766 input.type = 'radio';
|
nicholas@2498
|
2767 input.name = commentQuestion.id;
|
nicholas@2711
|
2768 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2769 input.className = 'comment-radio';
|
nicholas@2498
|
2770 div.appendChild(input);
|
nicholas@2722
|
2771
|
nicholas@2498
|
2772 this.inputs.appendChild(div);
|
nicholas@2498
|
2773 this.options.push(input);
|
nicholas@2498
|
2774 }
|
nicholas@2498
|
2775 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2776
|
nicholas@2498
|
2777 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2778 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2779 root.id = this.specification.id;
|
nicholas@2498
|
2780 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2781 var question = document.createElement('question');
|
nicholas@2498
|
2782 question.textContent = this.string.textContent;
|
nicholas@2498
|
2783 var response = document.createElement('response');
|
nicholas@2498
|
2784 var i = 0;
|
nicholas@2708
|
2785 while (this.options[i].checked === false) {
|
nicholas@2498
|
2786 i++;
|
nicholas@2498
|
2787 if (i >= this.options.length) {
|
nicholas@2498
|
2788 break;
|
nicholas@2498
|
2789 }
|
nicholas@2498
|
2790 }
|
nicholas@2498
|
2791 if (i >= this.options.length) {
|
nicholas@2498
|
2792 response.textContent = 'null';
|
nicholas@2498
|
2793 } else {
|
nicholas@2498
|
2794 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2498
|
2795 response.setAttribute('number', i);
|
nicholas@2498
|
2796 }
|
nicholas@2498
|
2797 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2798 console.log('Response: ' + response.textContent);
|
nicholas@2498
|
2799 root.appendChild(question);
|
nicholas@2498
|
2800 root.appendChild(response);
|
nicholas@2224
|
2801 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2802 return root;
|
nicholas@2498
|
2803 };
|
nicholas@2498
|
2804 this.resize = function () {
|
nicholas@2498
|
2805 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2806 if (boxwidth >= 600) {
|
nicholas@2498
|
2807 boxwidth = 600;
|
nicholas@2498
|
2808 } else if (boxwidth < 400) {
|
nicholas@2498
|
2809 boxwidth = 400;
|
nicholas@2498
|
2810 }
|
nicholas@2498
|
2811 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2812 };
|
nicholas@3063
|
2813 this.check = function () {
|
nicholas@3063
|
2814 var anyChecked = this.options.some(function (a) {
|
nicholas@3063
|
2815 return a.checked;
|
nicholas@3063
|
2816 });
|
n@3095
|
2817 if (this.specification.mandatory && anyChecked === false) {
|
nicholas@3063
|
2818 return false;
|
nicholas@3063
|
2819 }
|
nicholas@3063
|
2820 return true;
|
n@3095
|
2821 };
|
nicholas@2498
|
2822 this.resize();
|
nicholas@2498
|
2823 };
|
nicholas@2498
|
2824
|
nicholas@2498
|
2825 this.checkboxBox = function (commentQuestion) {
|
nicholas@2498
|
2826 this.specification = commentQuestion;
|
nicholas@2498
|
2827 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2828 this.holder = document.createElement('div');
|
nicholas@2498
|
2829 this.holder.className = 'comment-div';
|
nicholas@2498
|
2830 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2831 this.string = document.createElement('span');
|
nicholas@2498
|
2832 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2833 // Add to the holder.
|
nicholas@2498
|
2834 this.holder.appendChild(this.string);
|
nicholas@2498
|
2835 this.options = [];
|
nicholas@2498
|
2836 this.inputs = document.createElement('div');
|
nicholas@2294
|
2837 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2838
|
nicholas@2498
|
2839 var optCount = commentQuestion.options.length;
|
nicholas@2498
|
2840 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2841 var div = document.createElement('div');
|
nicholas@2711
|
2842 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2843
|
nicholas@2711
|
2844 var span = document.createElement('span');
|
nicholas@2711
|
2845 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2846 span.className = 'comment-radio-span';
|
nicholas@2711
|
2847 div.appendChild(span);
|
nicholas@2722
|
2848
|
nicholas@2498
|
2849 var input = document.createElement('input');
|
nicholas@2498
|
2850 input.type = 'checkbox';
|
nicholas@2498
|
2851 input.name = commentQuestion.id;
|
nicholas@2498
|
2852 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2853 input.className = 'comment-radio';
|
nicholas@2498
|
2854 div.appendChild(input);
|
nicholas@2722
|
2855
|
nicholas@2498
|
2856 this.inputs.appendChild(div);
|
nicholas@2498
|
2857 this.options.push(input);
|
nicholas@2498
|
2858 }
|
nicholas@2498
|
2859 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2860
|
nicholas@2498
|
2861 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2862 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2863 root.id = this.specification.id;
|
nicholas@2498
|
2864 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2865 var question = document.createElement('question');
|
nicholas@2498
|
2866 question.textContent = this.string.textContent;
|
nicholas@2498
|
2867 root.appendChild(question);
|
nicholas@2498
|
2868 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2869 for (var i = 0; i < this.options.length; i++) {
|
nicholas@2498
|
2870 var response = document.createElement('response');
|
nicholas@2498
|
2871 response.textContent = this.options[i].checked;
|
nicholas@2498
|
2872 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
|
nicholas@2498
|
2873 root.appendChild(response);
|
nicholas@2498
|
2874 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
|
nicholas@2498
|
2875 }
|
nicholas@2224
|
2876 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2877 return root;
|
nicholas@2498
|
2878 };
|
nicholas@2498
|
2879 this.resize = function () {
|
nicholas@2498
|
2880 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2881 if (boxwidth >= 600) {
|
nicholas@2498
|
2882 boxwidth = 600;
|
nicholas@2498
|
2883 } else if (boxwidth < 400) {
|
nicholas@2498
|
2884 boxwidth = 400;
|
nicholas@2498
|
2885 }
|
nicholas@2498
|
2886 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2887 };
|
nicholas@3063
|
2888 this.check = function () {
|
nicholas@3063
|
2889 var anyChecked = this.options.some(function (a) {
|
nicholas@3063
|
2890 return a.checked;
|
nicholas@3063
|
2891 });
|
n@3095
|
2892 if (this.specification.mandatory && anyChecked === false) {
|
nicholas@3063
|
2893 return false;
|
nicholas@3063
|
2894 }
|
nicholas@3063
|
2895 return true;
|
nicholas@3063
|
2896 };
|
nicholas@2498
|
2897 this.resize();
|
nicholas@2498
|
2898 };
|
nicholas@2498
|
2899
|
n@2579
|
2900 this.sliderBox = function (commentQuestion) {
|
n@2579
|
2901 this.specification = commentQuestion;
|
n@2579
|
2902 this.holder = document.createElement("div");
|
n@2579
|
2903 this.holder.className = 'comment-div';
|
n@2579
|
2904 this.string = document.createElement("span");
|
n@2579
|
2905 this.string.innerHTML = commentQuestion.statement;
|
n@2579
|
2906 this.slider = document.createElement("input");
|
n@2579
|
2907 this.slider.type = "range";
|
n@2579
|
2908 this.slider.min = commentQuestion.min;
|
n@2579
|
2909 this.slider.max = commentQuestion.max;
|
n@2579
|
2910 this.slider.step = commentQuestion.step;
|
n@2579
|
2911 this.slider.value = commentQuestion.value;
|
n@2579
|
2912 var br = document.createElement('br');
|
n@2579
|
2913
|
n@2580
|
2914 var textHolder = document.createElement("div");
|
n@2580
|
2915 textHolder.className = "comment-slider-text-holder";
|
n@2580
|
2916
|
n@2580
|
2917 this.leftText = document.createElement("span");
|
n@2580
|
2918 this.leftText.textContent = commentQuestion.leftText;
|
n@2580
|
2919 this.rightText = document.createElement("span");
|
n@2580
|
2920 this.rightText.textContent = commentQuestion.rightText;
|
n@2580
|
2921 textHolder.appendChild(this.leftText);
|
n@2580
|
2922 textHolder.appendChild(this.rightText);
|
n@2580
|
2923
|
n@2579
|
2924 this.holder.appendChild(this.string);
|
n@2579
|
2925 this.holder.appendChild(br);
|
n@2579
|
2926 this.holder.appendChild(this.slider);
|
n@2580
|
2927 this.holder.appendChild(textHolder);
|
n@2579
|
2928
|
n@2579
|
2929 this.exportXMLDOM = function (storePoint) {
|
n@2579
|
2930 var root = storePoint.parent.document.createElement('comment');
|
n@2579
|
2931 root.id = this.specification.id;
|
n@2579
|
2932 root.setAttribute('type', this.specification.type);
|
n@2579
|
2933 console.log("Question: " + this.string.textContent);
|
n@2579
|
2934 console.log("Response: " + this.slider.value);
|
n@2579
|
2935 var question = storePoint.parent.document.createElement('question');
|
n@2579
|
2936 question.textContent = this.string.textContent;
|
n@2579
|
2937 var response = storePoint.parent.document.createElement('response');
|
n@2579
|
2938 response.textContent = this.slider.value;
|
n@2579
|
2939 root.appendChild(question);
|
n@2579
|
2940 root.appendChild(response);
|
n@2579
|
2941 storePoint.XMLDOM.appendChild(root);
|
n@2579
|
2942 return root;
|
n@2579
|
2943 };
|
n@2579
|
2944 this.resize = function () {
|
n@2579
|
2945 var boxwidth = (window.innerWidth - 100) / 2;
|
n@2579
|
2946 if (boxwidth >= 600) {
|
n@2579
|
2947 boxwidth = 600;
|
n@2579
|
2948 } else if (boxwidth < 400) {
|
n@2579
|
2949 boxwidth = 400;
|
n@2579
|
2950 }
|
n@2579
|
2951 this.holder.style.width = boxwidth + "px";
|
n@2579
|
2952 this.slider.style.width = boxwidth - 24 + "px";
|
n@2579
|
2953 };
|
nicholas@3063
|
2954 this.check = function () {
|
nicholas@3063
|
2955 return true;
|
n@3095
|
2956 };
|
n@2579
|
2957 this.resize();
|
n@2579
|
2958 };
|
n@2579
|
2959
|
nicholas@2498
|
2960 this.createCommentQuestion = function (element) {
|
nicholas@2498
|
2961 var node;
|
nicholas@2498
|
2962 if (element.type == 'question') {
|
nicholas@2498
|
2963 node = new this.commentBox(element);
|
nicholas@2498
|
2964 } else if (element.type == 'radio') {
|
nicholas@2498
|
2965 node = new this.radioBox(element);
|
nicholas@2498
|
2966 } else if (element.type == 'checkbox') {
|
nicholas@2498
|
2967 node = new this.checkboxBox(element);
|
n@2579
|
2968 } else if (element.type == 'slider') {
|
n@2579
|
2969 node = new this.sliderBox(element);
|
nicholas@2498
|
2970 }
|
nicholas@2498
|
2971 this.commentQuestions.push(node);
|
nicholas@2498
|
2972 return node;
|
nicholas@2498
|
2973 };
|
nicholas@2498
|
2974
|
nicholas@2498
|
2975 this.deleteCommentQuestions = function () {
|
nicholas@2498
|
2976 this.commentQuestions = [];
|
nicholas@2498
|
2977 };
|
nicholas@2498
|
2978
|
nicholas@3063
|
2979 this.checkCommentQuestions = function () {
|
nicholas@3063
|
2980 var errored = this.commentQuestions.reduce(function (a, cq) {
|
n@3095
|
2981 if (cq.check() === false) {
|
nicholas@3063
|
2982 a.push(cq);
|
nicholas@3063
|
2983 }
|
nicholas@3063
|
2984 return a;
|
nicholas@3063
|
2985 }, []);
|
n@3095
|
2986 if (errored.length === 0) {
|
nicholas@3063
|
2987 return true;
|
nicholas@3063
|
2988 }
|
nicholas@3063
|
2989 interfaceContext.lightbox.post("Message", "Not all the mandatory comment boxes below have been filled.");
|
n@3095
|
2990 };
|
nicholas@3063
|
2991
|
nicholas@2498
|
2992 this.outsideReferenceDOM = function (audioObject, index, inject) {
|
nicholas@2224
|
2993 this.parent = audioObject;
|
nicholas@2224
|
2994 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2995 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2498
|
2996 this.outsideReferenceHolder.setAttribute('track-id', index);
|
nicholas@2409
|
2997 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2998 this.outsideReferenceHolder.disabled = true;
|
nicholas@2708
|
2999 this.handleEvent = function (event) {
|
nicholas@2708
|
3000 audioEngineContext.play(this.parent.id);
|
nicholas@2224
|
3001 };
|
nicholas@2708
|
3002 this.outsideReferenceHolder.addEventListener("click", this);
|
nicholas@2224
|
3003 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2498
|
3004 this.enable = function () {
|
nicholas@2498
|
3005 if (this.parent.state == 1) {
|
nicholas@2224
|
3006 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
3007 }
|
nicholas@2224
|
3008 };
|
nicholas@2498
|
3009 this.updateLoading = function (progress) {
|
nicholas@2498
|
3010 if (progress != 100) {
|
nicholas@2224
|
3011 progress = String(progress);
|
nicholas@2224
|
3012 progress = progress.split('.')[0];
|
nicholas@2498
|
3013 this.outsideReferenceHolder.textContent = progress + '%';
|
nicholas@2224
|
3014 } else {
|
nicholas@2409
|
3015 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
3016 }
|
nicholas@2224
|
3017 };
|
nicholas@2498
|
3018 this.startPlayback = function () {
|
nicholas@2224
|
3019 // Called when playback has begun
|
nicholas@2224
|
3020 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
3021 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
3022 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
3023 };
|
nicholas@2498
|
3024 this.stopPlayback = function () {
|
nicholas@2224
|
3025 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
3026 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
3027 };
|
nicholas@2498
|
3028 this.exportXMLDOM = function (audioObject) {
|
nicholas@2224
|
3029 return null;
|
nicholas@2224
|
3030 };
|
nicholas@2498
|
3031 this.getValue = function () {
|
nicholas@2224
|
3032 return 0;
|
nicholas@2224
|
3033 };
|
nicholas@2498
|
3034 this.getPresentedId = function () {
|
nicholas@2409
|
3035 return this.parent.specification.label || "Reference";
|
nicholas@2224
|
3036 };
|
nicholas@2498
|
3037 this.canMove = function () {
|
nicholas@2224
|
3038 return false;
|
nicholas@2224
|
3039 };
|
nicholas@2498
|
3040 this.error = function () {
|
nicholas@2498
|
3041 // audioObject has an error!!
|
nicholas@2224
|
3042 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
3043 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2708
|
3044 };
|
nicholas@2708
|
3045 };
|
nicholas@2498
|
3046
|
nicholas@2712
|
3047 this.playhead = (function () {
|
nicholas@2722
|
3048 var playhead = {};
|
nicholas@2712
|
3049 playhead.object = document.createElement('div');
|
nicholas@2712
|
3050 playhead.object.className = 'playhead';
|
nicholas@2712
|
3051 playhead.object.align = 'left';
|
nicholas@2498
|
3052 var curTime = document.createElement('div');
|
nicholas@2498
|
3053 curTime.style.width = '50px';
|
nicholas@2712
|
3054 playhead.curTimeSpan = document.createElement('span');
|
nicholas@2712
|
3055 playhead.curTimeSpan.textContent = '00:00';
|
nicholas@2712
|
3056 curTime.appendChild(playhead.curTimeSpan);
|
nicholas@2712
|
3057 playhead.object.appendChild(curTime);
|
nicholas@2712
|
3058 playhead.scrubberTrack = document.createElement('div');
|
nicholas@2712
|
3059 playhead.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2498
|
3060
|
nicholas@2712
|
3061 playhead.scrubberHead = document.createElement('div');
|
nicholas@2712
|
3062 playhead.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2712
|
3063 playhead.scrubberTrack.appendChild(playhead.scrubberHead);
|
nicholas@2712
|
3064 playhead.object.appendChild(playhead.scrubberTrack);
|
nicholas@2498
|
3065
|
nicholas@2712
|
3066 playhead.timePerPixel = 0;
|
nicholas@2712
|
3067 playhead.maxTime = 0;
|
nicholas@2498
|
3068
|
nicholas@2712
|
3069 playhead.playbackObject = undefined;
|
nicholas@2498
|
3070
|
nicholas@2712
|
3071 playhead.setTimePerPixel = function (audioObject) {
|
nicholas@2498
|
3072 //maxTime must be in seconds
|
nicholas@2498
|
3073 this.playbackObject = audioObject;
|
nicholas@2498
|
3074 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2498
|
3075 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2498
|
3076 this.timePerPixel = this.maxTime / 490;
|
nicholas@2498
|
3077 if (this.maxTime < 60) {
|
nicholas@2498
|
3078 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
3079 } else {
|
nicholas@2498
|
3080 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
3081 }
|
nicholas@2498
|
3082 };
|
nicholas@2498
|
3083
|
nicholas@2712
|
3084 playhead.update = function () {
|
nicholas@2498
|
3085 // Update the playhead position, startPlay must be called
|
nicholas@2498
|
3086 if (this.timePerPixel > 0) {
|
nicholas@2498
|
3087 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2498
|
3088 if (time > 0 && time < this.maxTime) {
|
nicholas@2498
|
3089 var width = 490;
|
nicholas@2498
|
3090 var pix = Math.floor(time / this.timePerPixel);
|
nicholas@2498
|
3091 this.scrubberHead.style.left = pix + 'px';
|
nicholas@2498
|
3092 if (this.maxTime > 60.0) {
|
nicholas@2498
|
3093 var secs = time % 60;
|
nicholas@2498
|
3094 var mins = Math.floor((time - secs) / 60);
|
nicholas@2498
|
3095 secs = secs.toString();
|
nicholas@2498
|
3096 secs = secs.substr(0, 2);
|
nicholas@2498
|
3097 mins = mins.toString();
|
nicholas@2498
|
3098 this.curTimeSpan.textContent = mins + ':' + secs;
|
nicholas@2498
|
3099 } else {
|
nicholas@2498
|
3100 time = time.toString();
|
nicholas@2498
|
3101 this.curTimeSpan.textContent = time.substr(0, 4);
|
nicholas@2498
|
3102 }
|
nicholas@2498
|
3103 } else {
|
nicholas@2498
|
3104 this.scrubberHead.style.left = '0px';
|
nicholas@2498
|
3105 if (this.maxTime < 60) {
|
nicholas@2498
|
3106 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
3107 } else {
|
nicholas@2498
|
3108 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
3109 }
|
nicholas@2498
|
3110 }
|
nicholas@2498
|
3111 }
|
nicholas@2817
|
3112 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
3113 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2817
|
3114 }
|
nicholas@2498
|
3115 };
|
nicholas@2498
|
3116
|
nicholas@2712
|
3117 playhead.interval = undefined;
|
nicholas@2498
|
3118
|
nicholas@2712
|
3119 playhead.start = function () {
|
nicholas@2708
|
3120 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
3121 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2498
|
3122 }
|
nicholas@2498
|
3123 };
|
nicholas@2712
|
3124 playhead.stop = function () {
|
nicholas@2817
|
3125 this.timePerPixel = 0;
|
nicholas@2498
|
3126 };
|
nicholas@2712
|
3127 return playhead;
|
nicholas@2712
|
3128 })();
|
nicholas@2498
|
3129
|
nicholas@2712
|
3130 this.volume = (function () {
|
nicholas@2224
|
3131 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
3132 // Includes trackers on page-by-page data
|
nicholas@2224
|
3133 // Volume does NOT reset to 0dB on each page load
|
nicholas@2712
|
3134 var volume = {};
|
nicholas@2712
|
3135 volume.valueLin = 1.0;
|
nicholas@2712
|
3136 volume.valueDB = 0.0;
|
nicholas@2712
|
3137 volume.root = document.createElement('div');
|
nicholas@2712
|
3138 volume.root.id = 'master-volume-root';
|
nicholas@2712
|
3139 volume.object = document.createElement('div');
|
nicholas@2712
|
3140 volume.object.className = 'master-volume-holder-float';
|
nicholas@2712
|
3141 volume.object.appendChild(volume.root);
|
nicholas@2712
|
3142 volume.slider = document.createElement('input');
|
nicholas@2712
|
3143 volume.slider.id = 'master-volume-control';
|
nicholas@2712
|
3144 volume.slider.type = 'range';
|
nicholas@2712
|
3145 volume.valueText = document.createElement('span');
|
nicholas@2712
|
3146 volume.valueText.id = 'master-volume-feedback';
|
nicholas@2712
|
3147 volume.valueText.textContent = '0dB';
|
nicholas@2498
|
3148
|
nicholas@2712
|
3149 volume.slider.min = -60;
|
nicholas@2712
|
3150 volume.slider.max = 12;
|
nicholas@2712
|
3151 volume.slider.value = 0;
|
nicholas@2712
|
3152 volume.slider.step = 1;
|
nicholas@2712
|
3153 volume.handleEvent = function (event) {
|
nicholas@2951
|
3154 if (event.type == "mousemove" || event.type == "mouseup") {
|
nicholas@2669
|
3155 this.valueDB = Number(this.slider.value);
|
nicholas@2669
|
3156 this.valueLin = decibelToLinear(this.valueDB);
|
nicholas@2669
|
3157 this.valueText.textContent = this.valueDB + 'dB';
|
nicholas@2669
|
3158 audioEngineContext.outputGain.gain.value = this.valueLin;
|
nicholas@2951
|
3159 }
|
nicholas@2951
|
3160 if (event.type == "mouseup") {
|
nicholas@2669
|
3161 this.onmouseup();
|
nicholas@2669
|
3162 }
|
nicholas@2669
|
3163 this.slider.value = this.valueDB;
|
nicholas@2669
|
3164
|
nicholas@2669
|
3165 if (event.stopPropagation) {
|
nicholas@2669
|
3166 event.stopPropagation();
|
nicholas@2669
|
3167 }
|
nicholas@2711
|
3168 };
|
nicholas@2712
|
3169 volume.onmouseup = function () {
|
nicholas@2224
|
3170 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2708
|
3171 if (storePoint.length === 0) {
|
nicholas@2224
|
3172 storePoint = storage.document.createElement('metricresult');
|
nicholas@2498
|
3173 storePoint.setAttribute('name', 'volumeTracker');
|
nicholas@2224
|
3174 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2498
|
3175 } else {
|
nicholas@2224
|
3176 storePoint = storePoint[0];
|
nicholas@2224
|
3177 }
|
nicholas@2224
|
3178 var node = storage.document.createElement('movement');
|
nicholas@2498
|
3179 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
|
nicholas@2669
|
3180 node.setAttribute('volume', this.valueDB);
|
nicholas@2498
|
3181 node.setAttribute('format', 'dBFS');
|
nicholas@2224
|
3182 storePoint.appendChild(node);
|
nicholas@2711
|
3183 };
|
nicholas@2712
|
3184 volume.slider.addEventListener("mousemove", volume);
|
nicholas@2712
|
3185 volume.root.addEventListener("mouseup", volume);
|
nicholas@2498
|
3186
|
nicholas@2224
|
3187 var title = document.createElement('div');
|
nicholas@2224
|
3188 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
3189 title.style.fontSize = '0.75em';
|
nicholas@2224
|
3190 title.style.width = "100%";
|
nicholas@2224
|
3191 title.align = 'center';
|
nicholas@2712
|
3192 volume.root.appendChild(title);
|
nicholas@2498
|
3193
|
nicholas@2712
|
3194 volume.root.appendChild(volume.slider);
|
nicholas@2712
|
3195 volume.root.appendChild(volume.valueText);
|
nicholas@2498
|
3196
|
nicholas@2712
|
3197 volume.resize = function (event) {
|
nicholas@2352
|
3198 if (window.innerWidth < 1000) {
|
nicholas@2708
|
3199 this.object.className = "master-volume-holder-inline";
|
nicholas@2352
|
3200 } else {
|
nicholas@2352
|
3201 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
3202 }
|
nicholas@2708
|
3203 };
|
nicholas@2712
|
3204 return volume;
|
nicholas@2712
|
3205 })();
|
nicholas@2498
|
3206
|
nicholas@2778
|
3207 this.imageHolder = (function () {
|
nicholas@2778
|
3208 var imageController = {};
|
nicholas@2778
|
3209 imageController.root = document.createElement("div");
|
nicholas@2778
|
3210 imageController.root.id = "imageController";
|
nicholas@2778
|
3211 imageController.img = document.createElement("img");
|
nicholas@2778
|
3212 imageController.root.appendChild(imageController.img);
|
nicholas@2778
|
3213 imageController.setImage = function (src) {
|
nicholas@2778
|
3214 imageController.img.src = "";
|
n@2785
|
3215 if (typeof src !== "string" || src.length === undefined) {
|
nicholas@2778
|
3216 return;
|
nicholas@2778
|
3217 }
|
nicholas@2778
|
3218 imageController.img.src = src;
|
n@2785
|
3219 };
|
nicholas@2778
|
3220 return imageController;
|
nicholas@2778
|
3221 })();
|
nicholas@2778
|
3222
|
nicholas@3101
|
3223 this.calibrationTests = (function () {
|
nicholas@3101
|
3224 function readonly(t) {
|
nicholas@3101
|
3225 throw ("Cannot set read-only variable");
|
nicholas@3101
|
3226 }
|
nicholas@3101
|
3227
|
nicholas@3101
|
3228 function getStorageRoot() {
|
nicholas@3101
|
3229 var storageRoot = storage.root.querySelector("calibration");
|
nicholas@3101
|
3230 if (storageRoot === undefined) {
|
nicholas@3101
|
3231 storageRoot = storage.document.createElement("calibration");
|
nicholas@3101
|
3232 storage.root.appendChild(storageRoot);
|
nicholas@3101
|
3233 }
|
nicholas@3101
|
3234 return storageRoot;
|
nicholas@3101
|
3235 }
|
n@3102
|
3236 var calibrationObject,
|
nicholas@3101
|
3237 _checkedFrequency = false,
|
nicholas@3101
|
3238 _checkedChannels = false;
|
nicholas@3101
|
3239
|
nicholas@3101
|
3240 // Define the checkFrequencies test!
|
nicholas@3101
|
3241 var checkFrequencyUnit = function (htmlRoot, storageRoot) {
|
nicholas@3101
|
3242
|
nicholas@3101
|
3243 function createFrequencyElement(frequency) {
|
nicholas@3101
|
3244 return (function (frequency) {
|
n@3102
|
3245 var hold = document.createElement("div");
|
n@3102
|
3246 hold.className = "calibration-slider";
|
nicholas@3101
|
3247 var range = document.createElement("input");
|
nicholas@3101
|
3248 range.type = "range";
|
nicholas@3101
|
3249 range.min = "-24";
|
nicholas@3101
|
3250 range.max = "24";
|
nicholas@3101
|
3251 range.step = "0.5";
|
nicholas@3101
|
3252 range.setAttribute("orient", "vertical");
|
nicholas@3101
|
3253 range.value = (Math.random() - 0.5) * 24;
|
nicholas@3101
|
3254 range.setAttribute("frequency", frequency);
|
n@3102
|
3255 hold.appendChild(range);
|
n@3102
|
3256 htmlRoot.appendChild(hold);
|
nicholas@3101
|
3257
|
nicholas@3101
|
3258 var gain = audioContext.createGain();
|
nicholas@3101
|
3259 gain.connect(outputGain);
|
nicholas@3101
|
3260 gain.gain.value = Math.pow(10, Number(range.value) / 20.0);
|
n@3102
|
3261 var osc;
|
nicholas@3101
|
3262
|
nicholas@3101
|
3263 var store = storage.document.createElement("response");
|
nicholas@3101
|
3264 store.setAttribute("frequency", frequency);
|
nicholas@3101
|
3265 storageHook.appendChild(store);
|
nicholas@3101
|
3266 var interface = {};
|
nicholas@3101
|
3267 Object.defineProperties(interface, {
|
nicholas@3101
|
3268 "handleEvent": {
|
nicholas@3101
|
3269 "value": function (e) {
|
nicholas@3101
|
3270 if (e.type == "mouseenter") {
|
nicholas@3101
|
3271 osc = audioContext.createOscillator();
|
nicholas@3101
|
3272 osc.frequency.value = frequency;
|
nicholas@3101
|
3273 osc.connect(gain);
|
nicholas@3101
|
3274 osc.start();
|
nicholas@3101
|
3275 console.log("start " + frequency);
|
nicholas@3101
|
3276 } else if (e.type == "mouseleave") {
|
nicholas@3101
|
3277 console.log("stop " + frequency);
|
nicholas@3101
|
3278 osc.stop();
|
nicholas@3101
|
3279 osc = undefined;
|
nicholas@2224
|
3280 }
|
nicholas@3101
|
3281 store.textContent = e.currentTarget.value;
|
nicholas@3101
|
3282 gain.gain.value = Math.pow(10, Number(e.currentTarget.value) / 20.0);
|
nicholas@3101
|
3283 }
|
nicholas@2224
|
3284 }
|
nicholas@3101
|
3285 });
|
nicholas@3101
|
3286 range.addEventListener("mousemove", interface);
|
nicholas@3101
|
3287 range.addEventListener("mouseenter", interface);
|
nicholas@3101
|
3288 range.addEventListener("mouseleave", interface);
|
nicholas@3101
|
3289 return interface;
|
nicholas@3101
|
3290 })(frequency);
|
nicholas@3101
|
3291 }
|
nicholas@3101
|
3292 var htmlHook = document.createElement("div");
|
nicholas@3101
|
3293 htmlRoot.appendChild(htmlHook);
|
nicholas@3101
|
3294 var storageHook = storage.document.createElement("frequency");
|
nicholas@3101
|
3295 storageRoot.appendChild(storageHook);
|
nicholas@3101
|
3296 var frequencies = [100, 200, 400, 800, 1200, 1600, 2000, 4000, 8000, 12000];
|
nicholas@3101
|
3297 var outputGain = audioContext.createGain();
|
nicholas@3101
|
3298 outputGain.gain.value = 0.25;
|
nicholas@3101
|
3299 outputGain.connect(audioContext.destination);
|
nicholas@3101
|
3300 this.sliders = frequencies.map(createFrequencyElement);
|
n@3102
|
3301 };
|
nicholas@3101
|
3302
|
nicholas@3101
|
3303 var checkChannelsUnit = function (htmlRoot, storageRoot) {
|
nicholas@3101
|
3304
|
nicholas@3101
|
3305 function onclick(ev) {
|
nicholas@3101
|
3306 var storageHook = storage.document.querySelector("calibration").querySelector("channels");
|
nicholas@3101
|
3307 storageHook.setAttribute("selected", ev.currentTarget.value);
|
nicholas@3101
|
3308 storageHook.setAttribute("selectedText", ev.currentTarget.textContent);
|
nicholas@3101
|
3309 osc.stop();
|
nicholas@3101
|
3310 gainL = undefined;
|
nicholas@3101
|
3311 gainR = undefined;
|
nicholas@3101
|
3312 cmerge = undefined;
|
nicholas@3101
|
3313 popup.proceedClicked();
|
nicholas@3101
|
3314 }
|
nicholas@3101
|
3315 var osc = audioContext.createOscillator();
|
nicholas@3101
|
3316 var gainL = audioContext.createGain();
|
nicholas@3101
|
3317 var gainR = audioContext.createGain();
|
nicholas@3101
|
3318 gainL.channelCount = 1;
|
nicholas@3101
|
3319 gainR.channelCount = 1;
|
nicholas@3101
|
3320 var cmerge = audioContext.createChannelMerger(2);
|
nicholas@3101
|
3321 osc.connect(gainL, 0, 0);
|
nicholas@3101
|
3322 osc.connect(gainR, 0, 0);
|
nicholas@3101
|
3323 gainL.connect(cmerge, 0, 0);
|
nicholas@3101
|
3324 gainR.connect(cmerge, 0, 1);
|
nicholas@3101
|
3325 cmerge.connect(audioContext.destination);
|
nicholas@3101
|
3326 var play = document.createElement("button");
|
nicholas@3101
|
3327 play.textContent = "Play Audio";
|
nicholas@3101
|
3328 play.onclick = function () {
|
nicholas@3101
|
3329 osc.start();
|
nicholas@3101
|
3330 play.disabled = true;
|
n@3102
|
3331 };
|
n@3102
|
3332 play.className = "calibration-button";
|
nicholas@3101
|
3333 htmlRoot.appendChild(play);
|
nicholas@3101
|
3334 var choiceHolder = document.createElement("div");
|
nicholas@3101
|
3335 var leftButton = document.createElement("button");
|
nicholas@3101
|
3336 leftButton.textContent = "Left";
|
nicholas@3101
|
3337 leftButton.value = "-1";
|
n@3102
|
3338 leftButton.className = "calibration-button";
|
nicholas@3101
|
3339 var centerButton = document.createElement("button");
|
nicholas@3101
|
3340 centerButton.textContent = "Middle";
|
nicholas@3101
|
3341 centerButton.value = "0";
|
n@3102
|
3342 centerButton.className = "calibration-button";
|
nicholas@3101
|
3343 var rightButton = document.createElement("button");
|
nicholas@3101
|
3344 rightButton.textContent = "Right";
|
nicholas@3101
|
3345 rightButton.value = "1";
|
n@3102
|
3346 rightButton.className = "calibration-button";
|
nicholas@3101
|
3347 choiceHolder.appendChild(leftButton);
|
nicholas@3101
|
3348 choiceHolder.appendChild(centerButton);
|
nicholas@3101
|
3349 choiceHolder.appendChild(rightButton);
|
nicholas@3101
|
3350 htmlRoot.appendChild(choiceHolder);
|
nicholas@3101
|
3351 leftButton.addEventListener("click", onclick);
|
nicholas@3101
|
3352 centerButton.addEventListener("click", onclick);
|
nicholas@3101
|
3353 rightButton.addEventListener("click", onclick);
|
nicholas@3101
|
3354
|
nicholas@3101
|
3355 var storageHook = storage.document.createElement("channels");
|
nicholas@3101
|
3356 storageRoot.appendChild(storageHook);
|
nicholas@3101
|
3357
|
nicholas@3101
|
3358 var pan;
|
nicholas@3101
|
3359 if (Math.random() > 0.5) {
|
nicholas@3101
|
3360 pan = 1;
|
nicholas@3101
|
3361 gainL.gain.value = 0.0;
|
nicholas@3101
|
3362 gainR.gain.value = 0.25;
|
nicholas@3101
|
3363 storageHook.setAttribute("presented", pan);
|
nicholas@3101
|
3364 storageHook.setAttribute("presentedText", "Right");
|
nicholas@3101
|
3365 } else {
|
nicholas@3101
|
3366 pan = -1;
|
nicholas@3101
|
3367 gainL.gain.value = 0.25;
|
nicholas@3101
|
3368 gainR.gain.value = 0.0;
|
nicholas@3101
|
3369 storageHook.setAttribute("presented", pan);
|
nicholas@3101
|
3370 storageHook.setAttribute("presentedText", "Left");
|
nicholas@3101
|
3371 }
|
n@3102
|
3372 };
|
nicholas@3101
|
3373
|
nicholas@3101
|
3374 var interface = {};
|
nicholas@3101
|
3375 Object.defineProperties(interface, {
|
nicholas@3101
|
3376 "calibrationObject": {
|
nicholas@3101
|
3377 "get": function () {
|
n@3102
|
3378 return calibrationObject;
|
nicholas@3101
|
3379 },
|
nicholas@3101
|
3380 "set": readonly
|
nicholas@3101
|
3381 },
|
nicholas@3101
|
3382 "checkFrequencies": {
|
nicholas@3101
|
3383 "get": function () {
|
nicholas@3101
|
3384 if (specification.calibration.checkFrequencies && _checkedFrequency === false) {
|
nicholas@3101
|
3385 return true;
|
nicholas@2224
|
3386 }
|
nicholas@3101
|
3387 return false;
|
nicholas@3101
|
3388 },
|
nicholas@3101
|
3389 "set": readonly
|
nicholas@3101
|
3390 },
|
nicholas@3101
|
3391 "checkChannels": {
|
nicholas@3101
|
3392 "get": function () {
|
nicholas@3101
|
3393 if (specification.calibration.checkChannels && _checkedChannels === false) {
|
nicholas@3101
|
3394 return true;
|
nicholas@3101
|
3395 }
|
nicholas@3101
|
3396 return false;
|
nicholas@3101
|
3397 },
|
nicholas@3101
|
3398 "set": readonly
|
nicholas@3101
|
3399 },
|
nicholas@3101
|
3400 "performFrequencyCheck": {
|
nicholas@3101
|
3401 "value": function (htmlRoot) {
|
nicholas@3101
|
3402 htmlRoot.innerHTML = "";
|
nicholas@3101
|
3403 calibrationObject = new checkFrequencyUnit(htmlRoot, getStorageRoot());
|
nicholas@3101
|
3404 _checkedFrequency = true;
|
nicholas@2224
|
3405 }
|
nicholas@3101
|
3406 },
|
nicholas@3101
|
3407 "performChannelCheck": {
|
nicholas@3101
|
3408 "value": function (htmlRoot) {
|
nicholas@3101
|
3409 htmlRoot.innerHTML = "";
|
nicholas@3101
|
3410 calibrationObject = new checkChannelsUnit(htmlRoot, getStorageRoot());
|
nicholas@3101
|
3411 _checkedChannels = true;
|
nicholas@3101
|
3412 }
|
nicholas@2224
|
3413 }
|
n@3102
|
3414 });
|
nicholas@3101
|
3415 return interface;
|
nicholas@3101
|
3416 })();
|
nicholas@2498
|
3417
|
nicholas@2498
|
3418
|
nicholas@2498
|
3419 // Global Checkers
|
nicholas@2498
|
3420 // These functions will help enforce the checkers
|
n@2789
|
3421 this.checkHiddenAnchor = function (message) {
|
nicholas@2708
|
3422 var anchors = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3423 return ao.specification.type === "anchor";
|
nicholas@2708
|
3424 });
|
nicholas@2708
|
3425 var state = anchors.some(function (ao) {
|
nicholas@2708
|
3426 return (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3427 });
|
nicholas@2708
|
3428 if (state) {
|
nicholas@2708
|
3429 console.log('Anchor node not below marker value');
|
n@2789
|
3430 if (message) {
|
n@2789
|
3431 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3432 } else {
|
n@2789
|
3433 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3434 }
|
nicholas@2708
|
3435 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2708
|
3436 return false;
|
nicholas@2498
|
3437 }
|
nicholas@2498
|
3438 return true;
|
nicholas@2498
|
3439 };
|
nicholas@2498
|
3440
|
n@2789
|
3441 this.checkHiddenReference = function (message) {
|
nicholas@2708
|
3442 var references = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3443 return ao.specification.type === "reference";
|
nicholas@2708
|
3444 });
|
nicholas@2708
|
3445 var state = references.some(function (ao) {
|
nicholas@2708
|
3446 return (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3447 });
|
nicholas@2708
|
3448 if (state) {
|
nicholas@2708
|
3449 console.log('Reference node not below marker value');
|
n@2789
|
3450 if (message) {
|
n@2789
|
3451 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3452 } else {
|
n@2789
|
3453 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3454 }
|
nicholas@2708
|
3455 this.storeErrorNode('Reference node not below marker value');
|
nicholas@2708
|
3456 return false;
|
nicholas@2498
|
3457 }
|
nicholas@2498
|
3458 return true;
|
nicholas@2498
|
3459 };
|
nicholas@2498
|
3460
|
n@2789
|
3461 this.checkFragmentsFullyPlayed = function (message) {
|
nicholas@2498
|
3462 // Checks the entire file has been played back
|
nicholas@2498
|
3463 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2498
|
3464 if (audioEngineContext.loopPlayback) {
|
nicholas@2498
|
3465 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2498
|
3466 return true;
|
nicholas@2498
|
3467 }
|
nicholas@2498
|
3468 var check_pass = true;
|
nicholas@2708
|
3469 var error_obj = [],
|
nicholas@2708
|
3470 i;
|
nicholas@2708
|
3471 for (i = 0; i < audioEngineContext.audioObjects.length; i++) {
|
nicholas@2498
|
3472 var object = audioEngineContext.audioObjects[i];
|
nicholas@2498
|
3473 var time = object.buffer.buffer.duration;
|
nicholas@2498
|
3474 var metric = object.metric;
|
nicholas@2498
|
3475 var passed = false;
|
nicholas@2498
|
3476 for (var j = 0; j < metric.listenTracker.length; j++) {
|
nicholas@2498
|
3477 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
|
nicholas@2498
|
3478 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2498
|
3479 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2498
|
3480 var delta = stop_time - start_time;
|
nicholas@2498
|
3481 if (delta >= time) {
|
nicholas@2498
|
3482 passed = true;
|
nicholas@2498
|
3483 break;
|
nicholas@2498
|
3484 }
|
nicholas@2498
|
3485 }
|
nicholas@2708
|
3486 if (passed === false) {
|
nicholas@2498
|
3487 check_pass = false;
|
nicholas@2498
|
3488 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3489 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3490 }
|
nicholas@2498
|
3491 }
|
nicholas@2708
|
3492 if (check_pass === false) {
|
nicholas@2498
|
3493 var str_start = "You have not completely listened to fragments ";
|
nicholas@2708
|
3494 for (i = 0; i < error_obj.length; i++) {
|
nicholas@2498
|
3495 str_start += error_obj[i];
|
nicholas@2498
|
3496 if (i != error_obj.length - 1) {
|
nicholas@2498
|
3497 str_start += ', ';
|
nicholas@2498
|
3498 }
|
nicholas@2498
|
3499 }
|
nicholas@2498
|
3500 str_start += ". Please keep listening";
|
n@2789
|
3501 console.log(str_start);
|
n@2789
|
3502 this.storeErrorNode(str_start);
|
n@2789
|
3503 if (message) {
|
n@2789
|
3504 str_start = message;
|
n@2789
|
3505 }
|
nicholas@2498
|
3506 interfaceContext.lightbox.post("Error", str_start);
|
nicholas@2444
|
3507 return false;
|
nicholas@2498
|
3508 }
|
nicholas@2444
|
3509 return true;
|
nicholas@2498
|
3510 };
|
n@2789
|
3511 this.checkAllMoved = function (message) {
|
nicholas@2498
|
3512 var str = "You have not moved ";
|
nicholas@2498
|
3513 var failed = [];
|
nicholas@2708
|
3514 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3515 if (ao.metric.wasMoved === false && ao.interfaceDOM.canMove() === true) {
|
nicholas@2498
|
3516 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3517 }
|
nicholas@2708
|
3518 }, this);
|
nicholas@2708
|
3519 if (failed.length === 0) {
|
nicholas@2498
|
3520 return true;
|
nicholas@2498
|
3521 } else if (failed.length == 1) {
|
nicholas@2498
|
3522 str += 'track ' + failed[0];
|
nicholas@2498
|
3523 } else {
|
nicholas@2498
|
3524 str += 'tracks ';
|
nicholas@2498
|
3525 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3526 str += failed[i] + ', ';
|
nicholas@2498
|
3527 }
|
nicholas@2498
|
3528 str += 'and ' + failed[i];
|
nicholas@2498
|
3529 }
|
nicholas@2498
|
3530 str += '.';
|
nicholas@2498
|
3531 console.log(str);
|
nicholas@2224
|
3532 this.storeErrorNode(str);
|
n@2789
|
3533 if (message) {
|
n@2789
|
3534 str = message;
|
n@2789
|
3535 }
|
n@2789
|
3536 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3537 return false;
|
nicholas@2498
|
3538 };
|
n@2789
|
3539 this.checkAllPlayed = function (message) {
|
nicholas@2498
|
3540 var str = "You have not played ";
|
nicholas@2498
|
3541 var failed = [];
|
nicholas@2708
|
3542 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3543 if (ao.metric.wasListenedTo === false) {
|
nicholas@2498
|
3544 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3545 }
|
nicholas@2708
|
3546 }, this);
|
nicholas@2708
|
3547 if (failed.length === 0) {
|
nicholas@2498
|
3548 return true;
|
nicholas@2498
|
3549 } else if (failed.length == 1) {
|
nicholas@2498
|
3550 str += 'track ' + failed[0];
|
nicholas@2498
|
3551 } else {
|
nicholas@2498
|
3552 str += 'tracks ';
|
nicholas@2498
|
3553 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3554 str += failed[i] + ', ';
|
nicholas@2498
|
3555 }
|
nicholas@2498
|
3556 str += 'and ' + failed[i];
|
nicholas@2498
|
3557 }
|
nicholas@2498
|
3558 str += '.';
|
nicholas@2498
|
3559 console.log(str);
|
nicholas@2224
|
3560 this.storeErrorNode(str);
|
n@2789
|
3561 if (message) {
|
n@2789
|
3562 str = message;
|
n@2789
|
3563 }
|
n@2789
|
3564 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3565 return false;
|
nicholas@2498
|
3566 };
|
n@2789
|
3567 this.checkAllCommented = function (message) {
|
nicholas@2540
|
3568 var str = "You have not commented on all the fragments.";
|
nicholas@2540
|
3569 var cont = true,
|
nicholas@2540
|
3570 boxes = this.commentBoxes.boxes,
|
nicholas@2540
|
3571 numBoxes = boxes.length,
|
nicholas@2540
|
3572 i;
|
nicholas@2540
|
3573 for (i = 0; i < numBoxes; i++) {
|
nicholas@2540
|
3574 if (boxes[i].trackCommentBox.value === "") {
|
nicholas@2540
|
3575 console.log(str);
|
nicholas@2540
|
3576 this.storeErrorNode(str);
|
n@2789
|
3577 if (message) {
|
n@2789
|
3578 str = message;
|
n@2789
|
3579 }
|
n@2789
|
3580 interfaceContext.lightbox.post("Error", str);
|
nicholas@2540
|
3581 return false;
|
nicholas@2540
|
3582 }
|
nicholas@2540
|
3583 }
|
nicholas@2540
|
3584 return true;
|
nicholas@2708
|
3585 };
|
n@2789
|
3586 this.checkScaleRange = function (message) {
|
nicholas@2310
|
3587 var page = testState.getCurrentTestPage();
|
nicholas@2708
|
3588 var interfaceObject = page.interfaces;
|
nicholas@2310
|
3589 var state = true;
|
nicholas@2310
|
3590 var str = "Please keep listening. ";
|
nicholas@2708
|
3591 if (interfaceObject === undefined) {
|
nicholas@2708
|
3592 return true;
|
nicholas@2310
|
3593 }
|
nicholas@2708
|
3594 interfaceObject = interfaceObject[0];
|
nicholas@2708
|
3595 var scales = (function () {
|
nicholas@2708
|
3596 var scaleRange = interfaceObject.options.find(function (a) {
|
nicholas@2708
|
3597 return a.name == "scalerange";
|
nicholas@2708
|
3598 });
|
nicholas@2708
|
3599 return {
|
nicholas@2708
|
3600 min: scaleRange.min,
|
nicholas@2708
|
3601 max: scaleRange.max
|
nicholas@2708
|
3602 };
|
nicholas@2708
|
3603 })();
|
nicholas@2708
|
3604 var range = audioEngineContext.audioObjects.reduce(function (a, b) {
|
nicholas@2742
|
3605 var v = b.interfaceDOM.getValue() * 100.0;
|
nicholas@2708
|
3606 return {
|
nicholas@2708
|
3607 min: Math.min(a.min, v),
|
nicholas@2708
|
3608 max: Math.max(a.max, v)
|
nicholas@2712
|
3609 };
|
nicholas@2708
|
3610 }, {
|
nicholas@2708
|
3611 min: 100,
|
nicholas@2708
|
3612 max: 0
|
nicholas@2708
|
3613 });
|
nicholas@2708
|
3614 if (range.min > scales.min) {
|
nicholas@2742
|
3615 str += "At least one fragment must be below the " + scales.min + " mark.";
|
nicholas@2708
|
3616 state = false;
|
nicholas@2712
|
3617 } else if (range.max < scales.max) {
|
nicholas@2742
|
3618 str += "At least one fragment must be above the " + scales.max + " mark.";
|
nicholas@2310
|
3619 state = false;
|
nicholas@2310
|
3620 }
|
nicholas@2708
|
3621 if (state === false) {
|
nicholas@2310
|
3622 console.log(str);
|
nicholas@2310
|
3623 this.storeErrorNode(str);
|
n@2789
|
3624 if (message) {
|
n@2789
|
3625 str = message;
|
n@2789
|
3626 }
|
nicholas@2498
|
3627 interfaceContext.lightbox.post("Error", str);
|
nicholas@2310
|
3628 }
|
nicholas@2310
|
3629 return state;
|
nicholas@2708
|
3630 };
|
nicholas@2826
|
3631 this.checkFragmentMinPlays = function () {
|
nicholas@2826
|
3632 var failedObjects = audioEngineContext.audioObjects.filter(function (a) {
|
nicholas@2826
|
3633 var minPlays = a.specification.minNumberPlays || a.specification.parent.minNumberPlays || specification.minNumberPlays;
|
nicholas@2826
|
3634 if (minPlays === undefined || a.numberOfPlays >= minPlays) {
|
nicholas@2826
|
3635 return false;
|
nicholas@2826
|
3636 }
|
nicholas@2826
|
3637 return true;
|
nicholas@2826
|
3638 });
|
nicholas@2826
|
3639 if (failedObjects.length === 0) {
|
nicholas@2827
|
3640 return true;
|
nicholas@2826
|
3641 }
|
nicholas@2826
|
3642 var failedString = [];
|
nicholas@2826
|
3643 failedObjects.forEach(function (a) {
|
nicholas@2826
|
3644 failedString.push(a.interfaceDOM.getPresentedId());
|
nicholas@2826
|
3645 });
|
nicholas@2826
|
3646 var str = "You have not played fragments " + failedString.join(", ") + " enough. Please keep listening";
|
nicholas@2826
|
3647 interfaceContext.lightbox.post("Message", str);
|
nicholas@2826
|
3648 this.storeErrorNode(str);
|
nicholas@2827
|
3649 return false;
|
nicholas@2826
|
3650 };
|
nicholas@2826
|
3651
|
nicholas@2498
|
3652
|
nicholas@2849
|
3653 this.sortFragmentsByScore = function () {
|
nicholas@2849
|
3654 var elements = audioEngineContext.audioObjects.filter(function (elem) {
|
nicholas@2849
|
3655 return elem.specification.type !== "outside-reference";
|
nicholas@2849
|
3656 });
|
nicholas@2849
|
3657 var indexes = [];
|
nicholas@2849
|
3658 var i = 0;
|
nicholas@2849
|
3659 while (indexes.push(i++) < elements.length);
|
nicholas@2849
|
3660 return indexes.sort(function (x, y) {
|
nicholas@2849
|
3661 var a = elements[x].interfaceDOM.getValue();
|
nicholas@2849
|
3662 var b = elements[y].interfaceDOM.getValue();
|
nicholas@2849
|
3663 if (a > b) {
|
nicholas@2849
|
3664 return 1;
|
nicholas@2849
|
3665 } else if (a < b) {
|
nicholas@2849
|
3666 return -1;
|
nicholas@2849
|
3667 }
|
nicholas@2849
|
3668 return 0;
|
nicholas@2849
|
3669 }, elements[0].interfaceDOM.getValue());
|
nicholas@2849
|
3670 };
|
nicholas@2849
|
3671
|
nicholas@2498
|
3672 this.storeErrorNode = function (errorMessage) {
|
nicholas@2224
|
3673 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
3674 var node = storage.document.createElement('error');
|
nicholas@2498
|
3675 node.setAttribute('time', time);
|
nicholas@2224
|
3676 node.textContent = errorMessage;
|
nicholas@2224
|
3677 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
3678 };
|
nicholas@2595
|
3679
|
nicholas@2595
|
3680 this.getLabel = function (labelType, index, labelStart) {
|
nicholas@2595
|
3681 /*
|
nicholas@2595
|
3682 Get the correct label based on type, index and offset
|
nicholas@2595
|
3683 */
|
nicholas@2595
|
3684
|
nicholas@2595
|
3685 function calculateLabel(labelType, index, offset) {
|
nicholas@2595
|
3686 if (labelType == "none") {
|
nicholas@2595
|
3687 return "";
|
nicholas@2595
|
3688 }
|
nicholas@2595
|
3689 switch (labelType) {
|
nicholas@2595
|
3690 case "letter":
|
nicholas@2596
|
3691 return String.fromCharCode((index + offset) % 26 + 97);
|
nicholas@2595
|
3692 case "capital":
|
nicholas@2607
|
3693 return String.fromCharCode((index + offset) % 26 + 65);
|
nicholas@2625
|
3694 case "samediff":
|
nicholas@2708
|
3695 if (index === 0) {
|
nicholas@2625
|
3696 return "Same";
|
nicholas@2625
|
3697 } else if (index == 1) {
|
nicholas@2625
|
3698 return "Difference";
|
nicholas@2625
|
3699 }
|
nicholas@2708
|
3700 return "";
|
nicholas@2595
|
3701 case "number":
|
nicholas@2595
|
3702 return String(index + offset);
|
nicholas@2595
|
3703 default:
|
nicholas@2595
|
3704 return "";
|
nicholas@2595
|
3705 }
|
nicholas@2595
|
3706 }
|
nicholas@2595
|
3707
|
nicholas@2708
|
3708 if (typeof labelStart !== "string" || labelStart.length === 0) {
|
nicholas@2595
|
3709 labelStart = String.fromCharCode(0);
|
nicholas@2595
|
3710 }
|
nicholas@2595
|
3711
|
nicholas@2595
|
3712 switch (labelType) {
|
nicholas@2595
|
3713 case "letter":
|
nicholas@2595
|
3714 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3715 if (labelStart < 97 || labelStart > 122) {
|
nicholas@2595
|
3716 labelStart = 97;
|
nicholas@2595
|
3717 }
|
nicholas@2595
|
3718 labelStart -= 97;
|
nicholas@2595
|
3719 break;
|
nicholas@2595
|
3720 case "capital":
|
nicholas@2595
|
3721 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3722 if (labelStart < 65 || labelStart > 90) {
|
nicholas@2595
|
3723 labelStart = 65;
|
nicholas@2595
|
3724 }
|
nicholas@2595
|
3725 labelStart -= 65;
|
nicholas@2595
|
3726 break;
|
nicholas@2595
|
3727 case "number":
|
nicholas@2608
|
3728 labelStart = Number(labelStart);
|
nicholas@2608
|
3729 if (!isFinite(labelStart)) {
|
nicholas@2595
|
3730 labelStart = 1;
|
nicholas@2595
|
3731 }
|
nicholas@2595
|
3732 break;
|
nicholas@2595
|
3733 default:
|
nicholas@2596
|
3734 labelStart = 0;
|
nicholas@2595
|
3735 }
|
nicholas@2595
|
3736 if (typeof index == "number") {
|
nicholas@2595
|
3737 return calculateLabel(labelType, index, labelStart);
|
nicholas@2595
|
3738 } else if (index.length && index.length > 0) {
|
nicholas@2595
|
3739 var a = [],
|
nicholas@2595
|
3740 l = index.length,
|
nicholas@2595
|
3741 i;
|
nicholas@2595
|
3742 for (i = 0; i < l; i++) {
|
nicholas@2595
|
3743 a[i] = calculateLabel(labelType, index[i], labelStart);
|
nicholas@2595
|
3744 }
|
nicholas@2595
|
3745 return a;
|
nicholas@2595
|
3746 } else {
|
nicholas@2595
|
3747 throw ("Invalid arguments");
|
nicholas@2595
|
3748 }
|
nicholas@2708
|
3749 };
|
nicholas@2649
|
3750
|
nicholas@2649
|
3751 this.getCombinedInterfaces = function (page) {
|
nicholas@2649
|
3752 // Combine the interfaces with the global interface nodes
|
nicholas@2649
|
3753 var global = specification.interfaces,
|
nicholas@2649
|
3754 local = page.interfaces;
|
nicholas@2649
|
3755 local.forEach(function (locInt) {
|
nicholas@2649
|
3756 // Iterate through the options nodes
|
nicholas@2649
|
3757 var addList = [];
|
nicholas@2649
|
3758 global.options.forEach(function (gopt) {
|
nicholas@2649
|
3759 var lopt = locInt.options.find(function (lopt) {
|
nicholas@2649
|
3760 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
|
nicholas@2649
|
3761 });
|
nicholas@2649
|
3762 if (!lopt) {
|
nicholas@2649
|
3763 // Global option doesn't exist locally
|
nicholas@2649
|
3764 addList.push(gopt);
|
nicholas@2649
|
3765 }
|
nicholas@2649
|
3766 });
|
nicholas@2649
|
3767 locInt.options = locInt.options.concat(addList);
|
nicholas@2649
|
3768 if (!locInt.scales && global.scales) {
|
nicholas@2649
|
3769 // Use the global default scales
|
nicholas@2649
|
3770 locInt.scales = global.scales;
|
nicholas@2649
|
3771 }
|
nicholas@2649
|
3772 });
|
nicholas@2649
|
3773 return local;
|
nicholas@2708
|
3774 };
|
nicholas@2224
|
3775 }
|
nicholas@2224
|
3776
|
nicholas@2498
|
3777 function Storage() {
|
nicholas@2498
|
3778 // Holds results in XML format until ready for collection
|
nicholas@2498
|
3779 this.globalPreTest = null;
|
nicholas@2498
|
3780 this.globalPostTest = null;
|
nicholas@2498
|
3781 this.testPages = [];
|
nicholas@2498
|
3782 this.document = null;
|
nicholas@2498
|
3783 this.root = null;
|
nicholas@2498
|
3784 this.state = 0;
|
nicholas@2733
|
3785 var pFilenamePrefix = "save";
|
nicholas@2498
|
3786
|
nicholas@2498
|
3787 this.initialise = function (existingStore) {
|
nicholas@2708
|
3788 if (existingStore === undefined) {
|
nicholas@2224
|
3789 // We need to get the sessionKey
|
nicholas@2510
|
3790 this.SessionKey.requestKey();
|
nicholas@2498
|
3791 this.document = document.implementation.createDocument(null, "waetresult", null);
|
nicholas@2224
|
3792 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3793 var projectDocument = specification.projectXML;
|
nicholas@2708
|
3794 projectDocument.setAttribute('file-name', specification.url);
|
nicholas@2708
|
3795 projectDocument.setAttribute('url', qualifyURL(specification.url));
|
nicholas@2224
|
3796 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3797 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3798 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3799 } else {
|
nicholas@2224
|
3800 this.document = existingStore;
|
nicholas@2294
|
3801 this.root = existingStore.firstChild;
|
nicholas@2224
|
3802 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3803 }
|
nicholas@2708
|
3804 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3805 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
|
nicholas@2498
|
3806 }
|
nicholas@2708
|
3807 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3808 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
|
nicholas@2498
|
3809 }
|
nicholas@2498
|
3810 };
|
nicholas@2498
|
3811
|
n@2967
|
3812 this.SessionKey = (function (parent) {
|
n@2970
|
3813 var returnURL = "";
|
n@2970
|
3814 if (window.returnURL !== undefined) {
|
n@2970
|
3815 returnURL = String(window.returnURL);
|
n@2970
|
3816 }
|
n@2970
|
3817
|
n@2967
|
3818 function postUpdate() {
|
n@2967
|
3819 // Return a new promise.
|
n@2967
|
3820 var hold = document.createElement("div");
|
n@2967
|
3821 var clone = parent.root.cloneNode(true);
|
n@2967
|
3822 hold.appendChild(clone);
|
n@2967
|
3823 return new Promise(function (resolve, reject) {
|
n@2967
|
3824 // Do the usual XHR stuff
|
n@2977
|
3825 console.log("Requested save...");
|
n@2967
|
3826 var req = new XMLHttpRequest();
|
n@2973
|
3827 req.open("POST", returnURL + "php/save.php?key=" + sessionKey + "&saveFilenamePrefix=" + parent.filenamePrefix);
|
n@2967
|
3828 req.setRequestHeader('Content-Type', 'text/xml');
|
n@2967
|
3829
|
n@2967
|
3830 req.onload = function () {
|
n@2967
|
3831 // This is called even on 404 etc
|
n@2967
|
3832 // so check the status
|
n@2967
|
3833 if (this.status >= 300) {
|
n@2967
|
3834 console.log("WARNING - Could not update at this time");
|
n@2967
|
3835 } else {
|
n@2967
|
3836 var parser = new DOMParser();
|
n@2974
|
3837 var xmlDoc = parser.parseFromString(req.responseText, "application/xml");
|
n@2967
|
3838 var response = xmlDoc.getElementsByTagName('response')[0];
|
n@2967
|
3839 if (response.getAttribute("state") == "OK") {
|
n@2967
|
3840 var file = response.getElementsByTagName("file")[0];
|
n@2967
|
3841 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
n@2967
|
3842 resolve(true);
|
n@2967
|
3843 } else {
|
n@2967
|
3844 var message = response.getElementsByTagName("message");
|
n@2967
|
3845 console.log("Intermediate save: Error! " + message.textContent);
|
n@2967
|
3846 reject("Intermediate save: Error! " + message.textContent);
|
n@2967
|
3847 }
|
n@2967
|
3848 }
|
n@2967
|
3849 };
|
n@2967
|
3850
|
n@2967
|
3851 // Handle network errors
|
n@2967
|
3852 req.onerror = function () {
|
n@2967
|
3853 reject(Error("Network Error"));
|
n@2967
|
3854 };
|
n@2967
|
3855
|
n@2967
|
3856 // Make the request
|
n@2967
|
3857 req.send([hold.innerHTML]);
|
n@2967
|
3858 });
|
n@2979
|
3859 }
|
n@2967
|
3860
|
n@2967
|
3861 function keyPromise() {
|
n@2967
|
3862 return new Promise(function (resolve, reject) {
|
n@2967
|
3863 var req = new XMLHttpRequest();
|
n@2967
|
3864 req.open("GET", returnURL + "php/requestKey.php?saveFilenamePrefix=" + parent.filenamePrefix, true);
|
n@2967
|
3865 req.onload = function () {
|
n@2967
|
3866 // This is called even on 404 etc
|
n@2967
|
3867 // so check the status
|
n@2967
|
3868 if (req.status == 200) {
|
n@2967
|
3869 // Resolve the promise with the response text
|
n@2967
|
3870 resolve(req.response);
|
n@2967
|
3871 } else {
|
n@2967
|
3872 // Otherwise reject with the status text
|
n@2967
|
3873 // which will hopefully be a meaningful error
|
n@2967
|
3874 reject(Error(req.statusText));
|
n@2967
|
3875 }
|
n@2967
|
3876 };
|
n@2967
|
3877
|
n@2967
|
3878 // Handle network errors
|
n@2967
|
3879 req.onerror = function () {
|
n@2967
|
3880 reject(Error("Network Error"));
|
n@2967
|
3881 };
|
n@2967
|
3882
|
n@2967
|
3883 req.send();
|
n@2979
|
3884 });
|
n@2967
|
3885 }
|
n@2967
|
3886
|
n@2967
|
3887 var requestChains = null;
|
n@2969
|
3888 var sessionKey = null;
|
n@2967
|
3889 var object = {};
|
n@2967
|
3890
|
n@2967
|
3891 Object.defineProperties(object, {
|
n@2967
|
3892 "key": {
|
n@2967
|
3893 "get": function () {
|
n@2969
|
3894 return sessionKey;
|
n@2967
|
3895 },
|
n@2967
|
3896 "set": function (a) {
|
n@2979
|
3897 throw ("Cannot set read-only property");
|
n@2967
|
3898 }
|
n@2967
|
3899 },
|
n@2967
|
3900 "request": {
|
n@2967
|
3901 "value": new XMLHttpRequest()
|
n@2967
|
3902 },
|
n@2967
|
3903 "parent": {
|
n@2967
|
3904 "value": parent
|
n@2967
|
3905 },
|
n@2967
|
3906 "requestKey": {
|
n@2967
|
3907 "value": function () {
|
n@2967
|
3908 requestChains = keyPromise().then(function (response) {
|
n@2967
|
3909 function throwerror() {
|
n@2969
|
3910 sessionKey = null;
|
n@2967
|
3911 throw ("An unspecified error occured, no server key could be generated");
|
n@2967
|
3912 }
|
n@2967
|
3913 var parse = new DOMParser();
|
n@2967
|
3914 var xml = parse.parseFromString(response, "text/xml");
|
n@2971
|
3915 if (response.length === 0) {
|
n@2967
|
3916 throwerror();
|
n@2967
|
3917 }
|
n@2967
|
3918 if (xml.getElementsByTagName("state").length > 0) {
|
n@2967
|
3919 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
n@2969
|
3920 sessionKey = xml.getAllElementsByTagName("key")[0].textContent;
|
n@2972
|
3921 parent.root.setAttribute("key", sessionKey);
|
n@2972
|
3922 parent.root.setAttribute("state", "empty");
|
n@2967
|
3923 return (true);
|
n@2967
|
3924 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
|
n@2969
|
3925 sessionKey = null;
|
n@2967
|
3926 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
|
n@2969
|
3927 return (false);
|
n@2967
|
3928 }
|
n@2967
|
3929 } else {
|
n@2967
|
3930 throwerror();
|
n@2967
|
3931 }
|
n@2967
|
3932 return (true);
|
n@2967
|
3933 });
|
n@2967
|
3934 }
|
n@2967
|
3935 },
|
n@2968
|
3936 "update": {
|
n@2968
|
3937 "value": function () {
|
n@2979
|
3938 if (this.key === null || requestChains === undefined) {
|
n@2968
|
3939 throw ("Cannot save as key == null");
|
n@2968
|
3940 }
|
n@2968
|
3941 this.parent.root.setAttribute("state", "update");
|
n@2978
|
3942 requestChains = requestChains.then(postUpdate);
|
n@2967
|
3943 }
|
n@2967
|
3944 },
|
n@2968
|
3945 "finish": {
|
n@2968
|
3946 "value": function () {
|
n@2979
|
3947 if (this.key === null || requestChains === undefined) {
|
n@2968
|
3948 throw ("Cannot save as key == null");
|
n@2968
|
3949 }
|
n@2968
|
3950 this.parent.finish();
|
n@2975
|
3951 return requestChains.then(postUpdate()).then(function () {
|
n@2968
|
3952 console.log("OK");
|
n@2968
|
3953 }, function () {
|
n@2968
|
3954 createProjectSave("local");
|
n@2979
|
3955 });
|
n@2967
|
3956 }
|
n@2967
|
3957 }
|
n@2968
|
3958 });
|
n@2967
|
3959 return object;
|
n@2967
|
3960 })(this);
|
n@2967
|
3961 /*
|
nicholas@2224
|
3962 this.SessionKey = {
|
nicholas@2224
|
3963 key: null,
|
nicholas@2224
|
3964 request: new XMLHttpRequest(),
|
nicholas@2224
|
3965 parent: this,
|
nicholas@2498
|
3966 handleEvent: function () {
|
n@2967
|
3967
|
nicholas@2224
|
3968 },
|
nicholas@2510
|
3969 requestKey: function () {
|
n@2967
|
3970
|
nicholas@2510
|
3971 },
|
nicholas@2498
|
3972 update: function () {
|
nicholas@2708
|
3973 if (this.key === null) {
|
nicholas@2357
|
3974 console.log("Cannot save as key == null");
|
nicholas@2357
|
3975 return;
|
nicholas@2357
|
3976 }
|
nicholas@2498
|
3977 this.parent.root.setAttribute("state", "update");
|
nicholas@2224
|
3978 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3979 var returnURL = "";
|
nicholas@2302
|
3980 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
3981 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2302
|
3982 returnURL = specification.projectReturn;
|
nicholas@2302
|
3983 }
|
nicholas@2302
|
3984 }
|
nicholas@2722
|
3985 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=" + this.parent.filenamePrefix);
|
nicholas@2224
|
3986 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2498
|
3987 xmlhttp.onerror = function () {
|
nicholas@2224
|
3988 console.log('Error updating file to server!');
|
nicholas@2224
|
3989 };
|
nicholas@2224
|
3990 var hold = document.createElement("div");
|
nicholas@2224
|
3991 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3992 hold.appendChild(clone);
|
nicholas@2498
|
3993 xmlhttp.onload = function () {
|
nicholas@2224
|
3994 if (this.status >= 300) {
|
nicholas@2224
|
3995 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3996 } else {
|
nicholas@2224
|
3997 var parser = new DOMParser();
|
nicholas@2224
|
3998 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3999 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
4000 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
4001 var file = response.getElementsByTagName("file")[0];
|
nicholas@2498
|
4002 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2224
|
4003 } else {
|
nicholas@2224
|
4004 var message = response.getElementsByTagName("message");
|
nicholas@2498
|
4005 console.log("Intermediate save: Error! " + message.textContent);
|
nicholas@2224
|
4006 }
|
nicholas@2224
|
4007 }
|
nicholas@2708
|
4008 };
|
nicholas@2224
|
4009 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
4010 },
|
nicholas@2723
|
4011 finish: function () {
|
nicholas@2723
|
4012 // Final upload to complete the test
|
nicholas@2723
|
4013 this.parent.finish();
|
nicholas@2723
|
4014 var hold = document.createElement("div");
|
nicholas@2723
|
4015 var clone = this.parent.root.cloneNode(true);
|
nicholas@2723
|
4016 hold.appendChild(clone);
|
nicholas@2733
|
4017 var saveURL = specification.returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=";
|
nicholas@2742
|
4018 if (this.parent.filenamePrefix.length === 0) {
|
nicholas@2733
|
4019 saveURL += "save";
|
nicholas@2733
|
4020 } else {
|
nicholas@2733
|
4021 saveURL += this.parent.filenamePrefix;
|
nicholas@2733
|
4022 }
|
nicholas@2723
|
4023 return new Promise(function (resolve, reject) {
|
nicholas@2723
|
4024 var xmlhttp = new XMLHttpRequest();
|
nicholas@2723
|
4025 xmlhttp.open("POST", saveURL);
|
nicholas@2723
|
4026 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2723
|
4027 xmlhttp.onerror = function () {
|
nicholas@2723
|
4028 console.log('Error updating file to server!');
|
nicholas@2723
|
4029 createProjectSave("local");
|
nicholas@2723
|
4030 };
|
nicholas@2723
|
4031 xmlhttp.onload = function () {
|
nicholas@2723
|
4032 if (this.status >= 300) {
|
nicholas@2723
|
4033 console.log("WARNING - Could not update at this time");
|
nicholas@2723
|
4034 createProjectSave("local");
|
nicholas@2723
|
4035 } else {
|
nicholas@2723
|
4036 var parser = new DOMParser();
|
nicholas@2723
|
4037 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2723
|
4038 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2723
|
4039 if (response.getAttribute("state") == "OK") {
|
nicholas@2723
|
4040 var file = response.getElementsByTagName("file")[0];
|
nicholas@2723
|
4041 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2723
|
4042 resolve(response);
|
nicholas@2723
|
4043 } else {
|
nicholas@2723
|
4044 var message = response.getElementsByTagName("message");
|
nicholas@2723
|
4045 reject(message);
|
nicholas@2723
|
4046 }
|
nicholas@2723
|
4047 }
|
nicholas@2723
|
4048 };
|
nicholas@2723
|
4049 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
4050 });
|
nicholas@2224
|
4051 }
|
nicholas@2708
|
4052 };
|
n@2967
|
4053 */
|
nicholas@2498
|
4054 this.createTestPageStore = function (specification) {
|
nicholas@2498
|
4055 var store = new this.pageNode(this, specification);
|
nicholas@2498
|
4056 this.testPages.push(store);
|
nicholas@2498
|
4057 return this.testPages[this.testPages.length - 1];
|
nicholas@2498
|
4058 };
|
nicholas@2498
|
4059
|
nicholas@2498
|
4060 this.surveyNode = function (parent, root, specification) {
|
nicholas@2498
|
4061 this.specification = specification;
|
nicholas@2498
|
4062 this.parent = parent;
|
nicholas@2224
|
4063 this.state = "empty";
|
nicholas@2498
|
4064 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2498
|
4065 this.XMLDOM.setAttribute('location', this.specification.location);
|
nicholas@2498
|
4066 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
4067 this.specification.options.forEach(function (optNode) {
|
nicholas@2498
|
4068 if (optNode.type != 'statement') {
|
nicholas@2498
|
4069 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2498
|
4070 node.setAttribute("ref", optNode.id);
|
nicholas@2498
|
4071 node.setAttribute('type', optNode.type);
|
nicholas@2498
|
4072 this.XMLDOM.appendChild(node);
|
nicholas@2498
|
4073 }
|
nicholas@2708
|
4074 }, this);
|
nicholas@2498
|
4075 root.appendChild(this.XMLDOM);
|
nicholas@2498
|
4076
|
nicholas@2498
|
4077 this.postResult = function (node) {
|
nicholas@2708
|
4078 function postNumber(doc, value) {
|
nicholas@2708
|
4079 var child = doc.createElement("response");
|
nicholas@2708
|
4080 child.textContent = value;
|
nicholas@2708
|
4081 return child;
|
nicholas@2708
|
4082 }
|
nicholas@2708
|
4083
|
nicholas@2708
|
4084 function postRadio(doc, node) {
|
nicholas@2708
|
4085 var child = doc.createElement('response');
|
nicholas@2708
|
4086 if (node.response !== null) {
|
nicholas@2708
|
4087 child.setAttribute('name', node.response.name);
|
nicholas@2708
|
4088 child.textContent = node.response.text;
|
nicholas@2708
|
4089 }
|
nicholas@2708
|
4090 return child;
|
nicholas@2708
|
4091 }
|
nicholas@2708
|
4092
|
nicholas@2708
|
4093 function postCheckbox(doc, node) {
|
nicholas@2708
|
4094 var checkNode = doc.createElement('response');
|
nicholas@2708
|
4095 checkNode.setAttribute('name', node.name);
|
nicholas@2708
|
4096 checkNode.setAttribute('checked', node.checked);
|
nicholas@2708
|
4097 return checkNode;
|
nicholas@2708
|
4098 }
|
nicholas@2498
|
4099 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2498
|
4100 // ID is the position
|
nicholas@2498
|
4101 if (node.specification.type == 'statement') {
|
nicholas@2498
|
4102 return;
|
nicholas@2498
|
4103 }
|
nicholas@2498
|
4104 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2708
|
4105 while (surveyresult !== null) {
|
nicholas@2498
|
4106 if (surveyresult.getAttribute("ref") == node.specification.id) {
|
nicholas@2224
|
4107 break;
|
nicholas@2224
|
4108 }
|
nicholas@2224
|
4109 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
4110 }
|
nicholas@2775
|
4111 surveyresult.setAttribute("duration", node.elapsedTime);
|
nicholas@2498
|
4112 switch (node.specification.type) {
|
nicholas@2498
|
4113 case "number":
|
nicholas@2498
|
4114 case "question":
|
n@2583
|
4115 case "slider":
|
nicholas@2708
|
4116 surveyresult.appendChild(postNumber(this.parent.document, node.response));
|
nicholas@2464
|
4117 break;
|
nicholas@2498
|
4118 case "radio":
|
nicholas@2708
|
4119 surveyresult.appendChild(postRadio(this.parent.document, node));
|
nicholas@2498
|
4120 break;
|
nicholas@2498
|
4121 case "checkbox":
|
nicholas@2708
|
4122 if (node.response === undefined) {
|
nicholas@2498
|
4123 surveyresult.appendChild(this.parent.document.createElement('response'));
|
nicholas@2498
|
4124 break;
|
nicholas@2498
|
4125 }
|
nicholas@2498
|
4126 for (var i = 0; i < node.response.length; i++) {
|
nicholas@2708
|
4127 surveyresult.appendChild(postCheckbox(this.parent.document, node.response[i]));
|
nicholas@2498
|
4128 }
|
nicholas@2498
|
4129 break;
|
nicholas@2498
|
4130 }
|
nicholas@2498
|
4131 };
|
nicholas@2498
|
4132 this.complete = function () {
|
nicholas@2498
|
4133 this.state = "complete";
|
nicholas@2498
|
4134 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
4135 };
|
nicholas@2498
|
4136 };
|
nicholas@2498
|
4137
|
nicholas@2498
|
4138 this.pageNode = function (parent, specification) {
|
nicholas@2498
|
4139 // Create one store per test page
|
nicholas@2498
|
4140 this.specification = specification;
|
nicholas@2498
|
4141 this.parent = parent;
|
nicholas@2498
|
4142 this.state = "empty";
|
nicholas@2498
|
4143 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2498
|
4144 this.XMLDOM.setAttribute('ref', specification.id);
|
nicholas@2498
|
4145 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
|
nicholas@2498
|
4146 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
4147 if (specification.preTest !== undefined) {
|
nicholas@2498
|
4148 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
|
nicholas@2498
|
4149 }
|
nicholas@2708
|
4150 if (specification.postTest !== undefined) {
|
nicholas@2498
|
4151 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
|
nicholas@2498
|
4152 }
|
nicholas@2498
|
4153
|
nicholas@2498
|
4154 // Add any page metrics
|
nicholas@2498
|
4155 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
4156 this.XMLDOM.appendChild(page_metric);
|
nicholas@2498
|
4157
|
nicholas@2498
|
4158 // Add the audioelement
|
nicholas@2708
|
4159 this.specification.audioElements.forEach(function (element) {
|
nicholas@2498
|
4160 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2498
|
4161 aeNode.setAttribute('ref', element.id);
|
nicholas@2708
|
4162 if (element.name !== undefined) {
|
nicholas@2708
|
4163 aeNode.setAttribute('name', element.name);
|
nicholas@2708
|
4164 }
|
nicholas@2498
|
4165 aeNode.setAttribute('type', element.type);
|
nicholas@2498
|
4166 aeNode.setAttribute('url', element.url);
|
nicholas@2498
|
4167 aeNode.setAttribute('fqurl', qualifyURL(element.url));
|
nicholas@2498
|
4168 aeNode.setAttribute('gain', element.gain);
|
nicholas@2498
|
4169 if (element.type == 'anchor' || element.type == 'reference') {
|
nicholas@2498
|
4170 if (element.marker > 0) {
|
nicholas@2498
|
4171 aeNode.setAttribute('marker', element.marker);
|
nicholas@2464
|
4172 }
|
nicholas@2498
|
4173 }
|
nicholas@2498
|
4174 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
4175 aeNode.appendChild(ae_metric);
|
nicholas@2498
|
4176 this.XMLDOM.appendChild(aeNode);
|
nicholas@2708
|
4177 }, this);
|
nicholas@2498
|
4178
|
nicholas@2498
|
4179 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2498
|
4180
|
nicholas@2498
|
4181 this.complete = function () {
|
nicholas@2224
|
4182 this.state = "complete";
|
nicholas@2498
|
4183 this.XMLDOM.setAttribute("state", "complete");
|
nicholas@2708
|
4184 };
|
nicholas@2498
|
4185 };
|
nicholas@2498
|
4186 this.update = function () {
|
nicholas@2224
|
4187 this.SessionKey.update();
|
nicholas@2708
|
4188 };
|
nicholas@2498
|
4189 this.finish = function () {
|
nicholas@2498
|
4190 this.state = 1;
|
nicholas@2498
|
4191 this.root.setAttribute("state", "complete");
|
nicholas@2498
|
4192 return this.root;
|
nicholas@2498
|
4193 };
|
nicholas@2722
|
4194
|
nicholas@2722
|
4195 Object.defineProperties(this, {
|
nicholas@2722
|
4196 'filenamePrefix': {
|
nicholas@2722
|
4197 'get': function () {
|
nicholas@2722
|
4198 return pFilenamePrefix;
|
nicholas@2722
|
4199 },
|
nicholas@2722
|
4200 'set': function (value) {
|
nicholas@2722
|
4201 if (typeof value !== "string") {
|
nicholas@2722
|
4202 value = String(value);
|
nicholas@2722
|
4203 }
|
nicholas@2722
|
4204 pFilenamePrefix = value;
|
nicholas@2722
|
4205 return value;
|
nicholas@2722
|
4206 }
|
nicholas@2722
|
4207 }
|
nicholas@2725
|
4208 });
|
nicholas@2224
|
4209 }
|
nicholas@2384
|
4210
|
nicholas@2401
|
4211 var window_depedancy_callback;
|
nicholas@2498
|
4212 window_depedancy_callback = window.setInterval(function () {
|
nicholas@2401
|
4213 if (check_dependancies()) {
|
nicholas@2401
|
4214 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
4215 onload();
|
nicholas@2401
|
4216 } else {
|
nicholas@2401
|
4217 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
4218 }
|
nicholas@2498
|
4219 }, 100);
|