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@2498
|
2223 if (time > 0) {
|
nicholas@2498
|
2224 this.wasMoved = true;
|
nicholas@2498
|
2225 }
|
nicholas@2498
|
2226 this.movementTracker[this.movementTracker.length] = [time, position];
|
nicholas@2498
|
2227 };
|
nicholas@2498
|
2228
|
nicholas@2498
|
2229 this.startListening = function (time) {
|
nicholas@2708
|
2230 if (this.listenHold === false) {
|
nicholas@2498
|
2231 this.wasListenedTo = true;
|
nicholas@2498
|
2232 this.listenStart = time;
|
nicholas@2498
|
2233 this.listenHold = true;
|
nicholas@2498
|
2234
|
nicholas@2498
|
2235 var evnt = document.createElement('event');
|
nicholas@2498
|
2236 var testTime = document.createElement('testTime');
|
nicholas@2498
|
2237 testTime.setAttribute('start', time);
|
nicholas@2498
|
2238 var bufferTime = document.createElement('bufferTime');
|
nicholas@2498
|
2239 bufferTime.setAttribute('start', this.parent.getCurrentPosition());
|
nicholas@2498
|
2240 evnt.appendChild(testTime);
|
nicholas@2498
|
2241 evnt.appendChild(bufferTime);
|
nicholas@2498
|
2242 this.listenTracker.push(evnt);
|
nicholas@2498
|
2243
|
nicholas@2498
|
2244 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2498
|
2245 }
|
nicholas@2498
|
2246 };
|
nicholas@2498
|
2247
|
nicholas@2498
|
2248 this.stopListening = function (time, bufferStopTime) {
|
nicholas@2708
|
2249 if (this.listenHold === true) {
|
nicholas@2498
|
2250 var diff = time - this.listenStart;
|
nicholas@2498
|
2251 this.listenedTimer += (diff);
|
nicholas@2498
|
2252 this.listenStart = 0;
|
nicholas@2498
|
2253 this.listenHold = false;
|
nicholas@2498
|
2254
|
nicholas@2498
|
2255 var evnt = this.listenTracker[this.listenTracker.length - 1];
|
nicholas@2498
|
2256 var testTime = evnt.getElementsByTagName('testTime')[0];
|
nicholas@2498
|
2257 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
nicholas@2498
|
2258 testTime.setAttribute('stop', time);
|
nicholas@2708
|
2259 if (bufferStopTime === undefined) {
|
nicholas@2498
|
2260 bufferTime.setAttribute('stop', this.parent.getCurrentPosition());
|
nicholas@2498
|
2261 } else {
|
nicholas@2498
|
2262 bufferTime.setAttribute('stop', bufferStopTime);
|
nicholas@2498
|
2263 }
|
nicholas@2498
|
2264 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2498
|
2265 }
|
nicholas@2498
|
2266 };
|
nicholas@2498
|
2267
|
nicholas@2708
|
2268 function exportElementTimer(parentElement) {
|
nicholas@2708
|
2269 var mElementTimer = storage.document.createElement('metricresult');
|
nicholas@2708
|
2270 mElementTimer.setAttribute('name', 'enableElementTimer');
|
nicholas@2708
|
2271 mElementTimer.textContent = this.listenedTimer;
|
nicholas@2708
|
2272 parentElement.appendChild(mElementTimer);
|
nicholas@2708
|
2273 return mElementTimer;
|
nicholas@2708
|
2274 }
|
nicholas@2708
|
2275
|
nicholas@2708
|
2276 function exportElementTrack(parentElement) {
|
nicholas@2708
|
2277 var elementTrackerFull = storage.document.createElement('metricresult');
|
nicholas@2708
|
2278 elementTrackerFull.setAttribute('name', 'elementTrackerFull');
|
nicholas@2708
|
2279 for (var k = 0; k < this.movementTracker.length; k++) {
|
nicholas@2708
|
2280 var timePos = storage.document.createElement('movement');
|
nicholas@2708
|
2281 timePos.setAttribute("time", this.movementTracker[k][0]);
|
nicholas@2708
|
2282 timePos.setAttribute("value", this.movementTracker[k][1]);
|
nicholas@2708
|
2283 elementTrackerFull.appendChild(timePos);
|
nicholas@2708
|
2284 }
|
nicholas@2708
|
2285 parentElement.appendChild(elementTrackerFull);
|
nicholas@2708
|
2286 return elementTrackerFull;
|
nicholas@2708
|
2287 }
|
nicholas@2708
|
2288
|
nicholas@2708
|
2289 function exportElementListenTracker(parentElement) {
|
nicholas@2708
|
2290 var elementListenTracker = storage.document.createElement('metricresult');
|
nicholas@2708
|
2291 elementListenTracker.setAttribute('name', 'elementListenTracker');
|
nicholas@2708
|
2292 for (var k = 0; k < this.listenTracker.length; k++) {
|
nicholas@2708
|
2293 elementListenTracker.appendChild(this.listenTracker[k]);
|
nicholas@2708
|
2294 }
|
nicholas@2708
|
2295 parentElement.appendChild(elementListenTracker);
|
nicholas@2708
|
2296 return elementListenTracker;
|
nicholas@2708
|
2297 }
|
nicholas@2708
|
2298
|
nicholas@2708
|
2299 function exportElementInitialPosition(parentElement) {
|
nicholas@2708
|
2300 var elementInitial = storage.document.createElement('metricresult');
|
nicholas@2708
|
2301 elementInitial.setAttribute('name', 'elementInitialPosition');
|
nicholas@2708
|
2302 elementInitial.textContent = this.initialPosition;
|
nicholas@2708
|
2303 parentElement.appendChild(elementInitial);
|
nicholas@2708
|
2304 return elementInitial;
|
nicholas@2708
|
2305 }
|
nicholas@2708
|
2306
|
nicholas@2708
|
2307 function exportFlagListenedTo(parentElement) {
|
nicholas@2708
|
2308 var flagListenedTo = storage.document.createElement('metricresult');
|
nicholas@2708
|
2309 flagListenedTo.setAttribute('name', 'elementFlagListenedTo');
|
nicholas@2708
|
2310 flagListenedTo.textContent = this.wasListenedTo;
|
nicholas@2708
|
2311 parentElement.appendChild(flagListenedTo);
|
nicholas@2708
|
2312 return flagListenedTo;
|
nicholas@2708
|
2313 }
|
nicholas@2708
|
2314
|
nicholas@2708
|
2315 function exportFlagMoved(parentElement) {
|
nicholas@2708
|
2316 var flagMoved = storage.document.createElement('metricresult');
|
nicholas@2708
|
2317 flagMoved.setAttribute('name', 'elementFlagMoved');
|
nicholas@2708
|
2318 flagMoved.textContent = this.wasMoved;
|
nicholas@2708
|
2319 parentElement.appendChild(flagMoved);
|
nicholas@2708
|
2320 return flagMoved;
|
nicholas@2708
|
2321 }
|
nicholas@2708
|
2322
|
nicholas@2708
|
2323 function exportFlagComments(parentElement) {
|
nicholas@2708
|
2324 var flagComments = storage.document.createElement('metricresult');
|
nicholas@2708
|
2325 flagComments.setAttribute('name', 'elementFlagComments');
|
nicholas@2708
|
2326 if (this.parent.commentDOM === null) {
|
nicholas@2708
|
2327 flagComments.textContent = 'false';
|
nicholas@2708
|
2328 } else if (this.parent.commentDOM.textContent.length === 0) {
|
nicholas@2708
|
2329 flagComments.textContent = 'false';
|
nicholas@2708
|
2330 } else {
|
nicholas@2708
|
2331 flagComments.textContet = 'true';
|
nicholas@2708
|
2332 }
|
nicholas@2708
|
2333 parentElement.appendChild(flagComments);
|
nicholas@2708
|
2334 return flagComments;
|
nicholas@2708
|
2335 }
|
nicholas@2708
|
2336
|
nicholas@2708
|
2337 this.exportXMLDOM = function (parentElement) {
|
nicholas@2708
|
2338 var elems = [];
|
nicholas@2498
|
2339 if (audioEngineContext.metric.enableElementTimer) {
|
nicholas@2708
|
2340 elems.push(exportElementTimer.call(this, parentElement));
|
nicholas@2498
|
2341 }
|
nicholas@2498
|
2342 if (audioEngineContext.metric.enableElementTracker) {
|
nicholas@2708
|
2343 elems.push(exportElementTrack.call(this, parentElement));
|
nicholas@2498
|
2344 }
|
nicholas@2498
|
2345 if (audioEngineContext.metric.enableElementListenTracker) {
|
nicholas@2708
|
2346 elems.push(exportElementListenTracker.call(this, parentElement));
|
nicholas@2498
|
2347 }
|
nicholas@2498
|
2348 if (audioEngineContext.metric.enableElementInitialPosition) {
|
nicholas@2708
|
2349 elems.push(exportElementInitialPosition.call(this, parentElement));
|
nicholas@2498
|
2350 }
|
nicholas@2498
|
2351 if (audioEngineContext.metric.enableFlagListenedTo) {
|
nicholas@2708
|
2352 elems.push(exportFlagListenedTo.call(this, parentElement));
|
nicholas@2498
|
2353 }
|
nicholas@2498
|
2354 if (audioEngineContext.metric.enableFlagMoved) {
|
nicholas@2708
|
2355 elems.push(exportFlagMoved.call(this, parentElement));
|
nicholas@2498
|
2356 }
|
nicholas@2498
|
2357 if (audioEngineContext.metric.enableFlagComments) {
|
nicholas@2708
|
2358 elems.push(exportFlagComments.call(this, parentElement));
|
nicholas@2498
|
2359 }
|
nicholas@2708
|
2360 return elems;
|
nicholas@2498
|
2361 };
|
nicholas@2224
|
2362 }
|
nicholas@2498
|
2363
|
nicholas@2224
|
2364 function Interface(specificationObject) {
|
nicholas@2498
|
2365 // This handles the bindings between the interface and the audioEngineContext;
|
nicholas@2498
|
2366 this.specification = specificationObject;
|
nicholas@2498
|
2367 this.insertPoint = document.getElementById("topLevelBody");
|
nicholas@2498
|
2368
|
nicholas@2498
|
2369 this.newPage = function (audioHolderObject, store) {
|
nicholas@2498
|
2370 audioEngineContext.newTestPage(audioHolderObject, store);
|
nicholas@2498
|
2371 interfaceContext.commentBoxes.deleteCommentBoxes();
|
nicholas@2498
|
2372 interfaceContext.deleteCommentQuestions();
|
nicholas@2498
|
2373 loadTest(audioHolderObject, store);
|
nicholas@2498
|
2374 };
|
nicholas@2498
|
2375
|
nicholas@2955
|
2376 this.keyboardInterface = (function () {
|
nicholas@2955
|
2377 var keyboardInterfaceController = {
|
nicholas@2955
|
2378 keys: [],
|
nicholas@2955
|
2379 registerKeyBinding: function (key, audioObject) {
|
nicholas@2955
|
2380 if (typeof key != "string" || key.length != 1) {
|
nicholas@2955
|
2381 throw ("Key must be a singular character");
|
nicholas@2955
|
2382 }
|
nicholas@2955
|
2383 var included = this.keys.findIndex(function (k) {
|
n@2979
|
2384 return k.key == key;
|
nicholas@2955
|
2385 }) >= 0;
|
nicholas@2955
|
2386 if (included) {
|
nicholas@2955
|
2387 throw ("Key " + key + " already bounded!");
|
nicholas@2955
|
2388 }
|
nicholas@2955
|
2389 this.keys.push({
|
nicholas@2955
|
2390 key: key,
|
nicholas@2955
|
2391 audioObject: audioObject
|
nicholas@2955
|
2392 });
|
nicholas@2955
|
2393 return true;
|
nicholas@2955
|
2394 },
|
nicholas@2955
|
2395 deregisterKeyBinding: function (key) {
|
nicholas@2955
|
2396 var index = this.keys.findIndex(function (k) {
|
nicholas@2955
|
2397 return k.key == key;
|
nicholas@2955
|
2398 });
|
nicholas@2955
|
2399 if (index == -1) {
|
nicholas@2955
|
2400 throw ("Key " + key + " not bounded!");
|
nicholas@2955
|
2401 }
|
nicholas@2955
|
2402 this.keys.splice(index, 1);
|
nicholas@2955
|
2403 return true;
|
nicholas@2955
|
2404 },
|
nicholas@2955
|
2405 resetKeyBindings: function () {
|
nicholas@2955
|
2406 this.keys = [];
|
nicholas@2955
|
2407 },
|
nicholas@2955
|
2408 handleEvent: function (e) {
|
nicholas@2955
|
2409 function isPlaying() {
|
nicholas@2955
|
2410 return audioEngineContext.audioObjects.some(function (a) {
|
nicholas@2955
|
2411 return a.playing;
|
nicholas@2955
|
2412 });
|
nicholas@2955
|
2413 }
|
nicholas@2955
|
2414
|
nicholas@2955
|
2415 function keypress(key) {
|
nicholas@2955
|
2416 var index = this.keys.findIndex(function (k) {
|
n@2979
|
2417 return k.key == key;
|
nicholas@2955
|
2418 });
|
nicholas@2955
|
2419 if (index >= 0) {
|
nicholas@2955
|
2420 audioEngineContext.play(this.keys[index].audioObject.id);
|
nicholas@2955
|
2421 }
|
nicholas@2955
|
2422 }
|
n@2965
|
2423
|
n@2965
|
2424 function trackCommentFocus() {
|
n@2965
|
2425 return document.activeElement.className.indexOf("trackComment") >= 0;
|
n@2965
|
2426 }
|
nicholas@2982
|
2427 if (trackCommentFocus()) {
|
nicholas@2982
|
2428 return;
|
nicholas@2982
|
2429 }
|
nicholas@2955
|
2430 if (e.key === " ") {
|
nicholas@2982
|
2431 if (isPlaying()) {
|
n@2965
|
2432 e.preventDefault();
|
nicholas@2955
|
2433 audioEngineContext.stop();
|
nicholas@2955
|
2434 }
|
nicholas@2955
|
2435 } else {
|
nicholas@2955
|
2436 keypress.call(this, e.key);
|
nicholas@2955
|
2437 }
|
n@2965
|
2438 console.log(e);
|
nicholas@2955
|
2439 }
|
nicholas@2955
|
2440 };
|
nicholas@2955
|
2441 document.addEventListener("keydown", keyboardInterfaceController, false);
|
nicholas@2955
|
2442 return keyboardInterfaceController;
|
nicholas@2955
|
2443 })();
|
nicholas@2955
|
2444
|
nicholas@2498
|
2445 // Bounded by interface!!
|
nicholas@2498
|
2446 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
nicholas@2498
|
2447 // For example, APE returns the slider position normalised in a <value> tag.
|
nicholas@2498
|
2448 this.interfaceObjects = [];
|
nicholas@2498
|
2449 this.interfaceObject = function () {};
|
nicholas@2498
|
2450
|
nicholas@2498
|
2451 this.resizeWindow = function (event) {
|
nicholas@2498
|
2452 popup.resize(event);
|
nicholas@2352
|
2453 this.volume.resize();
|
nicholas@2360
|
2454 this.lightbox.resize();
|
n@2718
|
2455 this.commentBoxes.boxes.forEach(function (elem) {
|
nicholas@2708
|
2456 elem.resize();
|
nicholas@2708
|
2457 });
|
nicholas@2708
|
2458 this.commentQuestions.forEach(function (elem) {
|
nicholas@2708
|
2459 elem.resize();
|
nicholas@2708
|
2460 });
|
nicholas@2498
|
2461 try {
|
nicholas@2498
|
2462 resizeWindow(event);
|
nicholas@2498
|
2463 } catch (err) {
|
nicholas@2498
|
2464 console.log("Warning - Interface does not have Resize option");
|
nicholas@2498
|
2465 console.log(err);
|
nicholas@2498
|
2466 }
|
nicholas@2498
|
2467 };
|
nicholas@2498
|
2468
|
nicholas@2498
|
2469 this.returnNavigator = function () {
|
nicholas@2498
|
2470 var node = storage.document.createElement("navigator");
|
nicholas@2498
|
2471 var platform = storage.document.createElement("platform");
|
nicholas@2498
|
2472 platform.textContent = navigator.platform;
|
nicholas@2498
|
2473 var vendor = storage.document.createElement("vendor");
|
nicholas@2498
|
2474 vendor.textContent = navigator.vendor;
|
nicholas@2498
|
2475 var userAgent = storage.document.createElement("uagent");
|
nicholas@2498
|
2476 userAgent.textContent = navigator.userAgent;
|
nicholas@2224
|
2477 var screen = storage.document.createElement("window");
|
nicholas@2498
|
2478 screen.setAttribute('innerWidth', window.innerWidth);
|
nicholas@2498
|
2479 screen.setAttribute('innerHeight', window.innerHeight);
|
nicholas@2498
|
2480 node.appendChild(platform);
|
nicholas@2498
|
2481 node.appendChild(vendor);
|
nicholas@2498
|
2482 node.appendChild(userAgent);
|
nicholas@2224
|
2483 node.appendChild(screen);
|
nicholas@2498
|
2484 return node;
|
nicholas@2498
|
2485 };
|
nicholas@2498
|
2486
|
nicholas@2498
|
2487 this.returnDateNode = function () {
|
nicholas@2224
|
2488 // Create an XML Node for the Date and Time a test was conducted
|
nicholas@2224
|
2489 // Structure is
|
nicholas@2224
|
2490 // <datetime>
|
nicholas@2224
|
2491 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
nicholas@2224
|
2492 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
nicholas@2224
|
2493 // </datetime>
|
nicholas@2224
|
2494 var dateTime = new Date();
|
nicholas@2224
|
2495 var hold = storage.document.createElement("datetime");
|
nicholas@2224
|
2496 var date = storage.document.createElement("date");
|
nicholas@2224
|
2497 var time = storage.document.createElement("time");
|
nicholas@2498
|
2498 date.setAttribute('year', dateTime.getFullYear());
|
nicholas@2498
|
2499 date.setAttribute('month', dateTime.getMonth() + 1);
|
nicholas@2498
|
2500 date.setAttribute('day', dateTime.getDate());
|
nicholas@2498
|
2501 time.setAttribute('hour', dateTime.getHours());
|
nicholas@2498
|
2502 time.setAttribute('minute', dateTime.getMinutes());
|
nicholas@2498
|
2503 time.setAttribute('secs', dateTime.getSeconds());
|
nicholas@2498
|
2504
|
nicholas@2224
|
2505 hold.appendChild(date);
|
nicholas@2224
|
2506 hold.appendChild(time);
|
nicholas@2224
|
2507 return hold;
|
nicholas@2224
|
2508
|
nicholas@2708
|
2509 };
|
nicholas@2498
|
2510
|
nicholas@2360
|
2511 this.lightbox = {
|
nicholas@2360
|
2512 parent: this,
|
nicholas@2360
|
2513 root: document.createElement("div"),
|
nicholas@2360
|
2514 content: document.createElement("div"),
|
nicholas@2360
|
2515 accept: document.createElement("button"),
|
nicholas@2360
|
2516 blanker: document.createElement("div"),
|
nicholas@2498
|
2517 post: function (type, message) {
|
nicholas@2498
|
2518 switch (type) {
|
nicholas@2360
|
2519 case "Error":
|
nicholas@2360
|
2520 this.content.className = "lightbox-error";
|
nicholas@2360
|
2521 break;
|
nicholas@2360
|
2522 case "Warning":
|
nicholas@2360
|
2523 this.content.className = "lightbox-warning";
|
nicholas@2360
|
2524 break;
|
nicholas@2360
|
2525 default:
|
nicholas@2360
|
2526 this.content.className = "lightbox-message";
|
nicholas@2360
|
2527 break;
|
nicholas@2360
|
2528 }
|
nicholas@2360
|
2529 var msg = document.createElement("p");
|
nicholas@2360
|
2530 msg.textContent = message;
|
nicholas@2360
|
2531 this.content.appendChild(msg);
|
nicholas@2360
|
2532 this.show();
|
nicholas@2360
|
2533 },
|
nicholas@2498
|
2534 show: function () {
|
nicholas@2360
|
2535 this.root.style.visibility = "visible";
|
nicholas@2360
|
2536 this.blanker.style.visibility = "visible";
|
n@2914
|
2537 this.accept.focus();
|
nicholas@2360
|
2538 },
|
nicholas@2498
|
2539 clear: function () {
|
nicholas@2360
|
2540 this.root.style.visibility = "";
|
nicholas@2360
|
2541 this.blanker.style.visibility = "";
|
nicholas@2360
|
2542 this.content.textContent = "";
|
nicholas@2360
|
2543 },
|
nicholas@2498
|
2544 handleEvent: function (event) {
|
nicholas@2360
|
2545 if (event.currentTarget == this.accept) {
|
nicholas@2360
|
2546 this.clear();
|
nicholas@2360
|
2547 }
|
nicholas@2360
|
2548 },
|
nicholas@2498
|
2549 resize: function (event) {
|
nicholas@2498
|
2550 this.root.style.left = (window.innerWidth / 2) - 250 + 'px';
|
n@2915
|
2551 },
|
n@2915
|
2552 isVisible: function () {
|
n@2915
|
2553 return this.root.style.visibility == "visible";
|
nicholas@2360
|
2554 }
|
nicholas@2708
|
2555 };
|
nicholas@2498
|
2556
|
nicholas@2360
|
2557 this.lightbox.root.appendChild(this.lightbox.content);
|
nicholas@2360
|
2558 this.lightbox.root.appendChild(this.lightbox.accept);
|
nicholas@2360
|
2559 this.lightbox.root.className = "popupHolder";
|
nicholas@2360
|
2560 this.lightbox.root.id = "lightbox-root";
|
nicholas@2360
|
2561 this.lightbox.accept.className = "popupButton";
|
nicholas@2360
|
2562 this.lightbox.accept.style.bottom = "10px";
|
nicholas@2360
|
2563 this.lightbox.accept.textContent = "OK";
|
nicholas@2360
|
2564 this.lightbox.accept.style.left = "237.5px";
|
nicholas@2498
|
2565 this.lightbox.accept.addEventListener("click", this.lightbox);
|
nicholas@2360
|
2566 this.lightbox.blanker.className = "testHalt";
|
nicholas@2360
|
2567 this.lightbox.blanker.id = "lightbox-blanker";
|
nicholas@2360
|
2568 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
|
nicholas@2360
|
2569 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
|
nicholas@2498
|
2570
|
nicholas@2712
|
2571 this.commentBoxes = (function () {
|
nicholas@2712
|
2572 var commentBoxes = {};
|
nicholas@2712
|
2573 commentBoxes.boxes = [];
|
nicholas@2712
|
2574 commentBoxes.injectPoint = null;
|
nicholas@2712
|
2575 commentBoxes.elementCommentBox = function (audioObject) {
|
nicholas@2224
|
2576 var element = audioObject.specification;
|
nicholas@2224
|
2577 this.audioObject = audioObject;
|
nicholas@2224
|
2578 this.id = audioObject.id;
|
nicholas@2224
|
2579 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
2580 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2581 this.trackComment = document.createElement('div');
|
nicholas@2224
|
2582 this.trackComment.className = 'comment-div';
|
nicholas@2498
|
2583 this.trackComment.id = 'comment-div-' + audioObject.id;
|
nicholas@2224
|
2584 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2585 this.trackString = document.createElement('span');
|
nicholas@2498
|
2586 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
2587 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2588 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
2589 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
2590 this.trackCommentBox.cols = '100';
|
nicholas@2498
|
2591 this.trackCommentBox.name = 'trackComment' + audioObject.id;
|
nicholas@2224
|
2592 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
2593 var br = document.createElement('br');
|
nicholas@2224
|
2594 // Add to the holder.
|
nicholas@2224
|
2595 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
2596 this.trackComment.appendChild(br);
|
nicholas@2224
|
2597 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
2598
|
nicholas@2498
|
2599 this.exportXMLDOM = function () {
|
nicholas@2224
|
2600 var root = document.createElement('comment');
|
nicholas@2224
|
2601 var question = document.createElement('question');
|
nicholas@2224
|
2602 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2603 var response = document.createElement('response');
|
nicholas@2224
|
2604 response.textContent = this.trackCommentBox.value;
|
nicholas@2498
|
2605 console.log("Comment frag-" + this.id + ": " + response.textContent);
|
nicholas@2224
|
2606 root.appendChild(question);
|
nicholas@2224
|
2607 root.appendChild(response);
|
nicholas@2224
|
2608 return root;
|
nicholas@2224
|
2609 };
|
nicholas@2498
|
2610 this.resize = function () {
|
nicholas@2498
|
2611 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2612 if (boxwidth >= 600) {
|
nicholas@2224
|
2613 boxwidth = 600;
|
nicholas@2498
|
2614 } else if (boxwidth < 400) {
|
nicholas@2224
|
2615 boxwidth = 400;
|
nicholas@2224
|
2616 }
|
nicholas@2498
|
2617 this.trackComment.style.width = boxwidth + "px";
|
nicholas@2498
|
2618 this.trackCommentBox.style.width = boxwidth - 6 + "px";
|
nicholas@2224
|
2619 };
|
nicholas@2224
|
2620 this.resize();
|
nicholas@2725
|
2621 this.highlight = function (state) {
|
nicholas@2725
|
2622 if (state === true) {
|
nicholas@2725
|
2623 $(this.trackComment).addClass("comment-box-playing");
|
nicholas@2725
|
2624 } else {
|
nicholas@2725
|
2625 $(this.trackComment).removeClass("comment-box-playing");
|
nicholas@2725
|
2626 }
|
nicholas@2725
|
2627 };
|
nicholas@2224
|
2628 };
|
nicholas@2712
|
2629 commentBoxes.createCommentBox = function (audioObject) {
|
nicholas@2224
|
2630 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2631 this.boxes.push(node);
|
nicholas@2224
|
2632 audioObject.commentDOM = node;
|
nicholas@2224
|
2633 return node;
|
nicholas@2224
|
2634 };
|
nicholas@2712
|
2635 commentBoxes.sortCommentBoxes = function () {
|
nicholas@2498
|
2636 this.boxes.sort(function (a, b) {
|
nicholas@2498
|
2637 return a.id - b.id;
|
nicholas@2498
|
2638 });
|
nicholas@2224
|
2639 };
|
nicholas@2224
|
2640
|
nicholas@2712
|
2641 commentBoxes.showCommentBoxes = function (inject, sort) {
|
nicholas@2224
|
2642 this.injectPoint = inject;
|
nicholas@2498
|
2643 if (sort) {
|
nicholas@2498
|
2644 this.sortCommentBoxes();
|
nicholas@2498
|
2645 }
|
nicholas@2708
|
2646 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2647 inject.appendChild(box.trackComment);
|
nicholas@2708
|
2648 });
|
nicholas@2224
|
2649 };
|
nicholas@2224
|
2650
|
nicholas@2712
|
2651 commentBoxes.deleteCommentBoxes = function () {
|
nicholas@2708
|
2652 if (this.injectPoint !== null) {
|
nicholas@2708
|
2653 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2654 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2708
|
2655 }, this);
|
nicholas@2224
|
2656 this.injectPoint = null;
|
nicholas@2224
|
2657 }
|
nicholas@2224
|
2658 this.boxes = [];
|
nicholas@2224
|
2659 };
|
nicholas@2725
|
2660 commentBoxes.highlightById = function (id) {
|
nicholas@2725
|
2661 if (id === undefined || typeof id !== "number" || id >= this.boxes.length) {
|
nicholas@2725
|
2662 console.log("Error - Invalid id");
|
nicholas@2725
|
2663 id = -1;
|
nicholas@2725
|
2664 }
|
nicholas@2725
|
2665 this.boxes.forEach(function (a) {
|
nicholas@2725
|
2666 if (a.id === id) {
|
nicholas@2725
|
2667 a.highlight(true);
|
nicholas@2725
|
2668 } else {
|
nicholas@2725
|
2669 a.highlight(false);
|
nicholas@2725
|
2670 }
|
nicholas@2725
|
2671 });
|
nicholas@2725
|
2672 };
|
nicholas@2712
|
2673 return commentBoxes;
|
nicholas@2712
|
2674 })();
|
nicholas@2498
|
2675
|
nicholas@2498
|
2676 this.commentQuestions = [];
|
nicholas@2498
|
2677
|
nicholas@2498
|
2678 this.commentBox = function (commentQuestion) {
|
nicholas@2498
|
2679 this.specification = commentQuestion;
|
nicholas@2498
|
2680 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2681 this.holder = document.createElement('div');
|
nicholas@2498
|
2682 this.holder.className = 'comment-div';
|
nicholas@2498
|
2683 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2684 this.string = document.createElement('span');
|
nicholas@2498
|
2685 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2686 // Create the HTML5 comment box 'textarea'
|
nicholas@2498
|
2687 this.textArea = document.createElement('textarea');
|
nicholas@2498
|
2688 this.textArea.rows = '4';
|
nicholas@2498
|
2689 this.textArea.cols = '100';
|
nicholas@2498
|
2690 this.textArea.className = 'trackComment';
|
nicholas@2498
|
2691 var br = document.createElement('br');
|
nicholas@2498
|
2692 // Add to the holder.
|
nicholas@2498
|
2693 this.holder.appendChild(this.string);
|
nicholas@2498
|
2694 this.holder.appendChild(br);
|
nicholas@2498
|
2695 this.holder.appendChild(this.textArea);
|
nicholas@2498
|
2696
|
nicholas@2498
|
2697 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2698 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2699 root.id = this.specification.id;
|
nicholas@2498
|
2700 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2701 console.log("Question: " + this.string.textContent);
|
nicholas@2498
|
2702 console.log("Response: " + root.textContent);
|
nicholas@2224
|
2703 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2704 question.textContent = this.string.textContent;
|
nicholas@2224
|
2705 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2706 response.textContent = this.textArea.value;
|
nicholas@2224
|
2707 root.appendChild(question);
|
nicholas@2224
|
2708 root.appendChild(response);
|
nicholas@2224
|
2709 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2710 return root;
|
nicholas@2498
|
2711 };
|
nicholas@2498
|
2712 this.resize = function () {
|
nicholas@2498
|
2713 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2714 if (boxwidth >= 600) {
|
nicholas@2498
|
2715 boxwidth = 600;
|
nicholas@2498
|
2716 } else if (boxwidth < 400) {
|
nicholas@2498
|
2717 boxwidth = 400;
|
nicholas@2498
|
2718 }
|
nicholas@2498
|
2719 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2720 this.textArea.style.width = boxwidth - 6 + "px";
|
nicholas@2498
|
2721 };
|
nicholas@2498
|
2722 this.resize();
|
nicholas@2498
|
2723 };
|
nicholas@2498
|
2724
|
nicholas@2498
|
2725 this.radioBox = function (commentQuestion) {
|
nicholas@2498
|
2726 this.specification = commentQuestion;
|
nicholas@2498
|
2727 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2728 this.holder = document.createElement('div');
|
nicholas@2498
|
2729 this.holder.className = 'comment-div';
|
nicholas@2498
|
2730 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2731 this.string = document.createElement('span');
|
nicholas@2498
|
2732 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2733 // Add to the holder.
|
nicholas@2498
|
2734 this.holder.appendChild(this.string);
|
nicholas@2498
|
2735 this.options = [];
|
nicholas@2498
|
2736 this.inputs = document.createElement('div');
|
nicholas@2711
|
2737 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2738
|
nicholas@2498
|
2739 var optCount = commentQuestion.options.length;
|
nicholas@2711
|
2740 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2741 var div = document.createElement('div');
|
nicholas@2711
|
2742 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2743
|
nicholas@2711
|
2744 var span = document.createElement('span');
|
nicholas@2711
|
2745 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2746 span.className = 'comment-radio-span';
|
nicholas@2711
|
2747 div.appendChild(span);
|
nicholas@2722
|
2748
|
nicholas@2498
|
2749 var input = document.createElement('input');
|
nicholas@2498
|
2750 input.type = 'radio';
|
nicholas@2498
|
2751 input.name = commentQuestion.id;
|
nicholas@2711
|
2752 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2753 input.className = 'comment-radio';
|
nicholas@2498
|
2754 div.appendChild(input);
|
nicholas@2722
|
2755
|
nicholas@2498
|
2756 this.inputs.appendChild(div);
|
nicholas@2498
|
2757 this.options.push(input);
|
nicholas@2498
|
2758 }
|
nicholas@2498
|
2759 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2760
|
nicholas@2498
|
2761 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2762 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2763 root.id = this.specification.id;
|
nicholas@2498
|
2764 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2765 var question = document.createElement('question');
|
nicholas@2498
|
2766 question.textContent = this.string.textContent;
|
nicholas@2498
|
2767 var response = document.createElement('response');
|
nicholas@2498
|
2768 var i = 0;
|
nicholas@2708
|
2769 while (this.options[i].checked === false) {
|
nicholas@2498
|
2770 i++;
|
nicholas@2498
|
2771 if (i >= this.options.length) {
|
nicholas@2498
|
2772 break;
|
nicholas@2498
|
2773 }
|
nicholas@2498
|
2774 }
|
nicholas@2498
|
2775 if (i >= this.options.length) {
|
nicholas@2498
|
2776 response.textContent = 'null';
|
nicholas@2498
|
2777 } else {
|
nicholas@2498
|
2778 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2498
|
2779 response.setAttribute('number', i);
|
nicholas@2498
|
2780 }
|
nicholas@2498
|
2781 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2782 console.log('Response: ' + response.textContent);
|
nicholas@2498
|
2783 root.appendChild(question);
|
nicholas@2498
|
2784 root.appendChild(response);
|
nicholas@2224
|
2785 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2786 return root;
|
nicholas@2498
|
2787 };
|
nicholas@2498
|
2788 this.resize = function () {
|
nicholas@2498
|
2789 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2790 if (boxwidth >= 600) {
|
nicholas@2498
|
2791 boxwidth = 600;
|
nicholas@2498
|
2792 } else if (boxwidth < 400) {
|
nicholas@2498
|
2793 boxwidth = 400;
|
nicholas@2498
|
2794 }
|
nicholas@2498
|
2795 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2796 };
|
nicholas@2498
|
2797 this.resize();
|
nicholas@2498
|
2798 };
|
nicholas@2498
|
2799
|
nicholas@2498
|
2800 this.checkboxBox = function (commentQuestion) {
|
nicholas@2498
|
2801 this.specification = commentQuestion;
|
nicholas@2498
|
2802 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2803 this.holder = document.createElement('div');
|
nicholas@2498
|
2804 this.holder.className = 'comment-div';
|
nicholas@2498
|
2805 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2806 this.string = document.createElement('span');
|
nicholas@2498
|
2807 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2808 // Add to the holder.
|
nicholas@2498
|
2809 this.holder.appendChild(this.string);
|
nicholas@2498
|
2810 this.options = [];
|
nicholas@2498
|
2811 this.inputs = document.createElement('div');
|
nicholas@2294
|
2812 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2813
|
nicholas@2498
|
2814 var optCount = commentQuestion.options.length;
|
nicholas@2498
|
2815 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2816 var div = document.createElement('div');
|
nicholas@2711
|
2817 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2818
|
nicholas@2711
|
2819 var span = document.createElement('span');
|
nicholas@2711
|
2820 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2821 span.className = 'comment-radio-span';
|
nicholas@2711
|
2822 div.appendChild(span);
|
nicholas@2722
|
2823
|
nicholas@2498
|
2824 var input = document.createElement('input');
|
nicholas@2498
|
2825 input.type = 'checkbox';
|
nicholas@2498
|
2826 input.name = commentQuestion.id;
|
nicholas@2498
|
2827 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2828 input.className = 'comment-radio';
|
nicholas@2498
|
2829 div.appendChild(input);
|
nicholas@2722
|
2830
|
nicholas@2498
|
2831 this.inputs.appendChild(div);
|
nicholas@2498
|
2832 this.options.push(input);
|
nicholas@2498
|
2833 }
|
nicholas@2498
|
2834 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2835
|
nicholas@2498
|
2836 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2837 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2838 root.id = this.specification.id;
|
nicholas@2498
|
2839 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2840 var question = document.createElement('question');
|
nicholas@2498
|
2841 question.textContent = this.string.textContent;
|
nicholas@2498
|
2842 root.appendChild(question);
|
nicholas@2498
|
2843 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2844 for (var i = 0; i < this.options.length; i++) {
|
nicholas@2498
|
2845 var response = document.createElement('response');
|
nicholas@2498
|
2846 response.textContent = this.options[i].checked;
|
nicholas@2498
|
2847 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
|
nicholas@2498
|
2848 root.appendChild(response);
|
nicholas@2498
|
2849 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
|
nicholas@2498
|
2850 }
|
nicholas@2224
|
2851 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2852 return root;
|
nicholas@2498
|
2853 };
|
nicholas@2498
|
2854 this.resize = function () {
|
nicholas@2498
|
2855 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2856 if (boxwidth >= 600) {
|
nicholas@2498
|
2857 boxwidth = 600;
|
nicholas@2498
|
2858 } else if (boxwidth < 400) {
|
nicholas@2498
|
2859 boxwidth = 400;
|
nicholas@2498
|
2860 }
|
nicholas@2498
|
2861 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2862 };
|
nicholas@2498
|
2863 this.resize();
|
nicholas@2498
|
2864 };
|
nicholas@2498
|
2865
|
n@2579
|
2866 this.sliderBox = function (commentQuestion) {
|
n@2579
|
2867 this.specification = commentQuestion;
|
n@2579
|
2868 this.holder = document.createElement("div");
|
n@2579
|
2869 this.holder.className = 'comment-div';
|
n@2579
|
2870 this.string = document.createElement("span");
|
n@2579
|
2871 this.string.innerHTML = commentQuestion.statement;
|
n@2579
|
2872 this.slider = document.createElement("input");
|
n@2579
|
2873 this.slider.type = "range";
|
n@2579
|
2874 this.slider.min = commentQuestion.min;
|
n@2579
|
2875 this.slider.max = commentQuestion.max;
|
n@2579
|
2876 this.slider.step = commentQuestion.step;
|
n@2579
|
2877 this.slider.value = commentQuestion.value;
|
n@2579
|
2878 var br = document.createElement('br');
|
n@2579
|
2879
|
n@2580
|
2880 var textHolder = document.createElement("div");
|
n@2580
|
2881 textHolder.className = "comment-slider-text-holder";
|
n@2580
|
2882
|
n@2580
|
2883 this.leftText = document.createElement("span");
|
n@2580
|
2884 this.leftText.textContent = commentQuestion.leftText;
|
n@2580
|
2885 this.rightText = document.createElement("span");
|
n@2580
|
2886 this.rightText.textContent = commentQuestion.rightText;
|
n@2580
|
2887 textHolder.appendChild(this.leftText);
|
n@2580
|
2888 textHolder.appendChild(this.rightText);
|
n@2580
|
2889
|
n@2579
|
2890 this.holder.appendChild(this.string);
|
n@2579
|
2891 this.holder.appendChild(br);
|
n@2579
|
2892 this.holder.appendChild(this.slider);
|
n@2580
|
2893 this.holder.appendChild(textHolder);
|
n@2579
|
2894
|
n@2579
|
2895 this.exportXMLDOM = function (storePoint) {
|
n@2579
|
2896 var root = storePoint.parent.document.createElement('comment');
|
n@2579
|
2897 root.id = this.specification.id;
|
n@2579
|
2898 root.setAttribute('type', this.specification.type);
|
n@2579
|
2899 console.log("Question: " + this.string.textContent);
|
n@2579
|
2900 console.log("Response: " + this.slider.value);
|
n@2579
|
2901 var question = storePoint.parent.document.createElement('question');
|
n@2579
|
2902 question.textContent = this.string.textContent;
|
n@2579
|
2903 var response = storePoint.parent.document.createElement('response');
|
n@2579
|
2904 response.textContent = this.slider.value;
|
n@2579
|
2905 root.appendChild(question);
|
n@2579
|
2906 root.appendChild(response);
|
n@2579
|
2907 storePoint.XMLDOM.appendChild(root);
|
n@2579
|
2908 return root;
|
n@2579
|
2909 };
|
n@2579
|
2910 this.resize = function () {
|
n@2579
|
2911 var boxwidth = (window.innerWidth - 100) / 2;
|
n@2579
|
2912 if (boxwidth >= 600) {
|
n@2579
|
2913 boxwidth = 600;
|
n@2579
|
2914 } else if (boxwidth < 400) {
|
n@2579
|
2915 boxwidth = 400;
|
n@2579
|
2916 }
|
n@2579
|
2917 this.holder.style.width = boxwidth + "px";
|
n@2579
|
2918 this.slider.style.width = boxwidth - 24 + "px";
|
n@2579
|
2919 };
|
n@2579
|
2920 this.resize();
|
n@2579
|
2921 };
|
n@2579
|
2922
|
nicholas@2498
|
2923 this.createCommentQuestion = function (element) {
|
nicholas@2498
|
2924 var node;
|
nicholas@2498
|
2925 if (element.type == 'question') {
|
nicholas@2498
|
2926 node = new this.commentBox(element);
|
nicholas@2498
|
2927 } else if (element.type == 'radio') {
|
nicholas@2498
|
2928 node = new this.radioBox(element);
|
nicholas@2498
|
2929 } else if (element.type == 'checkbox') {
|
nicholas@2498
|
2930 node = new this.checkboxBox(element);
|
n@2579
|
2931 } else if (element.type == 'slider') {
|
n@2579
|
2932 node = new this.sliderBox(element);
|
nicholas@2498
|
2933 }
|
nicholas@2498
|
2934 this.commentQuestions.push(node);
|
nicholas@2498
|
2935 return node;
|
nicholas@2498
|
2936 };
|
nicholas@2498
|
2937
|
nicholas@2498
|
2938 this.deleteCommentQuestions = function () {
|
nicholas@2498
|
2939 this.commentQuestions = [];
|
nicholas@2498
|
2940 };
|
nicholas@2498
|
2941
|
nicholas@2498
|
2942 this.outsideReferenceDOM = function (audioObject, index, inject) {
|
nicholas@2224
|
2943 this.parent = audioObject;
|
nicholas@2224
|
2944 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2945 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2498
|
2946 this.outsideReferenceHolder.setAttribute('track-id', index);
|
nicholas@2409
|
2947 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2948 this.outsideReferenceHolder.disabled = true;
|
nicholas@2708
|
2949 this.handleEvent = function (event) {
|
nicholas@2708
|
2950 audioEngineContext.play(this.parent.id);
|
nicholas@2224
|
2951 };
|
nicholas@2708
|
2952 this.outsideReferenceHolder.addEventListener("click", this);
|
nicholas@2224
|
2953 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2498
|
2954 this.enable = function () {
|
nicholas@2498
|
2955 if (this.parent.state == 1) {
|
nicholas@2224
|
2956 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2957 }
|
nicholas@2224
|
2958 };
|
nicholas@2498
|
2959 this.updateLoading = function (progress) {
|
nicholas@2498
|
2960 if (progress != 100) {
|
nicholas@2224
|
2961 progress = String(progress);
|
nicholas@2224
|
2962 progress = progress.split('.')[0];
|
nicholas@2498
|
2963 this.outsideReferenceHolder.textContent = progress + '%';
|
nicholas@2224
|
2964 } else {
|
nicholas@2409
|
2965 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2966 }
|
nicholas@2224
|
2967 };
|
nicholas@2498
|
2968 this.startPlayback = function () {
|
nicholas@2224
|
2969 // Called when playback has begun
|
nicholas@2224
|
2970 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2971 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2972 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2973 };
|
nicholas@2498
|
2974 this.stopPlayback = function () {
|
nicholas@2224
|
2975 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2976 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2977 };
|
nicholas@2498
|
2978 this.exportXMLDOM = function (audioObject) {
|
nicholas@2224
|
2979 return null;
|
nicholas@2224
|
2980 };
|
nicholas@2498
|
2981 this.getValue = function () {
|
nicholas@2224
|
2982 return 0;
|
nicholas@2224
|
2983 };
|
nicholas@2498
|
2984 this.getPresentedId = function () {
|
nicholas@2409
|
2985 return this.parent.specification.label || "Reference";
|
nicholas@2224
|
2986 };
|
nicholas@2498
|
2987 this.canMove = function () {
|
nicholas@2224
|
2988 return false;
|
nicholas@2224
|
2989 };
|
nicholas@2498
|
2990 this.error = function () {
|
nicholas@2498
|
2991 // audioObject has an error!!
|
nicholas@2224
|
2992 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2993 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2708
|
2994 };
|
nicholas@2708
|
2995 };
|
nicholas@2498
|
2996
|
nicholas@2712
|
2997 this.playhead = (function () {
|
nicholas@2722
|
2998 var playhead = {};
|
nicholas@2712
|
2999 playhead.object = document.createElement('div');
|
nicholas@2712
|
3000 playhead.object.className = 'playhead';
|
nicholas@2712
|
3001 playhead.object.align = 'left';
|
nicholas@2498
|
3002 var curTime = document.createElement('div');
|
nicholas@2498
|
3003 curTime.style.width = '50px';
|
nicholas@2712
|
3004 playhead.curTimeSpan = document.createElement('span');
|
nicholas@2712
|
3005 playhead.curTimeSpan.textContent = '00:00';
|
nicholas@2712
|
3006 curTime.appendChild(playhead.curTimeSpan);
|
nicholas@2712
|
3007 playhead.object.appendChild(curTime);
|
nicholas@2712
|
3008 playhead.scrubberTrack = document.createElement('div');
|
nicholas@2712
|
3009 playhead.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2498
|
3010
|
nicholas@2712
|
3011 playhead.scrubberHead = document.createElement('div');
|
nicholas@2712
|
3012 playhead.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2712
|
3013 playhead.scrubberTrack.appendChild(playhead.scrubberHead);
|
nicholas@2712
|
3014 playhead.object.appendChild(playhead.scrubberTrack);
|
nicholas@2498
|
3015
|
nicholas@2712
|
3016 playhead.timePerPixel = 0;
|
nicholas@2712
|
3017 playhead.maxTime = 0;
|
nicholas@2498
|
3018
|
nicholas@2712
|
3019 playhead.playbackObject = undefined;
|
nicholas@2498
|
3020
|
nicholas@2712
|
3021 playhead.setTimePerPixel = function (audioObject) {
|
nicholas@2498
|
3022 //maxTime must be in seconds
|
nicholas@2498
|
3023 this.playbackObject = audioObject;
|
nicholas@2498
|
3024 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2498
|
3025 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2498
|
3026 this.timePerPixel = this.maxTime / 490;
|
nicholas@2498
|
3027 if (this.maxTime < 60) {
|
nicholas@2498
|
3028 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
3029 } else {
|
nicholas@2498
|
3030 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
3031 }
|
nicholas@2498
|
3032 };
|
nicholas@2498
|
3033
|
nicholas@2712
|
3034 playhead.update = function () {
|
nicholas@2498
|
3035 // Update the playhead position, startPlay must be called
|
nicholas@2498
|
3036 if (this.timePerPixel > 0) {
|
nicholas@2498
|
3037 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2498
|
3038 if (time > 0 && time < this.maxTime) {
|
nicholas@2498
|
3039 var width = 490;
|
nicholas@2498
|
3040 var pix = Math.floor(time / this.timePerPixel);
|
nicholas@2498
|
3041 this.scrubberHead.style.left = pix + 'px';
|
nicholas@2498
|
3042 if (this.maxTime > 60.0) {
|
nicholas@2498
|
3043 var secs = time % 60;
|
nicholas@2498
|
3044 var mins = Math.floor((time - secs) / 60);
|
nicholas@2498
|
3045 secs = secs.toString();
|
nicholas@2498
|
3046 secs = secs.substr(0, 2);
|
nicholas@2498
|
3047 mins = mins.toString();
|
nicholas@2498
|
3048 this.curTimeSpan.textContent = mins + ':' + secs;
|
nicholas@2498
|
3049 } else {
|
nicholas@2498
|
3050 time = time.toString();
|
nicholas@2498
|
3051 this.curTimeSpan.textContent = time.substr(0, 4);
|
nicholas@2498
|
3052 }
|
nicholas@2498
|
3053 } else {
|
nicholas@2498
|
3054 this.scrubberHead.style.left = '0px';
|
nicholas@2498
|
3055 if (this.maxTime < 60) {
|
nicholas@2498
|
3056 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
3057 } else {
|
nicholas@2498
|
3058 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
3059 }
|
nicholas@2498
|
3060 }
|
nicholas@2498
|
3061 }
|
nicholas@2817
|
3062 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
3063 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2817
|
3064 }
|
nicholas@2498
|
3065 };
|
nicholas@2498
|
3066
|
nicholas@2712
|
3067 playhead.interval = undefined;
|
nicholas@2498
|
3068
|
nicholas@2712
|
3069 playhead.start = function () {
|
nicholas@2708
|
3070 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
3071 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2498
|
3072 }
|
nicholas@2498
|
3073 };
|
nicholas@2712
|
3074 playhead.stop = function () {
|
nicholas@2817
|
3075 this.timePerPixel = 0;
|
nicholas@2498
|
3076 };
|
nicholas@2712
|
3077 return playhead;
|
nicholas@2712
|
3078 })();
|
nicholas@2498
|
3079
|
nicholas@2712
|
3080 this.volume = (function () {
|
nicholas@2224
|
3081 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
3082 // Includes trackers on page-by-page data
|
nicholas@2224
|
3083 // Volume does NOT reset to 0dB on each page load
|
nicholas@2712
|
3084 var volume = {};
|
nicholas@2712
|
3085 volume.valueLin = 1.0;
|
nicholas@2712
|
3086 volume.valueDB = 0.0;
|
nicholas@2712
|
3087 volume.root = document.createElement('div');
|
nicholas@2712
|
3088 volume.root.id = 'master-volume-root';
|
nicholas@2712
|
3089 volume.object = document.createElement('div');
|
nicholas@2712
|
3090 volume.object.className = 'master-volume-holder-float';
|
nicholas@2712
|
3091 volume.object.appendChild(volume.root);
|
nicholas@2712
|
3092 volume.slider = document.createElement('input');
|
nicholas@2712
|
3093 volume.slider.id = 'master-volume-control';
|
nicholas@2712
|
3094 volume.slider.type = 'range';
|
nicholas@2712
|
3095 volume.valueText = document.createElement('span');
|
nicholas@2712
|
3096 volume.valueText.id = 'master-volume-feedback';
|
nicholas@2712
|
3097 volume.valueText.textContent = '0dB';
|
nicholas@2498
|
3098
|
nicholas@2712
|
3099 volume.slider.min = -60;
|
nicholas@2712
|
3100 volume.slider.max = 12;
|
nicholas@2712
|
3101 volume.slider.value = 0;
|
nicholas@2712
|
3102 volume.slider.step = 1;
|
nicholas@2712
|
3103 volume.handleEvent = function (event) {
|
nicholas@2951
|
3104 if (event.type == "mousemove" || event.type == "mouseup") {
|
nicholas@2669
|
3105 this.valueDB = Number(this.slider.value);
|
nicholas@2669
|
3106 this.valueLin = decibelToLinear(this.valueDB);
|
nicholas@2669
|
3107 this.valueText.textContent = this.valueDB + 'dB';
|
nicholas@2669
|
3108 audioEngineContext.outputGain.gain.value = this.valueLin;
|
nicholas@2951
|
3109 }
|
nicholas@2951
|
3110 if (event.type == "mouseup") {
|
nicholas@2669
|
3111 this.onmouseup();
|
nicholas@2669
|
3112 }
|
nicholas@2669
|
3113 this.slider.value = this.valueDB;
|
nicholas@2669
|
3114
|
nicholas@2669
|
3115 if (event.stopPropagation) {
|
nicholas@2669
|
3116 event.stopPropagation();
|
nicholas@2669
|
3117 }
|
nicholas@2711
|
3118 };
|
nicholas@2712
|
3119 volume.onmouseup = function () {
|
nicholas@2224
|
3120 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2708
|
3121 if (storePoint.length === 0) {
|
nicholas@2224
|
3122 storePoint = storage.document.createElement('metricresult');
|
nicholas@2498
|
3123 storePoint.setAttribute('name', 'volumeTracker');
|
nicholas@2224
|
3124 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2498
|
3125 } else {
|
nicholas@2224
|
3126 storePoint = storePoint[0];
|
nicholas@2224
|
3127 }
|
nicholas@2224
|
3128 var node = storage.document.createElement('movement');
|
nicholas@2498
|
3129 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
|
nicholas@2669
|
3130 node.setAttribute('volume', this.valueDB);
|
nicholas@2498
|
3131 node.setAttribute('format', 'dBFS');
|
nicholas@2224
|
3132 storePoint.appendChild(node);
|
nicholas@2711
|
3133 };
|
nicholas@2712
|
3134 volume.slider.addEventListener("mousemove", volume);
|
nicholas@2712
|
3135 volume.root.addEventListener("mouseup", volume);
|
nicholas@2498
|
3136
|
nicholas@2224
|
3137 var title = document.createElement('div');
|
nicholas@2224
|
3138 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
3139 title.style.fontSize = '0.75em';
|
nicholas@2224
|
3140 title.style.width = "100%";
|
nicholas@2224
|
3141 title.align = 'center';
|
nicholas@2712
|
3142 volume.root.appendChild(title);
|
nicholas@2498
|
3143
|
nicholas@2712
|
3144 volume.root.appendChild(volume.slider);
|
nicholas@2712
|
3145 volume.root.appendChild(volume.valueText);
|
nicholas@2498
|
3146
|
nicholas@2712
|
3147 volume.resize = function (event) {
|
nicholas@2352
|
3148 if (window.innerWidth < 1000) {
|
nicholas@2708
|
3149 this.object.className = "master-volume-holder-inline";
|
nicholas@2352
|
3150 } else {
|
nicholas@2352
|
3151 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
3152 }
|
nicholas@2708
|
3153 };
|
nicholas@2712
|
3154 return volume;
|
nicholas@2712
|
3155 })();
|
nicholas@2498
|
3156
|
nicholas@2778
|
3157 this.imageHolder = (function () {
|
nicholas@2778
|
3158 var imageController = {};
|
nicholas@2778
|
3159 imageController.root = document.createElement("div");
|
nicholas@2778
|
3160 imageController.root.id = "imageController";
|
nicholas@2778
|
3161 imageController.img = document.createElement("img");
|
nicholas@2778
|
3162 imageController.root.appendChild(imageController.img);
|
nicholas@2778
|
3163 imageController.setImage = function (src) {
|
nicholas@2778
|
3164 imageController.img.src = "";
|
n@2785
|
3165 if (typeof src !== "string" || src.length === undefined) {
|
nicholas@2778
|
3166 return;
|
nicholas@2778
|
3167 }
|
nicholas@2778
|
3168 imageController.img.src = src;
|
n@2785
|
3169 };
|
nicholas@2778
|
3170 return imageController;
|
nicholas@2778
|
3171 })();
|
nicholas@2778
|
3172
|
nicholas@2224
|
3173 this.calibrationModuleObject = null;
|
nicholas@2498
|
3174 this.calibrationModule = function () {
|
nicholas@2224
|
3175 // This creates an on-page calibration module
|
nicholas@2224
|
3176 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
3177 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
3178 // The calibration is a fixed state module
|
nicholas@2224
|
3179 this.calibrationNodes = [];
|
nicholas@2224
|
3180 this.holder = null;
|
nicholas@2498
|
3181 this.build = function (inject) {
|
nicholas@2224
|
3182 var f0 = 62.5;
|
nicholas@2224
|
3183 this.holder = document.createElement("div");
|
nicholas@2224
|
3184 this.holder.className = "calibration-holder";
|
nicholas@2224
|
3185 this.calibrationNodes = [];
|
nicholas@2498
|
3186 while (f0 < 20000) {
|
nicholas@2712
|
3187 /* jshint loopfunc: true */
|
nicholas@2224
|
3188 var obj = {
|
nicholas@2224
|
3189 root: document.createElement("div"),
|
nicholas@2224
|
3190 input: document.createElement("input"),
|
nicholas@2224
|
3191 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
3192 gain: audioContext.createGain(),
|
nicholas@2224
|
3193 f: f0,
|
nicholas@2224
|
3194 parent: this,
|
nicholas@2498
|
3195 handleEvent: function (event) {
|
nicholas@2498
|
3196 switch (event.type) {
|
nicholas@2224
|
3197 case "mouseenter":
|
nicholas@2224
|
3198 this.oscillator.start(0);
|
nicholas@2224
|
3199 break;
|
nicholas@2224
|
3200 case "mouseleave":
|
nicholas@2224
|
3201 this.oscillator.stop(0);
|
nicholas@2224
|
3202 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
3203 this.oscillator.connect(this.gain);
|
nicholas@2224
|
3204 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
3205 break;
|
nicholas@2224
|
3206 case "mousemove":
|
nicholas@2498
|
3207 var value = Math.pow(10, this.input.value / 20);
|
nicholas@2224
|
3208 if (this.f == 1000) {
|
nicholas@2224
|
3209 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
3210 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
3211 } else {
|
nicholas@2708
|
3212 this.gain.gain.value = value;
|
nicholas@2224
|
3213 }
|
nicholas@2224
|
3214 break;
|
nicholas@2224
|
3215 }
|
nicholas@2224
|
3216 },
|
nicholas@2498
|
3217 disconnect: function () {
|
nicholas@2224
|
3218 this.gain.disconnect();
|
nicholas@2224
|
3219 }
|
nicholas@2708
|
3220 };
|
nicholas@2224
|
3221 obj.root.className = "calibration-slider";
|
nicholas@2224
|
3222 obj.root.appendChild(obj.input);
|
nicholas@2224
|
3223 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
3224 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2498
|
3225 obj.gain.gain.value = Math.random() * 2;
|
nicholas@2224
|
3226 obj.input.value = obj.gain.gain.value;
|
nicholas@2498
|
3227 obj.input.setAttribute('orient', 'vertical');
|
nicholas@2224
|
3228 obj.input.type = "range";
|
nicholas@2593
|
3229 obj.input.min = -12;
|
nicholas@2593
|
3230 obj.input.max = 0;
|
nicholas@2224
|
3231 obj.input.step = 0.25;
|
nicholas@2224
|
3232 if (f0 != 1000) {
|
nicholas@2498
|
3233 obj.input.value = (Math.random() * 12) - 6;
|
nicholas@2224
|
3234 } else {
|
nicholas@2224
|
3235 obj.input.value = 0;
|
nicholas@2498
|
3236 obj.root.style.backgroundColor = "rgb(255,125,125)";
|
nicholas@2224
|
3237 }
|
nicholas@2498
|
3238 obj.input.addEventListener("mousemove", obj);
|
nicholas@2498
|
3239 obj.input.addEventListener("mouseenter", obj);
|
nicholas@2498
|
3240 obj.input.addEventListener("mouseleave", obj);
|
nicholas@2498
|
3241 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
|
nicholas@2224
|
3242 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
3243 this.calibrationNodes.push(obj);
|
nicholas@2224
|
3244 this.holder.appendChild(obj.root);
|
nicholas@2224
|
3245 f0 *= 2;
|
nicholas@2224
|
3246 }
|
nicholas@2224
|
3247 inject.appendChild(this.holder);
|
nicholas@2708
|
3248 };
|
nicholas@2498
|
3249 this.collect = function () {
|
nicholas@2708
|
3250 this.calibrationNodes.forEach(function (obj) {
|
nicholas@2224
|
3251 var node = storage.document.createElement("calibrationresult");
|
nicholas@2498
|
3252 node.setAttribute("frequency", obj.f);
|
nicholas@2498
|
3253 node.setAttribute("range-min", obj.input.min);
|
nicholas@2498
|
3254 node.setAttribute("range-max", obj.input.max);
|
nicholas@2498
|
3255 node.setAttribute("gain-lin", obj.gain.gain.value);
|
nicholas@2224
|
3256 this.storeDOM.appendChild(node);
|
nicholas@2708
|
3257 }, this);
|
nicholas@2708
|
3258 };
|
nicholas@2708
|
3259 };
|
nicholas@2498
|
3260
|
nicholas@2498
|
3261
|
nicholas@2498
|
3262 // Global Checkers
|
nicholas@2498
|
3263 // These functions will help enforce the checkers
|
n@2789
|
3264 this.checkHiddenAnchor = function (message) {
|
nicholas@2708
|
3265 var anchors = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3266 return ao.specification.type === "anchor";
|
nicholas@2708
|
3267 });
|
nicholas@2708
|
3268 var state = anchors.some(function (ao) {
|
nicholas@2708
|
3269 return (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3270 });
|
nicholas@2708
|
3271 if (state) {
|
nicholas@2708
|
3272 console.log('Anchor node not below marker value');
|
n@2789
|
3273 if (message) {
|
n@2789
|
3274 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3275 } else {
|
n@2789
|
3276 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3277 }
|
nicholas@2708
|
3278 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2708
|
3279 return false;
|
nicholas@2498
|
3280 }
|
nicholas@2498
|
3281 return true;
|
nicholas@2498
|
3282 };
|
nicholas@2498
|
3283
|
n@2789
|
3284 this.checkHiddenReference = function (message) {
|
nicholas@2708
|
3285 var references = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3286 return ao.specification.type === "reference";
|
nicholas@2708
|
3287 });
|
nicholas@2708
|
3288 var state = references.some(function (ao) {
|
nicholas@2708
|
3289 return (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3290 });
|
nicholas@2708
|
3291 if (state) {
|
nicholas@2708
|
3292 console.log('Reference node not below marker value');
|
n@2789
|
3293 if (message) {
|
n@2789
|
3294 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3295 } else {
|
n@2789
|
3296 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3297 }
|
nicholas@2708
|
3298 this.storeErrorNode('Reference node not below marker value');
|
nicholas@2708
|
3299 return false;
|
nicholas@2498
|
3300 }
|
nicholas@2498
|
3301 return true;
|
nicholas@2498
|
3302 };
|
nicholas@2498
|
3303
|
n@2789
|
3304 this.checkFragmentsFullyPlayed = function (message) {
|
nicholas@2498
|
3305 // Checks the entire file has been played back
|
nicholas@2498
|
3306 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2498
|
3307 if (audioEngineContext.loopPlayback) {
|
nicholas@2498
|
3308 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2498
|
3309 return true;
|
nicholas@2498
|
3310 }
|
nicholas@2498
|
3311 var check_pass = true;
|
nicholas@2708
|
3312 var error_obj = [],
|
nicholas@2708
|
3313 i;
|
nicholas@2708
|
3314 for (i = 0; i < audioEngineContext.audioObjects.length; i++) {
|
nicholas@2498
|
3315 var object = audioEngineContext.audioObjects[i];
|
nicholas@2498
|
3316 var time = object.buffer.buffer.duration;
|
nicholas@2498
|
3317 var metric = object.metric;
|
nicholas@2498
|
3318 var passed = false;
|
nicholas@2498
|
3319 for (var j = 0; j < metric.listenTracker.length; j++) {
|
nicholas@2498
|
3320 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
|
nicholas@2498
|
3321 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2498
|
3322 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2498
|
3323 var delta = stop_time - start_time;
|
nicholas@2498
|
3324 if (delta >= time) {
|
nicholas@2498
|
3325 passed = true;
|
nicholas@2498
|
3326 break;
|
nicholas@2498
|
3327 }
|
nicholas@2498
|
3328 }
|
nicholas@2708
|
3329 if (passed === false) {
|
nicholas@2498
|
3330 check_pass = false;
|
nicholas@2498
|
3331 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3332 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3333 }
|
nicholas@2498
|
3334 }
|
nicholas@2708
|
3335 if (check_pass === false) {
|
nicholas@2498
|
3336 var str_start = "You have not completely listened to fragments ";
|
nicholas@2708
|
3337 for (i = 0; i < error_obj.length; i++) {
|
nicholas@2498
|
3338 str_start += error_obj[i];
|
nicholas@2498
|
3339 if (i != error_obj.length - 1) {
|
nicholas@2498
|
3340 str_start += ', ';
|
nicholas@2498
|
3341 }
|
nicholas@2498
|
3342 }
|
nicholas@2498
|
3343 str_start += ". Please keep listening";
|
n@2789
|
3344 console.log(str_start);
|
n@2789
|
3345 this.storeErrorNode(str_start);
|
n@2789
|
3346 if (message) {
|
n@2789
|
3347 str_start = message;
|
n@2789
|
3348 }
|
nicholas@2498
|
3349 interfaceContext.lightbox.post("Error", str_start);
|
nicholas@2444
|
3350 return false;
|
nicholas@2498
|
3351 }
|
nicholas@2444
|
3352 return true;
|
nicholas@2498
|
3353 };
|
n@2789
|
3354 this.checkAllMoved = function (message) {
|
nicholas@2498
|
3355 var str = "You have not moved ";
|
nicholas@2498
|
3356 var failed = [];
|
nicholas@2708
|
3357 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3358 if (ao.metric.wasMoved === false && ao.interfaceDOM.canMove() === true) {
|
nicholas@2498
|
3359 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3360 }
|
nicholas@2708
|
3361 }, this);
|
nicholas@2708
|
3362 if (failed.length === 0) {
|
nicholas@2498
|
3363 return true;
|
nicholas@2498
|
3364 } else if (failed.length == 1) {
|
nicholas@2498
|
3365 str += 'track ' + failed[0];
|
nicholas@2498
|
3366 } else {
|
nicholas@2498
|
3367 str += 'tracks ';
|
nicholas@2498
|
3368 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3369 str += failed[i] + ', ';
|
nicholas@2498
|
3370 }
|
nicholas@2498
|
3371 str += 'and ' + failed[i];
|
nicholas@2498
|
3372 }
|
nicholas@2498
|
3373 str += '.';
|
nicholas@2498
|
3374 console.log(str);
|
nicholas@2224
|
3375 this.storeErrorNode(str);
|
n@2789
|
3376 if (message) {
|
n@2789
|
3377 str = message;
|
n@2789
|
3378 }
|
n@2789
|
3379 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3380 return false;
|
nicholas@2498
|
3381 };
|
n@2789
|
3382 this.checkAllPlayed = function (message) {
|
nicholas@2498
|
3383 var str = "You have not played ";
|
nicholas@2498
|
3384 var failed = [];
|
nicholas@2708
|
3385 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3386 if (ao.metric.wasListenedTo === false) {
|
nicholas@2498
|
3387 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3388 }
|
nicholas@2708
|
3389 }, this);
|
nicholas@2708
|
3390 if (failed.length === 0) {
|
nicholas@2498
|
3391 return true;
|
nicholas@2498
|
3392 } else if (failed.length == 1) {
|
nicholas@2498
|
3393 str += 'track ' + failed[0];
|
nicholas@2498
|
3394 } else {
|
nicholas@2498
|
3395 str += 'tracks ';
|
nicholas@2498
|
3396 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3397 str += failed[i] + ', ';
|
nicholas@2498
|
3398 }
|
nicholas@2498
|
3399 str += 'and ' + failed[i];
|
nicholas@2498
|
3400 }
|
nicholas@2498
|
3401 str += '.';
|
nicholas@2498
|
3402 console.log(str);
|
nicholas@2224
|
3403 this.storeErrorNode(str);
|
n@2789
|
3404 if (message) {
|
n@2789
|
3405 str = message;
|
n@2789
|
3406 }
|
n@2789
|
3407 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3408 return false;
|
nicholas@2498
|
3409 };
|
n@2789
|
3410 this.checkAllCommented = function (message) {
|
nicholas@2540
|
3411 var str = "You have not commented on all the fragments.";
|
nicholas@2540
|
3412 var cont = true,
|
nicholas@2540
|
3413 boxes = this.commentBoxes.boxes,
|
nicholas@2540
|
3414 numBoxes = boxes.length,
|
nicholas@2540
|
3415 i;
|
nicholas@2540
|
3416 for (i = 0; i < numBoxes; i++) {
|
nicholas@2540
|
3417 if (boxes[i].trackCommentBox.value === "") {
|
nicholas@2540
|
3418 console.log(str);
|
nicholas@2540
|
3419 this.storeErrorNode(str);
|
n@2789
|
3420 if (message) {
|
n@2789
|
3421 str = message;
|
n@2789
|
3422 }
|
n@2789
|
3423 interfaceContext.lightbox.post("Error", str);
|
nicholas@2540
|
3424 return false;
|
nicholas@2540
|
3425 }
|
nicholas@2540
|
3426 }
|
nicholas@2540
|
3427 return true;
|
nicholas@2708
|
3428 };
|
n@2789
|
3429 this.checkScaleRange = function (message) {
|
nicholas@2310
|
3430 var page = testState.getCurrentTestPage();
|
nicholas@2708
|
3431 var interfaceObject = page.interfaces;
|
nicholas@2310
|
3432 var state = true;
|
nicholas@2310
|
3433 var str = "Please keep listening. ";
|
nicholas@2708
|
3434 if (interfaceObject === undefined) {
|
nicholas@2708
|
3435 return true;
|
nicholas@2310
|
3436 }
|
nicholas@2708
|
3437 interfaceObject = interfaceObject[0];
|
nicholas@2708
|
3438 var scales = (function () {
|
nicholas@2708
|
3439 var scaleRange = interfaceObject.options.find(function (a) {
|
nicholas@2708
|
3440 return a.name == "scalerange";
|
nicholas@2708
|
3441 });
|
nicholas@2708
|
3442 return {
|
nicholas@2708
|
3443 min: scaleRange.min,
|
nicholas@2708
|
3444 max: scaleRange.max
|
nicholas@2708
|
3445 };
|
nicholas@2708
|
3446 })();
|
nicholas@2708
|
3447 var range = audioEngineContext.audioObjects.reduce(function (a, b) {
|
nicholas@2742
|
3448 var v = b.interfaceDOM.getValue() * 100.0;
|
nicholas@2708
|
3449 return {
|
nicholas@2708
|
3450 min: Math.min(a.min, v),
|
nicholas@2708
|
3451 max: Math.max(a.max, v)
|
nicholas@2712
|
3452 };
|
nicholas@2708
|
3453 }, {
|
nicholas@2708
|
3454 min: 100,
|
nicholas@2708
|
3455 max: 0
|
nicholas@2708
|
3456 });
|
nicholas@2708
|
3457 if (range.min > scales.min) {
|
nicholas@2742
|
3458 str += "At least one fragment must be below the " + scales.min + " mark.";
|
nicholas@2708
|
3459 state = false;
|
nicholas@2712
|
3460 } else if (range.max < scales.max) {
|
nicholas@2742
|
3461 str += "At least one fragment must be above the " + scales.max + " mark.";
|
nicholas@2310
|
3462 state = false;
|
nicholas@2310
|
3463 }
|
nicholas@2708
|
3464 if (state === false) {
|
nicholas@2310
|
3465 console.log(str);
|
nicholas@2310
|
3466 this.storeErrorNode(str);
|
n@2789
|
3467 if (message) {
|
n@2789
|
3468 str = message;
|
n@2789
|
3469 }
|
nicholas@2498
|
3470 interfaceContext.lightbox.post("Error", str);
|
nicholas@2310
|
3471 }
|
nicholas@2310
|
3472 return state;
|
nicholas@2708
|
3473 };
|
nicholas@2826
|
3474 this.checkFragmentMinPlays = function () {
|
nicholas@2826
|
3475 var failedObjects = audioEngineContext.audioObjects.filter(function (a) {
|
nicholas@2826
|
3476 var minPlays = a.specification.minNumberPlays || a.specification.parent.minNumberPlays || specification.minNumberPlays;
|
nicholas@2826
|
3477 if (minPlays === undefined || a.numberOfPlays >= minPlays) {
|
nicholas@2826
|
3478 return false;
|
nicholas@2826
|
3479 }
|
nicholas@2826
|
3480 return true;
|
nicholas@2826
|
3481 });
|
nicholas@2826
|
3482 if (failedObjects.length === 0) {
|
nicholas@2827
|
3483 return true;
|
nicholas@2826
|
3484 }
|
nicholas@2826
|
3485 var failedString = [];
|
nicholas@2826
|
3486 failedObjects.forEach(function (a) {
|
nicholas@2826
|
3487 failedString.push(a.interfaceDOM.getPresentedId());
|
nicholas@2826
|
3488 });
|
nicholas@2826
|
3489 var str = "You have not played fragments " + failedString.join(", ") + " enough. Please keep listening";
|
nicholas@2826
|
3490 interfaceContext.lightbox.post("Message", str);
|
nicholas@2826
|
3491 this.storeErrorNode(str);
|
nicholas@2827
|
3492 return false;
|
nicholas@2826
|
3493 };
|
nicholas@2826
|
3494
|
nicholas@2498
|
3495
|
nicholas@2849
|
3496 this.sortFragmentsByScore = function () {
|
nicholas@2849
|
3497 var elements = audioEngineContext.audioObjects.filter(function (elem) {
|
nicholas@2849
|
3498 return elem.specification.type !== "outside-reference";
|
nicholas@2849
|
3499 });
|
nicholas@2849
|
3500 var indexes = [];
|
nicholas@2849
|
3501 var i = 0;
|
nicholas@2849
|
3502 while (indexes.push(i++) < elements.length);
|
nicholas@2849
|
3503 return indexes.sort(function (x, y) {
|
nicholas@2849
|
3504 var a = elements[x].interfaceDOM.getValue();
|
nicholas@2849
|
3505 var b = elements[y].interfaceDOM.getValue();
|
nicholas@2849
|
3506 if (a > b) {
|
nicholas@2849
|
3507 return 1;
|
nicholas@2849
|
3508 } else if (a < b) {
|
nicholas@2849
|
3509 return -1;
|
nicholas@2849
|
3510 }
|
nicholas@2849
|
3511 return 0;
|
nicholas@2849
|
3512 }, elements[0].interfaceDOM.getValue());
|
nicholas@2849
|
3513 };
|
nicholas@2849
|
3514
|
nicholas@2498
|
3515 this.storeErrorNode = function (errorMessage) {
|
nicholas@2224
|
3516 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
3517 var node = storage.document.createElement('error');
|
nicholas@2498
|
3518 node.setAttribute('time', time);
|
nicholas@2224
|
3519 node.textContent = errorMessage;
|
nicholas@2224
|
3520 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
3521 };
|
nicholas@2595
|
3522
|
nicholas@2595
|
3523 this.getLabel = function (labelType, index, labelStart) {
|
nicholas@2595
|
3524 /*
|
nicholas@2595
|
3525 Get the correct label based on type, index and offset
|
nicholas@2595
|
3526 */
|
nicholas@2595
|
3527
|
nicholas@2595
|
3528 function calculateLabel(labelType, index, offset) {
|
nicholas@2595
|
3529 if (labelType == "none") {
|
nicholas@2595
|
3530 return "";
|
nicholas@2595
|
3531 }
|
nicholas@2595
|
3532 switch (labelType) {
|
nicholas@2595
|
3533 case "letter":
|
nicholas@2596
|
3534 return String.fromCharCode((index + offset) % 26 + 97);
|
nicholas@2595
|
3535 case "capital":
|
nicholas@2607
|
3536 return String.fromCharCode((index + offset) % 26 + 65);
|
nicholas@2625
|
3537 case "samediff":
|
nicholas@2708
|
3538 if (index === 0) {
|
nicholas@2625
|
3539 return "Same";
|
nicholas@2625
|
3540 } else if (index == 1) {
|
nicholas@2625
|
3541 return "Difference";
|
nicholas@2625
|
3542 }
|
nicholas@2708
|
3543 return "";
|
nicholas@2595
|
3544 case "number":
|
nicholas@2595
|
3545 return String(index + offset);
|
nicholas@2595
|
3546 default:
|
nicholas@2595
|
3547 return "";
|
nicholas@2595
|
3548 }
|
nicholas@2595
|
3549 }
|
nicholas@2595
|
3550
|
nicholas@2708
|
3551 if (typeof labelStart !== "string" || labelStart.length === 0) {
|
nicholas@2595
|
3552 labelStart = String.fromCharCode(0);
|
nicholas@2595
|
3553 }
|
nicholas@2595
|
3554
|
nicholas@2595
|
3555 switch (labelType) {
|
nicholas@2595
|
3556 case "letter":
|
nicholas@2595
|
3557 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3558 if (labelStart < 97 || labelStart > 122) {
|
nicholas@2595
|
3559 labelStart = 97;
|
nicholas@2595
|
3560 }
|
nicholas@2595
|
3561 labelStart -= 97;
|
nicholas@2595
|
3562 break;
|
nicholas@2595
|
3563 case "capital":
|
nicholas@2595
|
3564 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3565 if (labelStart < 65 || labelStart > 90) {
|
nicholas@2595
|
3566 labelStart = 65;
|
nicholas@2595
|
3567 }
|
nicholas@2595
|
3568 labelStart -= 65;
|
nicholas@2595
|
3569 break;
|
nicholas@2595
|
3570 case "number":
|
nicholas@2608
|
3571 labelStart = Number(labelStart);
|
nicholas@2608
|
3572 if (!isFinite(labelStart)) {
|
nicholas@2595
|
3573 labelStart = 1;
|
nicholas@2595
|
3574 }
|
nicholas@2595
|
3575 break;
|
nicholas@2595
|
3576 default:
|
nicholas@2596
|
3577 labelStart = 0;
|
nicholas@2595
|
3578 }
|
nicholas@2595
|
3579 if (typeof index == "number") {
|
nicholas@2595
|
3580 return calculateLabel(labelType, index, labelStart);
|
nicholas@2595
|
3581 } else if (index.length && index.length > 0) {
|
nicholas@2595
|
3582 var a = [],
|
nicholas@2595
|
3583 l = index.length,
|
nicholas@2595
|
3584 i;
|
nicholas@2595
|
3585 for (i = 0; i < l; i++) {
|
nicholas@2595
|
3586 a[i] = calculateLabel(labelType, index[i], labelStart);
|
nicholas@2595
|
3587 }
|
nicholas@2595
|
3588 return a;
|
nicholas@2595
|
3589 } else {
|
nicholas@2595
|
3590 throw ("Invalid arguments");
|
nicholas@2595
|
3591 }
|
nicholas@2708
|
3592 };
|
nicholas@2649
|
3593
|
nicholas@2649
|
3594 this.getCombinedInterfaces = function (page) {
|
nicholas@2649
|
3595 // Combine the interfaces with the global interface nodes
|
nicholas@2649
|
3596 var global = specification.interfaces,
|
nicholas@2649
|
3597 local = page.interfaces;
|
nicholas@2649
|
3598 local.forEach(function (locInt) {
|
nicholas@2649
|
3599 // Iterate through the options nodes
|
nicholas@2649
|
3600 var addList = [];
|
nicholas@2649
|
3601 global.options.forEach(function (gopt) {
|
nicholas@2649
|
3602 var lopt = locInt.options.find(function (lopt) {
|
nicholas@2649
|
3603 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
|
nicholas@2649
|
3604 });
|
nicholas@2649
|
3605 if (!lopt) {
|
nicholas@2649
|
3606 // Global option doesn't exist locally
|
nicholas@2649
|
3607 addList.push(gopt);
|
nicholas@2649
|
3608 }
|
nicholas@2649
|
3609 });
|
nicholas@2649
|
3610 locInt.options = locInt.options.concat(addList);
|
nicholas@2649
|
3611 if (!locInt.scales && global.scales) {
|
nicholas@2649
|
3612 // Use the global default scales
|
nicholas@2649
|
3613 locInt.scales = global.scales;
|
nicholas@2649
|
3614 }
|
nicholas@2649
|
3615 });
|
nicholas@2649
|
3616 return local;
|
nicholas@2708
|
3617 };
|
nicholas@2224
|
3618 }
|
nicholas@2224
|
3619
|
nicholas@2498
|
3620 function Storage() {
|
nicholas@2498
|
3621 // Holds results in XML format until ready for collection
|
nicholas@2498
|
3622 this.globalPreTest = null;
|
nicholas@2498
|
3623 this.globalPostTest = null;
|
nicholas@2498
|
3624 this.testPages = [];
|
nicholas@2498
|
3625 this.document = null;
|
nicholas@2498
|
3626 this.root = null;
|
nicholas@2498
|
3627 this.state = 0;
|
nicholas@2733
|
3628 var pFilenamePrefix = "save";
|
nicholas@2498
|
3629
|
nicholas@2498
|
3630 this.initialise = function (existingStore) {
|
nicholas@2708
|
3631 if (existingStore === undefined) {
|
nicholas@2224
|
3632 // We need to get the sessionKey
|
nicholas@2510
|
3633 this.SessionKey.requestKey();
|
nicholas@2498
|
3634 this.document = document.implementation.createDocument(null, "waetresult", null);
|
nicholas@2224
|
3635 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3636 var projectDocument = specification.projectXML;
|
nicholas@2708
|
3637 projectDocument.setAttribute('file-name', specification.url);
|
nicholas@2708
|
3638 projectDocument.setAttribute('url', qualifyURL(specification.url));
|
nicholas@2224
|
3639 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3640 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3641 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3642 } else {
|
nicholas@2224
|
3643 this.document = existingStore;
|
nicholas@2294
|
3644 this.root = existingStore.firstChild;
|
nicholas@2224
|
3645 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3646 }
|
nicholas@2708
|
3647 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3648 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
|
nicholas@2498
|
3649 }
|
nicholas@2708
|
3650 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3651 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
|
nicholas@2498
|
3652 }
|
nicholas@2498
|
3653 };
|
nicholas@2498
|
3654
|
n@2967
|
3655 this.SessionKey = (function (parent) {
|
n@2970
|
3656 var returnURL = "";
|
n@2970
|
3657 if (window.returnURL !== undefined) {
|
n@2970
|
3658 returnURL = String(window.returnURL);
|
n@2970
|
3659 }
|
n@2970
|
3660
|
n@2967
|
3661 function postUpdate() {
|
n@2967
|
3662 // Return a new promise.
|
n@2967
|
3663 var hold = document.createElement("div");
|
n@2967
|
3664 var clone = parent.root.cloneNode(true);
|
n@2967
|
3665 hold.appendChild(clone);
|
n@2967
|
3666 return new Promise(function (resolve, reject) {
|
n@2967
|
3667 // Do the usual XHR stuff
|
n@2977
|
3668 console.log("Requested save...");
|
n@2967
|
3669 var req = new XMLHttpRequest();
|
n@2973
|
3670 req.open("POST", returnURL + "php/save.php?key=" + sessionKey + "&saveFilenamePrefix=" + parent.filenamePrefix);
|
n@2967
|
3671 req.setRequestHeader('Content-Type', 'text/xml');
|
n@2967
|
3672
|
n@2967
|
3673 req.onload = function () {
|
n@2967
|
3674 // This is called even on 404 etc
|
n@2967
|
3675 // so check the status
|
n@2967
|
3676 if (this.status >= 300) {
|
n@2967
|
3677 console.log("WARNING - Could not update at this time");
|
n@2967
|
3678 } else {
|
n@2967
|
3679 var parser = new DOMParser();
|
n@2974
|
3680 var xmlDoc = parser.parseFromString(req.responseText, "application/xml");
|
n@2967
|
3681 var response = xmlDoc.getElementsByTagName('response')[0];
|
n@2967
|
3682 if (response.getAttribute("state") == "OK") {
|
n@2967
|
3683 var file = response.getElementsByTagName("file")[0];
|
n@2967
|
3684 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
n@2967
|
3685 resolve(true);
|
n@2967
|
3686 } else {
|
n@2967
|
3687 var message = response.getElementsByTagName("message");
|
n@2967
|
3688 console.log("Intermediate save: Error! " + message.textContent);
|
n@2967
|
3689 reject("Intermediate save: Error! " + message.textContent);
|
n@2967
|
3690 }
|
n@2967
|
3691 }
|
n@2967
|
3692 };
|
n@2967
|
3693
|
n@2967
|
3694 // Handle network errors
|
n@2967
|
3695 req.onerror = function () {
|
n@2967
|
3696 reject(Error("Network Error"));
|
n@2967
|
3697 };
|
n@2967
|
3698
|
n@2967
|
3699 // Make the request
|
n@2967
|
3700 req.send([hold.innerHTML]);
|
n@2967
|
3701 });
|
n@2979
|
3702 }
|
n@2967
|
3703
|
n@2967
|
3704 function keyPromise() {
|
n@2967
|
3705 return new Promise(function (resolve, reject) {
|
n@2967
|
3706 var req = new XMLHttpRequest();
|
n@2967
|
3707 req.open("GET", returnURL + "php/requestKey.php?saveFilenamePrefix=" + parent.filenamePrefix, true);
|
n@2967
|
3708 req.onload = function () {
|
n@2967
|
3709 // This is called even on 404 etc
|
n@2967
|
3710 // so check the status
|
n@2967
|
3711 if (req.status == 200) {
|
n@2967
|
3712 // Resolve the promise with the response text
|
n@2967
|
3713 resolve(req.response);
|
n@2967
|
3714 } else {
|
n@2967
|
3715 // Otherwise reject with the status text
|
n@2967
|
3716 // which will hopefully be a meaningful error
|
n@2967
|
3717 reject(Error(req.statusText));
|
n@2967
|
3718 }
|
n@2967
|
3719 };
|
n@2967
|
3720
|
n@2967
|
3721 // Handle network errors
|
n@2967
|
3722 req.onerror = function () {
|
n@2967
|
3723 reject(Error("Network Error"));
|
n@2967
|
3724 };
|
n@2967
|
3725
|
n@2967
|
3726 req.send();
|
n@2979
|
3727 });
|
n@2967
|
3728 }
|
n@2967
|
3729
|
n@2967
|
3730 var requestChains = null;
|
n@2969
|
3731 var sessionKey = null;
|
n@2967
|
3732 var object = {};
|
n@2967
|
3733
|
n@2967
|
3734 Object.defineProperties(object, {
|
n@2967
|
3735 "key": {
|
n@2967
|
3736 "get": function () {
|
n@2969
|
3737 return sessionKey;
|
n@2967
|
3738 },
|
n@2967
|
3739 "set": function (a) {
|
n@2979
|
3740 throw ("Cannot set read-only property");
|
n@2967
|
3741 }
|
n@2967
|
3742 },
|
n@2967
|
3743 "request": {
|
n@2967
|
3744 "value": new XMLHttpRequest()
|
n@2967
|
3745 },
|
n@2967
|
3746 "parent": {
|
n@2967
|
3747 "value": parent
|
n@2967
|
3748 },
|
n@2967
|
3749 "requestKey": {
|
n@2967
|
3750 "value": function () {
|
n@2967
|
3751 requestChains = keyPromise().then(function (response) {
|
n@2967
|
3752 function throwerror() {
|
n@2969
|
3753 sessionKey = null;
|
n@2967
|
3754 throw ("An unspecified error occured, no server key could be generated");
|
n@2967
|
3755 }
|
n@2967
|
3756 var parse = new DOMParser();
|
n@2967
|
3757 var xml = parse.parseFromString(response, "text/xml");
|
n@2971
|
3758 if (response.length === 0) {
|
n@2967
|
3759 throwerror();
|
n@2967
|
3760 }
|
n@2967
|
3761 if (xml.getElementsByTagName("state").length > 0) {
|
n@2967
|
3762 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
n@2969
|
3763 sessionKey = xml.getAllElementsByTagName("key")[0].textContent;
|
n@2972
|
3764 parent.root.setAttribute("key", sessionKey);
|
n@2972
|
3765 parent.root.setAttribute("state", "empty");
|
n@2967
|
3766 return (true);
|
n@2967
|
3767 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
|
n@2969
|
3768 sessionKey = null;
|
n@2967
|
3769 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
|
n@2969
|
3770 return (false);
|
n@2967
|
3771 }
|
n@2967
|
3772 } else {
|
n@2967
|
3773 throwerror();
|
n@2967
|
3774 }
|
n@2967
|
3775 return (true);
|
n@2967
|
3776 });
|
n@2967
|
3777 }
|
n@2967
|
3778 },
|
n@2968
|
3779 "update": {
|
n@2968
|
3780 "value": function () {
|
n@2979
|
3781 if (this.key === null || requestChains === undefined) {
|
n@2968
|
3782 throw ("Cannot save as key == null");
|
n@2968
|
3783 }
|
n@2968
|
3784 this.parent.root.setAttribute("state", "update");
|
n@2978
|
3785 requestChains = requestChains.then(postUpdate);
|
n@2967
|
3786 }
|
n@2967
|
3787 },
|
n@2968
|
3788 "finish": {
|
n@2968
|
3789 "value": function () {
|
n@2979
|
3790 if (this.key === null || requestChains === undefined) {
|
n@2968
|
3791 throw ("Cannot save as key == null");
|
n@2968
|
3792 }
|
n@2968
|
3793 this.parent.finish();
|
n@2975
|
3794 return requestChains.then(postUpdate()).then(function () {
|
n@2968
|
3795 console.log("OK");
|
n@2968
|
3796 }, function () {
|
n@2968
|
3797 createProjectSave("local");
|
n@2979
|
3798 });
|
n@2967
|
3799 }
|
n@2967
|
3800 }
|
n@2968
|
3801 });
|
n@2967
|
3802 return object;
|
n@2967
|
3803 })(this);
|
n@2967
|
3804 /*
|
nicholas@2224
|
3805 this.SessionKey = {
|
nicholas@2224
|
3806 key: null,
|
nicholas@2224
|
3807 request: new XMLHttpRequest(),
|
nicholas@2224
|
3808 parent: this,
|
nicholas@2498
|
3809 handleEvent: function () {
|
n@2967
|
3810
|
nicholas@2224
|
3811 },
|
nicholas@2510
|
3812 requestKey: function () {
|
n@2967
|
3813
|
nicholas@2510
|
3814 },
|
nicholas@2498
|
3815 update: function () {
|
nicholas@2708
|
3816 if (this.key === null) {
|
nicholas@2357
|
3817 console.log("Cannot save as key == null");
|
nicholas@2357
|
3818 return;
|
nicholas@2357
|
3819 }
|
nicholas@2498
|
3820 this.parent.root.setAttribute("state", "update");
|
nicholas@2224
|
3821 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3822 var returnURL = "";
|
nicholas@2302
|
3823 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
3824 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2302
|
3825 returnURL = specification.projectReturn;
|
nicholas@2302
|
3826 }
|
nicholas@2302
|
3827 }
|
nicholas@2722
|
3828 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=" + this.parent.filenamePrefix);
|
nicholas@2224
|
3829 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2498
|
3830 xmlhttp.onerror = function () {
|
nicholas@2224
|
3831 console.log('Error updating file to server!');
|
nicholas@2224
|
3832 };
|
nicholas@2224
|
3833 var hold = document.createElement("div");
|
nicholas@2224
|
3834 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3835 hold.appendChild(clone);
|
nicholas@2498
|
3836 xmlhttp.onload = function () {
|
nicholas@2224
|
3837 if (this.status >= 300) {
|
nicholas@2224
|
3838 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3839 } else {
|
nicholas@2224
|
3840 var parser = new DOMParser();
|
nicholas@2224
|
3841 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3842 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3843 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3844 var file = response.getElementsByTagName("file")[0];
|
nicholas@2498
|
3845 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2224
|
3846 } else {
|
nicholas@2224
|
3847 var message = response.getElementsByTagName("message");
|
nicholas@2498
|
3848 console.log("Intermediate save: Error! " + message.textContent);
|
nicholas@2224
|
3849 }
|
nicholas@2224
|
3850 }
|
nicholas@2708
|
3851 };
|
nicholas@2224
|
3852 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
3853 },
|
nicholas@2723
|
3854 finish: function () {
|
nicholas@2723
|
3855 // Final upload to complete the test
|
nicholas@2723
|
3856 this.parent.finish();
|
nicholas@2723
|
3857 var hold = document.createElement("div");
|
nicholas@2723
|
3858 var clone = this.parent.root.cloneNode(true);
|
nicholas@2723
|
3859 hold.appendChild(clone);
|
nicholas@2733
|
3860 var saveURL = specification.returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=";
|
nicholas@2742
|
3861 if (this.parent.filenamePrefix.length === 0) {
|
nicholas@2733
|
3862 saveURL += "save";
|
nicholas@2733
|
3863 } else {
|
nicholas@2733
|
3864 saveURL += this.parent.filenamePrefix;
|
nicholas@2733
|
3865 }
|
nicholas@2723
|
3866 return new Promise(function (resolve, reject) {
|
nicholas@2723
|
3867 var xmlhttp = new XMLHttpRequest();
|
nicholas@2723
|
3868 xmlhttp.open("POST", saveURL);
|
nicholas@2723
|
3869 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2723
|
3870 xmlhttp.onerror = function () {
|
nicholas@2723
|
3871 console.log('Error updating file to server!');
|
nicholas@2723
|
3872 createProjectSave("local");
|
nicholas@2723
|
3873 };
|
nicholas@2723
|
3874 xmlhttp.onload = function () {
|
nicholas@2723
|
3875 if (this.status >= 300) {
|
nicholas@2723
|
3876 console.log("WARNING - Could not update at this time");
|
nicholas@2723
|
3877 createProjectSave("local");
|
nicholas@2723
|
3878 } else {
|
nicholas@2723
|
3879 var parser = new DOMParser();
|
nicholas@2723
|
3880 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2723
|
3881 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2723
|
3882 if (response.getAttribute("state") == "OK") {
|
nicholas@2723
|
3883 var file = response.getElementsByTagName("file")[0];
|
nicholas@2723
|
3884 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2723
|
3885 resolve(response);
|
nicholas@2723
|
3886 } else {
|
nicholas@2723
|
3887 var message = response.getElementsByTagName("message");
|
nicholas@2723
|
3888 reject(message);
|
nicholas@2723
|
3889 }
|
nicholas@2723
|
3890 }
|
nicholas@2723
|
3891 };
|
nicholas@2723
|
3892 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
3893 });
|
nicholas@2224
|
3894 }
|
nicholas@2708
|
3895 };
|
n@2967
|
3896 */
|
nicholas@2498
|
3897 this.createTestPageStore = function (specification) {
|
nicholas@2498
|
3898 var store = new this.pageNode(this, specification);
|
nicholas@2498
|
3899 this.testPages.push(store);
|
nicholas@2498
|
3900 return this.testPages[this.testPages.length - 1];
|
nicholas@2498
|
3901 };
|
nicholas@2498
|
3902
|
nicholas@2498
|
3903 this.surveyNode = function (parent, root, specification) {
|
nicholas@2498
|
3904 this.specification = specification;
|
nicholas@2498
|
3905 this.parent = parent;
|
nicholas@2224
|
3906 this.state = "empty";
|
nicholas@2498
|
3907 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2498
|
3908 this.XMLDOM.setAttribute('location', this.specification.location);
|
nicholas@2498
|
3909 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3910 this.specification.options.forEach(function (optNode) {
|
nicholas@2498
|
3911 if (optNode.type != 'statement') {
|
nicholas@2498
|
3912 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2498
|
3913 node.setAttribute("ref", optNode.id);
|
nicholas@2498
|
3914 node.setAttribute('type', optNode.type);
|
nicholas@2498
|
3915 this.XMLDOM.appendChild(node);
|
nicholas@2498
|
3916 }
|
nicholas@2708
|
3917 }, this);
|
nicholas@2498
|
3918 root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3919
|
nicholas@2498
|
3920 this.postResult = function (node) {
|
nicholas@2708
|
3921 function postNumber(doc, value) {
|
nicholas@2708
|
3922 var child = doc.createElement("response");
|
nicholas@2708
|
3923 child.textContent = value;
|
nicholas@2708
|
3924 return child;
|
nicholas@2708
|
3925 }
|
nicholas@2708
|
3926
|
nicholas@2708
|
3927 function postRadio(doc, node) {
|
nicholas@2708
|
3928 var child = doc.createElement('response');
|
nicholas@2708
|
3929 if (node.response !== null) {
|
nicholas@2708
|
3930 child.setAttribute('name', node.response.name);
|
nicholas@2708
|
3931 child.textContent = node.response.text;
|
nicholas@2708
|
3932 }
|
nicholas@2708
|
3933 return child;
|
nicholas@2708
|
3934 }
|
nicholas@2708
|
3935
|
nicholas@2708
|
3936 function postCheckbox(doc, node) {
|
nicholas@2708
|
3937 var checkNode = doc.createElement('response');
|
nicholas@2708
|
3938 checkNode.setAttribute('name', node.name);
|
nicholas@2708
|
3939 checkNode.setAttribute('checked', node.checked);
|
nicholas@2708
|
3940 return checkNode;
|
nicholas@2708
|
3941 }
|
nicholas@2498
|
3942 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2498
|
3943 // ID is the position
|
nicholas@2498
|
3944 if (node.specification.type == 'statement') {
|
nicholas@2498
|
3945 return;
|
nicholas@2498
|
3946 }
|
nicholas@2498
|
3947 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2708
|
3948 while (surveyresult !== null) {
|
nicholas@2498
|
3949 if (surveyresult.getAttribute("ref") == node.specification.id) {
|
nicholas@2224
|
3950 break;
|
nicholas@2224
|
3951 }
|
nicholas@2224
|
3952 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3953 }
|
nicholas@2775
|
3954 surveyresult.setAttribute("duration", node.elapsedTime);
|
nicholas@2498
|
3955 switch (node.specification.type) {
|
nicholas@2498
|
3956 case "number":
|
nicholas@2498
|
3957 case "question":
|
n@2583
|
3958 case "slider":
|
nicholas@2708
|
3959 surveyresult.appendChild(postNumber(this.parent.document, node.response));
|
nicholas@2464
|
3960 break;
|
nicholas@2498
|
3961 case "radio":
|
nicholas@2708
|
3962 surveyresult.appendChild(postRadio(this.parent.document, node));
|
nicholas@2498
|
3963 break;
|
nicholas@2498
|
3964 case "checkbox":
|
nicholas@2708
|
3965 if (node.response === undefined) {
|
nicholas@2498
|
3966 surveyresult.appendChild(this.parent.document.createElement('response'));
|
nicholas@2498
|
3967 break;
|
nicholas@2498
|
3968 }
|
nicholas@2498
|
3969 for (var i = 0; i < node.response.length; i++) {
|
nicholas@2708
|
3970 surveyresult.appendChild(postCheckbox(this.parent.document, node.response[i]));
|
nicholas@2498
|
3971 }
|
nicholas@2498
|
3972 break;
|
nicholas@2498
|
3973 }
|
nicholas@2498
|
3974 };
|
nicholas@2498
|
3975 this.complete = function () {
|
nicholas@2498
|
3976 this.state = "complete";
|
nicholas@2498
|
3977 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3978 };
|
nicholas@2498
|
3979 };
|
nicholas@2498
|
3980
|
nicholas@2498
|
3981 this.pageNode = function (parent, specification) {
|
nicholas@2498
|
3982 // Create one store per test page
|
nicholas@2498
|
3983 this.specification = specification;
|
nicholas@2498
|
3984 this.parent = parent;
|
nicholas@2498
|
3985 this.state = "empty";
|
nicholas@2498
|
3986 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2498
|
3987 this.XMLDOM.setAttribute('ref', specification.id);
|
nicholas@2498
|
3988 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
|
nicholas@2498
|
3989 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3990 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3991 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
|
nicholas@2498
|
3992 }
|
nicholas@2708
|
3993 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3994 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
|
nicholas@2498
|
3995 }
|
nicholas@2498
|
3996
|
nicholas@2498
|
3997 // Add any page metrics
|
nicholas@2498
|
3998 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3999 this.XMLDOM.appendChild(page_metric);
|
nicholas@2498
|
4000
|
nicholas@2498
|
4001 // Add the audioelement
|
nicholas@2708
|
4002 this.specification.audioElements.forEach(function (element) {
|
nicholas@2498
|
4003 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2498
|
4004 aeNode.setAttribute('ref', element.id);
|
nicholas@2708
|
4005 if (element.name !== undefined) {
|
nicholas@2708
|
4006 aeNode.setAttribute('name', element.name);
|
nicholas@2708
|
4007 }
|
nicholas@2498
|
4008 aeNode.setAttribute('type', element.type);
|
nicholas@2498
|
4009 aeNode.setAttribute('url', element.url);
|
nicholas@2498
|
4010 aeNode.setAttribute('fqurl', qualifyURL(element.url));
|
nicholas@2498
|
4011 aeNode.setAttribute('gain', element.gain);
|
nicholas@2498
|
4012 if (element.type == 'anchor' || element.type == 'reference') {
|
nicholas@2498
|
4013 if (element.marker > 0) {
|
nicholas@2498
|
4014 aeNode.setAttribute('marker', element.marker);
|
nicholas@2464
|
4015 }
|
nicholas@2498
|
4016 }
|
nicholas@2498
|
4017 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
4018 aeNode.appendChild(ae_metric);
|
nicholas@2498
|
4019 this.XMLDOM.appendChild(aeNode);
|
nicholas@2708
|
4020 }, this);
|
nicholas@2498
|
4021
|
nicholas@2498
|
4022 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2498
|
4023
|
nicholas@2498
|
4024 this.complete = function () {
|
nicholas@2224
|
4025 this.state = "complete";
|
nicholas@2498
|
4026 this.XMLDOM.setAttribute("state", "complete");
|
nicholas@2708
|
4027 };
|
nicholas@2498
|
4028 };
|
nicholas@2498
|
4029 this.update = function () {
|
nicholas@2224
|
4030 this.SessionKey.update();
|
nicholas@2708
|
4031 };
|
nicholas@2498
|
4032 this.finish = function () {
|
nicholas@2498
|
4033 this.state = 1;
|
nicholas@2498
|
4034 this.root.setAttribute("state", "complete");
|
nicholas@2498
|
4035 return this.root;
|
nicholas@2498
|
4036 };
|
nicholas@2722
|
4037
|
nicholas@2722
|
4038 Object.defineProperties(this, {
|
nicholas@2722
|
4039 'filenamePrefix': {
|
nicholas@2722
|
4040 'get': function () {
|
nicholas@2722
|
4041 return pFilenamePrefix;
|
nicholas@2722
|
4042 },
|
nicholas@2722
|
4043 'set': function (value) {
|
nicholas@2722
|
4044 if (typeof value !== "string") {
|
nicholas@2722
|
4045 value = String(value);
|
nicholas@2722
|
4046 }
|
nicholas@2722
|
4047 pFilenamePrefix = value;
|
nicholas@2722
|
4048 return value;
|
nicholas@2722
|
4049 }
|
nicholas@2722
|
4050 }
|
nicholas@2725
|
4051 });
|
nicholas@2224
|
4052 }
|
nicholas@2384
|
4053
|
nicholas@2401
|
4054 var window_depedancy_callback;
|
nicholas@2498
|
4055 window_depedancy_callback = window.setInterval(function () {
|
nicholas@2401
|
4056 if (check_dependancies()) {
|
nicholas@2401
|
4057 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
4058 onload();
|
nicholas@2401
|
4059 } else {
|
nicholas@2401
|
4060 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
4061 }
|
nicholas@2498
|
4062 }, 100);
|