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