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