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