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