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@3020
|
1118 if (this.currentIndex > 0 && this.node.showBackButton)
|
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@2498
|
1848 };
|
nicholas@2498
|
1849
|
nicholas@2498
|
1850 this.checkAllPlayed = function () {
|
nicholas@2708
|
1851 var arr = [];
|
nicholas@2498
|
1852 for (var id = 0; id < this.audioObjects.length; id++) {
|
nicholas@2708
|
1853 if (this.audioObjects[id].metric.wasListenedTo === false) {
|
nicholas@2498
|
1854 arr.push(this.audioObjects[id].id);
|
nicholas@2498
|
1855 }
|
nicholas@2498
|
1856 }
|
nicholas@2498
|
1857 return arr;
|
nicholas@2498
|
1858 };
|
nicholas@2498
|
1859
|
nicholas@2498
|
1860 this.checkAllReady = function () {
|
nicholas@2498
|
1861 var ready = true;
|
nicholas@2498
|
1862 for (var i = 0; i < this.audioObjects.length; i++) {
|
nicholas@2708
|
1863 if (this.audioObjects[i].state === 0) {
|
nicholas@2498
|
1864 // Track not ready
|
nicholas@2498
|
1865 console.log('WAIT -- audioObject ' + i + ' not ready yet!');
|
nicholas@2498
|
1866 ready = false;
|
nicholas@2708
|
1867 }
|
nicholas@2498
|
1868 }
|
nicholas@2498
|
1869 return ready;
|
nicholas@2498
|
1870 };
|
nicholas@2498
|
1871
|
nicholas@2498
|
1872 this.setSynchronousLoop = function () {
|
nicholas@2570
|
1873 // Pads the signals so they are all exactly the same duration
|
nicholas@2570
|
1874 // Get the duration of the longest signal.
|
nicholas@2570
|
1875 var duration = 0;
|
nicholas@2498
|
1876 var maxId;
|
nicholas@2498
|
1877 for (var i = 0; i < this.audioObjects.length; i++) {
|
nicholas@2570
|
1878 if (duration < this.audioObjects[i].buffer.buffer.duration) {
|
nicholas@2570
|
1879 duration = this.audioObjects[i].buffer.buffer.duration;
|
nicholas@2498
|
1880 maxId = i;
|
nicholas@2498
|
1881 }
|
nicholas@2498
|
1882 }
|
nicholas@2498
|
1883 // Extract the audio and zero-pad
|
nicholas@2708
|
1884 this.audioObjects.forEach(function (ao) {
|
nicholas@2570
|
1885 if (ao.buffer.buffer.duration !== duration) {
|
nicholas@2570
|
1886 ao.buffer.buffer = ao.buffer.copyBuffer(0, duration - ao.buffer.buffer.duration);
|
nicholas@2500
|
1887 }
|
nicholas@2708
|
1888 });
|
nicholas@2498
|
1889 };
|
nicholas@2498
|
1890
|
nicholas@2498
|
1891 this.bufferReady = function (id) {
|
nicholas@2498
|
1892 if (this.checkAllReady()) {
|
nicholas@2498
|
1893 if (this.synchPlayback) {
|
nicholas@2498
|
1894 this.setSynchronousLoop();
|
nicholas@2498
|
1895 }
|
nicholas@2460
|
1896 this.status = 1;
|
nicholas@2460
|
1897 return true;
|
nicholas@2460
|
1898 }
|
nicholas@2460
|
1899 return false;
|
nicholas@2224
|
1900 };
|
nicholas@2498
|
1901
|
nicholas@2224
|
1902 }
|
nicholas@2224
|
1903
|
nicholas@2224
|
1904 function audioObject(id) {
|
nicholas@2498
|
1905 // The main buffer object with common control nodes to the AudioEngine
|
nicholas@2498
|
1906
|
nicholas@2823
|
1907 var playCounter = 0;
|
nicholas@2823
|
1908
|
nicholas@2708
|
1909 this.specification = undefined;
|
nicholas@2498
|
1910 this.id = id;
|
nicholas@2498
|
1911 this.state = 0; // 0 - no data, 1 - ready
|
nicholas@2498
|
1912 this.url = null; // Hold the URL given for the output back to the results.
|
nicholas@2498
|
1913 this.metric = new metricTracker(this);
|
nicholas@2498
|
1914 this.storeDOM = null;
|
nicholas@2949
|
1915 this.playing = false;
|
nicholas@2498
|
1916
|
nicholas@2498
|
1917 // Bindings for GUI
|
nicholas@2498
|
1918 this.interfaceDOM = null;
|
nicholas@2498
|
1919 this.commentDOM = null;
|
nicholas@2498
|
1920
|
nicholas@2498
|
1921 // Create a buffer and external gain control to allow internal patching of effects and volume leveling.
|
nicholas@2498
|
1922 this.bufferNode = undefined;
|
nicholas@2498
|
1923 this.outputGain = audioContext.createGain();
|
nicholas@2942
|
1924 this.outputGain.gain.value = 0.0;
|
nicholas@2498
|
1925
|
nicholas@2498
|
1926 this.onplayGain = 1.0;
|
nicholas@2498
|
1927
|
nicholas@2498
|
1928 // Connect buffer to the audio graph
|
nicholas@2498
|
1929 this.outputGain.connect(audioEngineContext.outputGain);
|
nicholas@2508
|
1930 audioEngineContext.nullBufferSource.connect(this.outputGain);
|
nicholas@2498
|
1931
|
nicholas@2498
|
1932 // the audiobuffer is not designed for multi-start playback
|
nicholas@2498
|
1933 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
|
nicholas@2708
|
1934 this.buffer = undefined;
|
nicholas@2498
|
1935
|
nicholas@2498
|
1936 this.bufferLoaded = function (callee) {
|
nicholas@2498
|
1937 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
|
nicholas@2498
|
1938 // audioObject and trigger the interfaceDOM.enable() function for user feedback
|
nicholas@2224
|
1939 if (callee.status == -1) {
|
nicholas@2224
|
1940 // ERROR
|
nicholas@2224
|
1941 this.state = -1;
|
nicholas@2708
|
1942 if (this.interfaceDOM !== null) {
|
nicholas@2498
|
1943 this.interfaceDOM.error();
|
nicholas@2498
|
1944 }
|
nicholas@2224
|
1945 this.buffer = callee;
|
nicholas@2224
|
1946 return;
|
nicholas@2224
|
1947 }
|
nicholas@2224
|
1948 this.buffer = callee;
|
nicholas@2224
|
1949 var preSilenceTime = this.specification.preSilence || this.specification.parent.preSilence || specification.preSilence || 0.0;
|
nicholas@2224
|
1950 var postSilenceTime = this.specification.postSilence || this.specification.parent.postSilence || specification.postSilence || 0.0;
|
nicholas@2460
|
1951 var startTime = this.specification.startTime;
|
nicholas@2460
|
1952 var stopTime = this.specification.stopTime;
|
nicholas@2460
|
1953 var copybuffer = new callee.constructor();
|
nicholas@2500
|
1954
|
nicholas@2500
|
1955 copybuffer.buffer = callee.cropBuffer(startTime || 0, stopTime || callee.buffer.duration);
|
nicholas@2708
|
1956 if (preSilenceTime !== 0 || postSilenceTime !== 0) {
|
nicholas@2500
|
1957 copybuffer.buffer = copybuffer.copyBuffer(preSilenceTime, postSilenceTime);
|
nicholas@2460
|
1958 }
|
nicholas@2500
|
1959
|
nicholas@2660
|
1960 copybuffer.buffer.lufs = callee.buffer.lufs;
|
nicholas@2500
|
1961 this.buffer = copybuffer;
|
nicholas@2498
|
1962
|
nicholas@2661
|
1963 var targetLUFS = this.specification.loudness || this.specification.parent.loudness || specification.loudness;
|
nicholas@2498
|
1964 if (typeof targetLUFS === "number" && isFinite(targetLUFS)) {
|
nicholas@2498
|
1965 this.buffer.buffer.playbackGain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
|
nicholas@2498
|
1966 } else {
|
nicholas@2498
|
1967 this.buffer.buffer.playbackGain = 1.0;
|
nicholas@2498
|
1968 }
|
nicholas@2708
|
1969 if (this.interfaceDOM !== null) {
|
nicholas@2498
|
1970 this.interfaceDOM.enable();
|
nicholas@2498
|
1971 }
|
nicholas@2498
|
1972 this.onplayGain = decibelToLinear(this.specification.gain) * (this.buffer.buffer.playbackGain || 1.0);
|
nicholas@2498
|
1973 this.storeDOM.setAttribute('playGain', linearToDecibel(this.onplayGain));
|
nicholas@2460
|
1974 this.state = 1;
|
nicholas@2460
|
1975 audioEngineContext.bufferReady(id);
|
nicholas@2498
|
1976 };
|
nicholas@2498
|
1977
|
nicholas@2498
|
1978 this.bindInterface = function (interfaceObject) {
|
nicholas@2498
|
1979 this.interfaceDOM = interfaceObject;
|
nicholas@2498
|
1980 this.metric.initialise(interfaceObject.getValue());
|
nicholas@2498
|
1981 if (this.state == 1) {
|
nicholas@2498
|
1982 this.interfaceDOM.enable();
|
nicholas@2498
|
1983 } else if (this.state == -1) {
|
nicholas@2224
|
1984 // ERROR
|
nicholas@2224
|
1985 this.interfaceDOM.error();
|
nicholas@2224
|
1986 return;
|
nicholas@2224
|
1987 }
|
nicholas@2498
|
1988 this.storeDOM.setAttribute('presentedId', interfaceObject.getPresentedId());
|
nicholas@2498
|
1989 };
|
nicholas@2498
|
1990
|
nicholas@2942
|
1991 this.listenStart = function (setTime) {
|
nicholas@2949
|
1992 if (this.playing === false) {
|
nicholas@2942
|
1993 playCounter++;
|
nicholas@2942
|
1994 this.outputGain.gain.linearRampToValueAtTime(this.onplayGain, setTime);
|
nicholas@2942
|
1995 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
nicholas@2942
|
1996 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
|
nicholas@2942
|
1997 this.interfaceDOM.startPlayback();
|
nicholas@2949
|
1998 this.playing = true;
|
nicholas@2942
|
1999 }
|
nicholas@2498
|
2000 };
|
nicholas@2498
|
2001
|
nicholas@2942
|
2002 this.listenStop = function (setTime) {
|
nicholas@2949
|
2003 if (this.playing === true) {
|
nicholas@2498
|
2004 this.outputGain.gain.linearRampToValueAtTime(0.0, setTime);
|
nicholas@2942
|
2005 this.metric.stopListening(audioEngineContext.timer.getTestTime(), this.getCurrentPosition());
|
nicholas@2498
|
2006 }
|
nicholas@2224
|
2007 this.interfaceDOM.stopPlayback();
|
nicholas@2949
|
2008 this.playing = false;
|
nicholas@2498
|
2009 };
|
nicholas@2498
|
2010
|
nicholas@2942
|
2011 this.setupPlayback = function () {
|
nicholas@2708
|
2012 if (this.bufferNode === undefined && this.buffer.buffer !== undefined) {
|
nicholas@2498
|
2013 this.bufferNode = audioContext.createBufferSource();
|
nicholas@2498
|
2014 this.bufferNode.owner = this;
|
nicholas@2498
|
2015 this.bufferNode.connect(this.outputGain);
|
nicholas@2498
|
2016 this.bufferNode.buffer = this.buffer.buffer;
|
nicholas@2942
|
2017 if (audioEngineContext.loopPlayback) {
|
nicholas@2942
|
2018 this.bufferNode.loopStart = this.specification.startTime || 0;
|
nicholas@2942
|
2019 this.bufferNode.loopEnd = this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration;
|
nicholas@2942
|
2020 this.bufferNode.loop = true;
|
nicholas@2942
|
2021 }
|
nicholas@2498
|
2022 this.bufferNode.onended = function (event) {
|
nicholas@2498
|
2023 // Safari does not like using 'this' to reference the calling object!
|
nicholas@2498
|
2024 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
|
nicholas@2708
|
2025 if (event.currentTarget !== null) {
|
nicholas@2942
|
2026 event.currentTarget.owner.bufferStop(audioContext.currentTime + 0.1);
|
nicholas@2224
|
2027 }
|
nicholas@2498
|
2028 };
|
nicholas@2942
|
2029 this.bufferNode.state = 0;
|
nicholas@2942
|
2030 }
|
nicholas@2942
|
2031 };
|
nicholas@2942
|
2032
|
nicholas@2942
|
2033 this.bufferStart = function (startTime) {
|
nicholas@2942
|
2034 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
nicholas@2942
|
2035 if (this.bufferNode && this.bufferNode.state == 0) {
|
nicholas@2942
|
2036 this.bufferNode.state = 1;
|
nicholas@2942
|
2037 if (this.bufferNode.loop == true) {
|
nicholas@2499
|
2038 this.bufferNode.start(startTime);
|
nicholas@2499
|
2039 } else {
|
nicholas@2499
|
2040 this.bufferNode.start(startTime, this.specification.startTime || 0, this.specification.stopTime - this.specification.startTime || this.buffer.buffer.duration);
|
nicholas@2499
|
2041 }
|
nicholas@2498
|
2042 }
|
nicholas@2498
|
2043 };
|
nicholas@2498
|
2044
|
nicholas@2942
|
2045 this.bufferStop = function (stopTime) {
|
nicholas@2224
|
2046 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
nicholas@2942
|
2047 if (this.bufferNode !== undefined && this.bufferNode.state > 0) {
|
nicholas@2498
|
2048 this.bufferNode.stop(stopTime);
|
nicholas@2498
|
2049 this.bufferNode = undefined;
|
nicholas@2498
|
2050 }
|
nicholas@2529
|
2051 this.outputGain.gain.linearRampToValueAtTime(0.0, stopTime);
|
nicholas@2224
|
2052 this.interfaceDOM.stopPlayback();
|
nicholas@2498
|
2053 };
|
nicholas@2498
|
2054
|
nicholas@2498
|
2055 this.getCurrentPosition = function () {
|
nicholas@2498
|
2056 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2708
|
2057 if (this.bufferNode !== undefined) {
|
nicholas@2498
|
2058 var position = (time - this.bufferNode.playbackStartTime) % this.buffer.buffer.duration;
|
nicholas@2498
|
2059 if (isNaN(position)) {
|
nicholas@2498
|
2060 return 0;
|
nicholas@2498
|
2061 }
|
nicholas@2224
|
2062 return position;
|
nicholas@2498
|
2063 } else {
|
nicholas@2498
|
2064 return 0;
|
nicholas@2498
|
2065 }
|
nicholas@2498
|
2066 };
|
nicholas@2498
|
2067
|
nicholas@2498
|
2068 this.exportXMLDOM = function () {
|
nicholas@2498
|
2069 var file = storage.document.createElement('file');
|
nicholas@2498
|
2070 file.setAttribute('sampleRate', this.buffer.buffer.sampleRate);
|
nicholas@2498
|
2071 file.setAttribute('channels', this.buffer.buffer.numberOfChannels);
|
nicholas@2498
|
2072 file.setAttribute('sampleCount', this.buffer.buffer.length);
|
nicholas@2498
|
2073 file.setAttribute('duration', this.buffer.buffer.duration);
|
nicholas@2498
|
2074 this.storeDOM.appendChild(file);
|
nicholas@2498
|
2075 if (this.specification.type != 'outside-reference') {
|
nicholas@2498
|
2076 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
nicholas@2708
|
2077 if (interfaceXML !== null) {
|
nicholas@2708
|
2078 if (interfaceXML.length === undefined) {
|
nicholas@2498
|
2079 this.storeDOM.appendChild(interfaceXML);
|
nicholas@2498
|
2080 } else {
|
nicholas@2498
|
2081 for (var i = 0; i < interfaceXML.length; i++) {
|
nicholas@2498
|
2082 this.storeDOM.appendChild(interfaceXML[i]);
|
nicholas@2498
|
2083 }
|
nicholas@2498
|
2084 }
|
nicholas@2498
|
2085 }
|
nicholas@2708
|
2086 if (this.commentDOM !== null) {
|
nicholas@2498
|
2087 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
nicholas@2498
|
2088 }
|
nicholas@2498
|
2089 }
|
nicholas@2708
|
2090 this.metric.exportXMLDOM(this.storeDOM.getElementsByTagName('metric')[0]);
|
nicholas@2498
|
2091 };
|
nicholas@2823
|
2092
|
nicholas@2823
|
2093 Object.defineProperties(this, {
|
nicholas@2823
|
2094 "numberOfPlays": {
|
nicholas@2823
|
2095 'get': function () {
|
nicholas@2823
|
2096 return playCounter;
|
nicholas@2823
|
2097 },
|
nicholas@2823
|
2098 'set': function () {
|
nicholas@2823
|
2099 return playCounter;
|
nicholas@2823
|
2100 }
|
nicholas@2823
|
2101 }
|
nicholas@2823
|
2102 });
|
nicholas@2224
|
2103 }
|
nicholas@2224
|
2104
|
nicholas@2498
|
2105 function timer() {
|
nicholas@2498
|
2106 /* Timer object used in audioEngine to keep track of session timings
|
nicholas@2498
|
2107 * Uses the timer of the web audio API, so sample resolution
|
nicholas@2498
|
2108 */
|
nicholas@2498
|
2109 this.testStarted = false;
|
nicholas@2498
|
2110 this.testStartTime = 0;
|
nicholas@2498
|
2111 this.testDuration = 0;
|
nicholas@2498
|
2112 this.minimumTestTime = 0; // No minimum test time
|
nicholas@2498
|
2113 this.startTest = function () {
|
nicholas@2708
|
2114 if (this.testStarted === false) {
|
nicholas@2498
|
2115 this.testStartTime = audioContext.currentTime;
|
nicholas@2498
|
2116 this.testStarted = true;
|
nicholas@2498
|
2117 this.updateTestTime();
|
nicholas@2498
|
2118 audioEngineContext.metric.initialiseTest();
|
nicholas@2498
|
2119 }
|
nicholas@2498
|
2120 };
|
nicholas@2498
|
2121 this.stopTest = function () {
|
nicholas@2498
|
2122 if (this.testStarted) {
|
nicholas@2498
|
2123 this.testDuration = this.getTestTime();
|
nicholas@2498
|
2124 this.testStarted = false;
|
nicholas@2498
|
2125 } else {
|
nicholas@2498
|
2126 console.log('ERR: Test tried to end before beginning');
|
nicholas@2498
|
2127 }
|
nicholas@2498
|
2128 };
|
nicholas@2498
|
2129 this.updateTestTime = function () {
|
nicholas@2498
|
2130 if (this.testStarted) {
|
nicholas@2498
|
2131 this.testDuration = audioContext.currentTime - this.testStartTime;
|
nicholas@2498
|
2132 }
|
nicholas@2498
|
2133 };
|
nicholas@2498
|
2134 this.getTestTime = function () {
|
nicholas@2498
|
2135 this.updateTestTime();
|
nicholas@2498
|
2136 return this.testDuration;
|
nicholas@2498
|
2137 };
|
nicholas@2224
|
2138 }
|
nicholas@2224
|
2139
|
nicholas@2498
|
2140 function sessionMetrics(engine, specification) {
|
nicholas@2498
|
2141 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
nicholas@2498
|
2142 */
|
nicholas@2498
|
2143 this.engine = engine;
|
nicholas@2498
|
2144 this.lastClicked = -1;
|
nicholas@2498
|
2145 this.data = -1;
|
nicholas@2498
|
2146 this.reset = function () {
|
nicholas@2498
|
2147 this.lastClicked = -1;
|
nicholas@2498
|
2148 this.data = -1;
|
nicholas@2498
|
2149 };
|
nicholas@2498
|
2150
|
nicholas@2498
|
2151 this.enableElementInitialPosition = false;
|
nicholas@2498
|
2152 this.enableElementListenTracker = false;
|
nicholas@2498
|
2153 this.enableElementTimer = false;
|
nicholas@2498
|
2154 this.enableElementTracker = false;
|
nicholas@2498
|
2155 this.enableFlagListenedTo = false;
|
nicholas@2498
|
2156 this.enableFlagMoved = false;
|
nicholas@2498
|
2157 this.enableTestTimer = false;
|
nicholas@2498
|
2158 // Obtain the metrics enabled
|
nicholas@2498
|
2159 for (var i = 0; i < specification.metrics.enabled.length; i++) {
|
nicholas@2498
|
2160 var node = specification.metrics.enabled[i];
|
nicholas@2498
|
2161 switch (node) {
|
nicholas@2498
|
2162 case 'testTimer':
|
nicholas@2498
|
2163 this.enableTestTimer = true;
|
nicholas@2498
|
2164 break;
|
nicholas@2498
|
2165 case 'elementTimer':
|
nicholas@2498
|
2166 this.enableElementTimer = true;
|
nicholas@2498
|
2167 break;
|
nicholas@2498
|
2168 case 'elementTracker':
|
nicholas@2498
|
2169 this.enableElementTracker = true;
|
nicholas@2498
|
2170 break;
|
nicholas@2498
|
2171 case 'elementListenTracker':
|
nicholas@2498
|
2172 this.enableElementListenTracker = true;
|
nicholas@2498
|
2173 break;
|
nicholas@2498
|
2174 case 'elementInitialPosition':
|
nicholas@2498
|
2175 this.enableElementInitialPosition = true;
|
nicholas@2498
|
2176 break;
|
nicholas@2498
|
2177 case 'elementFlagListenedTo':
|
nicholas@2498
|
2178 this.enableFlagListenedTo = true;
|
nicholas@2498
|
2179 break;
|
nicholas@2498
|
2180 case 'elementFlagMoved':
|
nicholas@2498
|
2181 this.enableFlagMoved = true;
|
nicholas@2498
|
2182 break;
|
nicholas@2498
|
2183 case 'elementFlagComments':
|
nicholas@2498
|
2184 this.enableFlagComments = true;
|
nicholas@2498
|
2185 break;
|
nicholas@2498
|
2186 }
|
nicholas@2498
|
2187 }
|
nicholas@2498
|
2188 this.initialiseTest = function () {};
|
nicholas@2224
|
2189 }
|
nicholas@2224
|
2190
|
nicholas@2498
|
2191 function metricTracker(caller) {
|
nicholas@2498
|
2192 /* Custom object to track and collect metric data
|
nicholas@2498
|
2193 * Used only inside the audioObjects object.
|
nicholas@2498
|
2194 */
|
nicholas@2498
|
2195
|
nicholas@2498
|
2196 this.listenedTimer = 0;
|
nicholas@2498
|
2197 this.listenStart = 0;
|
nicholas@2498
|
2198 this.listenHold = false;
|
nicholas@2498
|
2199 this.initialPosition = -1;
|
nicholas@2498
|
2200 this.movementTracker = [];
|
nicholas@2498
|
2201 this.listenTracker = [];
|
nicholas@2498
|
2202 this.wasListenedTo = false;
|
nicholas@2498
|
2203 this.wasMoved = false;
|
nicholas@2498
|
2204 this.hasComments = false;
|
nicholas@2498
|
2205 this.parent = caller;
|
nicholas@2498
|
2206
|
nicholas@2498
|
2207 this.initialise = function (position) {
|
nicholas@2498
|
2208 if (this.initialPosition == -1) {
|
nicholas@2498
|
2209 this.initialPosition = position;
|
nicholas@2498
|
2210 this.moved(0, position);
|
nicholas@2498
|
2211 }
|
nicholas@2498
|
2212 };
|
nicholas@2498
|
2213
|
nicholas@2498
|
2214 this.moved = function (time, position) {
|
nicholas@2498
|
2215 if (time > 0) {
|
nicholas@2498
|
2216 this.wasMoved = true;
|
nicholas@2498
|
2217 }
|
nicholas@2498
|
2218 this.movementTracker[this.movementTracker.length] = [time, position];
|
nicholas@2498
|
2219 };
|
nicholas@2498
|
2220
|
nicholas@2498
|
2221 this.startListening = function (time) {
|
nicholas@2708
|
2222 if (this.listenHold === false) {
|
nicholas@2498
|
2223 this.wasListenedTo = true;
|
nicholas@2498
|
2224 this.listenStart = time;
|
nicholas@2498
|
2225 this.listenHold = true;
|
nicholas@2498
|
2226
|
nicholas@2498
|
2227 var evnt = document.createElement('event');
|
nicholas@2498
|
2228 var testTime = document.createElement('testTime');
|
nicholas@2498
|
2229 testTime.setAttribute('start', time);
|
nicholas@2498
|
2230 var bufferTime = document.createElement('bufferTime');
|
nicholas@2498
|
2231 bufferTime.setAttribute('start', this.parent.getCurrentPosition());
|
nicholas@2498
|
2232 evnt.appendChild(testTime);
|
nicholas@2498
|
2233 evnt.appendChild(bufferTime);
|
nicholas@2498
|
2234 this.listenTracker.push(evnt);
|
nicholas@2498
|
2235
|
nicholas@2498
|
2236 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2498
|
2237 }
|
nicholas@2498
|
2238 };
|
nicholas@2498
|
2239
|
nicholas@2498
|
2240 this.stopListening = function (time, bufferStopTime) {
|
nicholas@2708
|
2241 if (this.listenHold === true) {
|
nicholas@2498
|
2242 var diff = time - this.listenStart;
|
nicholas@2498
|
2243 this.listenedTimer += (diff);
|
nicholas@2498
|
2244 this.listenStart = 0;
|
nicholas@2498
|
2245 this.listenHold = false;
|
nicholas@2498
|
2246
|
nicholas@2498
|
2247 var evnt = this.listenTracker[this.listenTracker.length - 1];
|
nicholas@2498
|
2248 var testTime = evnt.getElementsByTagName('testTime')[0];
|
nicholas@2498
|
2249 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
nicholas@2498
|
2250 testTime.setAttribute('stop', time);
|
nicholas@2708
|
2251 if (bufferStopTime === undefined) {
|
nicholas@2498
|
2252 bufferTime.setAttribute('stop', this.parent.getCurrentPosition());
|
nicholas@2498
|
2253 } else {
|
nicholas@2498
|
2254 bufferTime.setAttribute('stop', bufferStopTime);
|
nicholas@2498
|
2255 }
|
nicholas@2498
|
2256 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
nicholas@2498
|
2257 }
|
nicholas@2498
|
2258 };
|
nicholas@2498
|
2259
|
nicholas@2708
|
2260 function exportElementTimer(parentElement) {
|
nicholas@2708
|
2261 var mElementTimer = storage.document.createElement('metricresult');
|
nicholas@2708
|
2262 mElementTimer.setAttribute('name', 'enableElementTimer');
|
nicholas@2708
|
2263 mElementTimer.textContent = this.listenedTimer;
|
nicholas@2708
|
2264 parentElement.appendChild(mElementTimer);
|
nicholas@2708
|
2265 return mElementTimer;
|
nicholas@2708
|
2266 }
|
nicholas@2708
|
2267
|
nicholas@2708
|
2268 function exportElementTrack(parentElement) {
|
nicholas@2708
|
2269 var elementTrackerFull = storage.document.createElement('metricresult');
|
nicholas@2708
|
2270 elementTrackerFull.setAttribute('name', 'elementTrackerFull');
|
nicholas@2708
|
2271 for (var k = 0; k < this.movementTracker.length; k++) {
|
nicholas@2708
|
2272 var timePos = storage.document.createElement('movement');
|
nicholas@2708
|
2273 timePos.setAttribute("time", this.movementTracker[k][0]);
|
nicholas@2708
|
2274 timePos.setAttribute("value", this.movementTracker[k][1]);
|
nicholas@2708
|
2275 elementTrackerFull.appendChild(timePos);
|
nicholas@2708
|
2276 }
|
nicholas@2708
|
2277 parentElement.appendChild(elementTrackerFull);
|
nicholas@2708
|
2278 return elementTrackerFull;
|
nicholas@2708
|
2279 }
|
nicholas@2708
|
2280
|
nicholas@2708
|
2281 function exportElementListenTracker(parentElement) {
|
nicholas@2708
|
2282 var elementListenTracker = storage.document.createElement('metricresult');
|
nicholas@2708
|
2283 elementListenTracker.setAttribute('name', 'elementListenTracker');
|
nicholas@2708
|
2284 for (var k = 0; k < this.listenTracker.length; k++) {
|
nicholas@2708
|
2285 elementListenTracker.appendChild(this.listenTracker[k]);
|
nicholas@2708
|
2286 }
|
nicholas@2708
|
2287 parentElement.appendChild(elementListenTracker);
|
nicholas@2708
|
2288 return elementListenTracker;
|
nicholas@2708
|
2289 }
|
nicholas@2708
|
2290
|
nicholas@2708
|
2291 function exportElementInitialPosition(parentElement) {
|
nicholas@2708
|
2292 var elementInitial = storage.document.createElement('metricresult');
|
nicholas@2708
|
2293 elementInitial.setAttribute('name', 'elementInitialPosition');
|
nicholas@2708
|
2294 elementInitial.textContent = this.initialPosition;
|
nicholas@2708
|
2295 parentElement.appendChild(elementInitial);
|
nicholas@2708
|
2296 return elementInitial;
|
nicholas@2708
|
2297 }
|
nicholas@2708
|
2298
|
nicholas@2708
|
2299 function exportFlagListenedTo(parentElement) {
|
nicholas@2708
|
2300 var flagListenedTo = storage.document.createElement('metricresult');
|
nicholas@2708
|
2301 flagListenedTo.setAttribute('name', 'elementFlagListenedTo');
|
nicholas@2708
|
2302 flagListenedTo.textContent = this.wasListenedTo;
|
nicholas@2708
|
2303 parentElement.appendChild(flagListenedTo);
|
nicholas@2708
|
2304 return flagListenedTo;
|
nicholas@2708
|
2305 }
|
nicholas@2708
|
2306
|
nicholas@2708
|
2307 function exportFlagMoved(parentElement) {
|
nicholas@2708
|
2308 var flagMoved = storage.document.createElement('metricresult');
|
nicholas@2708
|
2309 flagMoved.setAttribute('name', 'elementFlagMoved');
|
nicholas@2708
|
2310 flagMoved.textContent = this.wasMoved;
|
nicholas@2708
|
2311 parentElement.appendChild(flagMoved);
|
nicholas@2708
|
2312 return flagMoved;
|
nicholas@2708
|
2313 }
|
nicholas@2708
|
2314
|
nicholas@2708
|
2315 function exportFlagComments(parentElement) {
|
nicholas@2708
|
2316 var flagComments = storage.document.createElement('metricresult');
|
nicholas@2708
|
2317 flagComments.setAttribute('name', 'elementFlagComments');
|
nicholas@2708
|
2318 if (this.parent.commentDOM === null) {
|
nicholas@2708
|
2319 flagComments.textContent = 'false';
|
nicholas@2708
|
2320 } else if (this.parent.commentDOM.textContent.length === 0) {
|
nicholas@2708
|
2321 flagComments.textContent = 'false';
|
nicholas@2708
|
2322 } else {
|
nicholas@2708
|
2323 flagComments.textContet = 'true';
|
nicholas@2708
|
2324 }
|
nicholas@2708
|
2325 parentElement.appendChild(flagComments);
|
nicholas@2708
|
2326 return flagComments;
|
nicholas@2708
|
2327 }
|
nicholas@2708
|
2328
|
nicholas@2708
|
2329 this.exportXMLDOM = function (parentElement) {
|
nicholas@2708
|
2330 var elems = [];
|
nicholas@2498
|
2331 if (audioEngineContext.metric.enableElementTimer) {
|
nicholas@2708
|
2332 elems.push(exportElementTimer.call(this, parentElement));
|
nicholas@2498
|
2333 }
|
nicholas@2498
|
2334 if (audioEngineContext.metric.enableElementTracker) {
|
nicholas@2708
|
2335 elems.push(exportElementTrack.call(this, parentElement));
|
nicholas@2498
|
2336 }
|
nicholas@2498
|
2337 if (audioEngineContext.metric.enableElementListenTracker) {
|
nicholas@2708
|
2338 elems.push(exportElementListenTracker.call(this, parentElement));
|
nicholas@2498
|
2339 }
|
nicholas@2498
|
2340 if (audioEngineContext.metric.enableElementInitialPosition) {
|
nicholas@2708
|
2341 elems.push(exportElementInitialPosition.call(this, parentElement));
|
nicholas@2498
|
2342 }
|
nicholas@2498
|
2343 if (audioEngineContext.metric.enableFlagListenedTo) {
|
nicholas@2708
|
2344 elems.push(exportFlagListenedTo.call(this, parentElement));
|
nicholas@2498
|
2345 }
|
nicholas@2498
|
2346 if (audioEngineContext.metric.enableFlagMoved) {
|
nicholas@2708
|
2347 elems.push(exportFlagMoved.call(this, parentElement));
|
nicholas@2498
|
2348 }
|
nicholas@2498
|
2349 if (audioEngineContext.metric.enableFlagComments) {
|
nicholas@2708
|
2350 elems.push(exportFlagComments.call(this, parentElement));
|
nicholas@2498
|
2351 }
|
nicholas@2708
|
2352 return elems;
|
nicholas@2498
|
2353 };
|
nicholas@2224
|
2354 }
|
nicholas@2498
|
2355
|
nicholas@2224
|
2356 function Interface(specificationObject) {
|
nicholas@2498
|
2357 // This handles the bindings between the interface and the audioEngineContext;
|
nicholas@2498
|
2358 this.specification = specificationObject;
|
nicholas@2498
|
2359 this.insertPoint = document.getElementById("topLevelBody");
|
nicholas@2498
|
2360
|
nicholas@2498
|
2361 this.newPage = function (audioHolderObject, store) {
|
nicholas@2498
|
2362 audioEngineContext.newTestPage(audioHolderObject, store);
|
nicholas@2498
|
2363 interfaceContext.commentBoxes.deleteCommentBoxes();
|
nicholas@2498
|
2364 interfaceContext.deleteCommentQuestions();
|
nicholas@2498
|
2365 loadTest(audioHolderObject, store);
|
nicholas@2498
|
2366 };
|
nicholas@2498
|
2367
|
nicholas@2498
|
2368 // Bounded by interface!!
|
nicholas@2498
|
2369 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
nicholas@2498
|
2370 // For example, APE returns the slider position normalised in a <value> tag.
|
nicholas@2498
|
2371 this.interfaceObjects = [];
|
nicholas@2498
|
2372 this.interfaceObject = function () {};
|
nicholas@2498
|
2373
|
nicholas@2498
|
2374 this.resizeWindow = function (event) {
|
nicholas@2498
|
2375 popup.resize(event);
|
nicholas@2352
|
2376 this.volume.resize();
|
nicholas@2360
|
2377 this.lightbox.resize();
|
n@2718
|
2378 this.commentBoxes.boxes.forEach(function (elem) {
|
nicholas@2708
|
2379 elem.resize();
|
nicholas@2708
|
2380 });
|
nicholas@2708
|
2381 this.commentQuestions.forEach(function (elem) {
|
nicholas@2708
|
2382 elem.resize();
|
nicholas@2708
|
2383 });
|
nicholas@2498
|
2384 try {
|
nicholas@2498
|
2385 resizeWindow(event);
|
nicholas@2498
|
2386 } catch (err) {
|
nicholas@2498
|
2387 console.log("Warning - Interface does not have Resize option");
|
nicholas@2498
|
2388 console.log(err);
|
nicholas@2498
|
2389 }
|
nicholas@2498
|
2390 };
|
nicholas@2498
|
2391
|
nicholas@2498
|
2392 this.returnNavigator = function () {
|
nicholas@2498
|
2393 var node = storage.document.createElement("navigator");
|
nicholas@2498
|
2394 var platform = storage.document.createElement("platform");
|
nicholas@2498
|
2395 platform.textContent = navigator.platform;
|
nicholas@2498
|
2396 var vendor = storage.document.createElement("vendor");
|
nicholas@2498
|
2397 vendor.textContent = navigator.vendor;
|
nicholas@2498
|
2398 var userAgent = storage.document.createElement("uagent");
|
nicholas@2498
|
2399 userAgent.textContent = navigator.userAgent;
|
nicholas@2224
|
2400 var screen = storage.document.createElement("window");
|
nicholas@2498
|
2401 screen.setAttribute('innerWidth', window.innerWidth);
|
nicholas@2498
|
2402 screen.setAttribute('innerHeight', window.innerHeight);
|
nicholas@2498
|
2403 node.appendChild(platform);
|
nicholas@2498
|
2404 node.appendChild(vendor);
|
nicholas@2498
|
2405 node.appendChild(userAgent);
|
nicholas@2224
|
2406 node.appendChild(screen);
|
nicholas@2498
|
2407 return node;
|
nicholas@2498
|
2408 };
|
nicholas@2498
|
2409
|
nicholas@2498
|
2410 this.returnDateNode = function () {
|
nicholas@2224
|
2411 // Create an XML Node for the Date and Time a test was conducted
|
nicholas@2224
|
2412 // Structure is
|
nicholas@2224
|
2413 // <datetime>
|
nicholas@2224
|
2414 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
nicholas@2224
|
2415 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
nicholas@2224
|
2416 // </datetime>
|
nicholas@2224
|
2417 var dateTime = new Date();
|
nicholas@2224
|
2418 var hold = storage.document.createElement("datetime");
|
nicholas@2224
|
2419 var date = storage.document.createElement("date");
|
nicholas@2224
|
2420 var time = storage.document.createElement("time");
|
nicholas@2498
|
2421 date.setAttribute('year', dateTime.getFullYear());
|
nicholas@2498
|
2422 date.setAttribute('month', dateTime.getMonth() + 1);
|
nicholas@2498
|
2423 date.setAttribute('day', dateTime.getDate());
|
nicholas@2498
|
2424 time.setAttribute('hour', dateTime.getHours());
|
nicholas@2498
|
2425 time.setAttribute('minute', dateTime.getMinutes());
|
nicholas@2498
|
2426 time.setAttribute('secs', dateTime.getSeconds());
|
nicholas@2498
|
2427
|
nicholas@2224
|
2428 hold.appendChild(date);
|
nicholas@2224
|
2429 hold.appendChild(time);
|
nicholas@2224
|
2430 return hold;
|
nicholas@2224
|
2431
|
nicholas@2708
|
2432 };
|
nicholas@2498
|
2433
|
nicholas@2360
|
2434 this.lightbox = {
|
nicholas@2360
|
2435 parent: this,
|
nicholas@2360
|
2436 root: document.createElement("div"),
|
nicholas@2360
|
2437 content: document.createElement("div"),
|
nicholas@2360
|
2438 accept: document.createElement("button"),
|
nicholas@2360
|
2439 blanker: document.createElement("div"),
|
nicholas@2498
|
2440 post: function (type, message) {
|
nicholas@2498
|
2441 switch (type) {
|
nicholas@2360
|
2442 case "Error":
|
nicholas@2360
|
2443 this.content.className = "lightbox-error";
|
nicholas@2360
|
2444 break;
|
nicholas@2360
|
2445 case "Warning":
|
nicholas@2360
|
2446 this.content.className = "lightbox-warning";
|
nicholas@2360
|
2447 break;
|
nicholas@2360
|
2448 default:
|
nicholas@2360
|
2449 this.content.className = "lightbox-message";
|
nicholas@2360
|
2450 break;
|
nicholas@2360
|
2451 }
|
nicholas@2360
|
2452 var msg = document.createElement("p");
|
nicholas@2360
|
2453 msg.textContent = message;
|
nicholas@2360
|
2454 this.content.appendChild(msg);
|
nicholas@2360
|
2455 this.show();
|
nicholas@2360
|
2456 },
|
nicholas@2498
|
2457 show: function () {
|
nicholas@2360
|
2458 this.root.style.visibility = "visible";
|
nicholas@2360
|
2459 this.blanker.style.visibility = "visible";
|
n@2914
|
2460 this.accept.focus();
|
nicholas@2360
|
2461 },
|
nicholas@2498
|
2462 clear: function () {
|
nicholas@2360
|
2463 this.root.style.visibility = "";
|
nicholas@2360
|
2464 this.blanker.style.visibility = "";
|
nicholas@2360
|
2465 this.content.textContent = "";
|
nicholas@2360
|
2466 },
|
nicholas@2498
|
2467 handleEvent: function (event) {
|
nicholas@2360
|
2468 if (event.currentTarget == this.accept) {
|
nicholas@2360
|
2469 this.clear();
|
nicholas@2360
|
2470 }
|
nicholas@2360
|
2471 },
|
nicholas@2498
|
2472 resize: function (event) {
|
nicholas@2498
|
2473 this.root.style.left = (window.innerWidth / 2) - 250 + 'px';
|
n@2915
|
2474 },
|
n@2915
|
2475 isVisible: function () {
|
n@2915
|
2476 return this.root.style.visibility == "visible";
|
nicholas@2360
|
2477 }
|
nicholas@2708
|
2478 };
|
nicholas@2498
|
2479
|
nicholas@2360
|
2480 this.lightbox.root.appendChild(this.lightbox.content);
|
nicholas@2360
|
2481 this.lightbox.root.appendChild(this.lightbox.accept);
|
nicholas@2360
|
2482 this.lightbox.root.className = "popupHolder";
|
nicholas@2360
|
2483 this.lightbox.root.id = "lightbox-root";
|
nicholas@2360
|
2484 this.lightbox.accept.className = "popupButton";
|
nicholas@2360
|
2485 this.lightbox.accept.style.bottom = "10px";
|
nicholas@2360
|
2486 this.lightbox.accept.textContent = "OK";
|
nicholas@2360
|
2487 this.lightbox.accept.style.left = "237.5px";
|
nicholas@2498
|
2488 this.lightbox.accept.addEventListener("click", this.lightbox);
|
nicholas@2360
|
2489 this.lightbox.blanker.className = "testHalt";
|
nicholas@2360
|
2490 this.lightbox.blanker.id = "lightbox-blanker";
|
nicholas@2360
|
2491 document.getElementsByTagName("body")[0].appendChild(this.lightbox.root);
|
nicholas@2360
|
2492 document.getElementsByTagName("body")[0].appendChild(this.lightbox.blanker);
|
nicholas@2498
|
2493
|
nicholas@2712
|
2494 this.commentBoxes = (function () {
|
nicholas@2712
|
2495 var commentBoxes = {};
|
nicholas@2712
|
2496 commentBoxes.boxes = [];
|
nicholas@2712
|
2497 commentBoxes.injectPoint = null;
|
nicholas@2712
|
2498 commentBoxes.elementCommentBox = function (audioObject) {
|
nicholas@2224
|
2499 var element = audioObject.specification;
|
nicholas@2224
|
2500 this.audioObject = audioObject;
|
nicholas@2224
|
2501 this.id = audioObject.id;
|
nicholas@2224
|
2502 var audioHolderObject = audioObject.specification.parent;
|
nicholas@2224
|
2503 // Create document objects to hold the comment boxes
|
nicholas@2224
|
2504 this.trackComment = document.createElement('div');
|
nicholas@2224
|
2505 this.trackComment.className = 'comment-div';
|
nicholas@2498
|
2506 this.trackComment.id = 'comment-div-' + audioObject.id;
|
nicholas@2224
|
2507 // Create a string next to each comment asking for a comment
|
nicholas@2224
|
2508 this.trackString = document.createElement('span');
|
nicholas@2498
|
2509 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix + ' ' + audioObject.interfaceDOM.getPresentedId();
|
nicholas@2224
|
2510 // Create the HTML5 comment box 'textarea'
|
nicholas@2224
|
2511 this.trackCommentBox = document.createElement('textarea');
|
nicholas@2224
|
2512 this.trackCommentBox.rows = '4';
|
nicholas@2224
|
2513 this.trackCommentBox.cols = '100';
|
nicholas@2498
|
2514 this.trackCommentBox.name = 'trackComment' + audioObject.id;
|
nicholas@2224
|
2515 this.trackCommentBox.className = 'trackComment';
|
nicholas@2224
|
2516 var br = document.createElement('br');
|
nicholas@2224
|
2517 // Add to the holder.
|
nicholas@2224
|
2518 this.trackComment.appendChild(this.trackString);
|
nicholas@2224
|
2519 this.trackComment.appendChild(br);
|
nicholas@2224
|
2520 this.trackComment.appendChild(this.trackCommentBox);
|
nicholas@2224
|
2521
|
nicholas@2498
|
2522 this.exportXMLDOM = function () {
|
nicholas@2224
|
2523 var root = document.createElement('comment');
|
nicholas@2224
|
2524 var question = document.createElement('question');
|
nicholas@2224
|
2525 question.textContent = this.trackString.textContent;
|
nicholas@2224
|
2526 var response = document.createElement('response');
|
nicholas@2224
|
2527 response.textContent = this.trackCommentBox.value;
|
nicholas@2498
|
2528 console.log("Comment frag-" + this.id + ": " + response.textContent);
|
nicholas@2224
|
2529 root.appendChild(question);
|
nicholas@2224
|
2530 root.appendChild(response);
|
nicholas@2224
|
2531 return root;
|
nicholas@2224
|
2532 };
|
nicholas@2498
|
2533 this.resize = function () {
|
nicholas@2498
|
2534 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2535 if (boxwidth >= 600) {
|
nicholas@2224
|
2536 boxwidth = 600;
|
nicholas@2498
|
2537 } else if (boxwidth < 400) {
|
nicholas@2224
|
2538 boxwidth = 400;
|
nicholas@2224
|
2539 }
|
nicholas@2498
|
2540 this.trackComment.style.width = boxwidth + "px";
|
nicholas@2498
|
2541 this.trackCommentBox.style.width = boxwidth - 6 + "px";
|
nicholas@2224
|
2542 };
|
nicholas@2224
|
2543 this.resize();
|
nicholas@2725
|
2544 this.highlight = function (state) {
|
nicholas@2725
|
2545 if (state === true) {
|
nicholas@2725
|
2546 $(this.trackComment).addClass("comment-box-playing");
|
nicholas@2725
|
2547 } else {
|
nicholas@2725
|
2548 $(this.trackComment).removeClass("comment-box-playing");
|
nicholas@2725
|
2549 }
|
nicholas@2725
|
2550 };
|
nicholas@2224
|
2551 };
|
nicholas@2712
|
2552 commentBoxes.createCommentBox = function (audioObject) {
|
nicholas@2224
|
2553 var node = new this.elementCommentBox(audioObject);
|
nicholas@2224
|
2554 this.boxes.push(node);
|
nicholas@2224
|
2555 audioObject.commentDOM = node;
|
nicholas@2224
|
2556 return node;
|
nicholas@2224
|
2557 };
|
nicholas@2712
|
2558 commentBoxes.sortCommentBoxes = function () {
|
nicholas@2498
|
2559 this.boxes.sort(function (a, b) {
|
nicholas@2498
|
2560 return a.id - b.id;
|
nicholas@2498
|
2561 });
|
nicholas@2224
|
2562 };
|
nicholas@2224
|
2563
|
nicholas@2712
|
2564 commentBoxes.showCommentBoxes = function (inject, sort) {
|
nicholas@2224
|
2565 this.injectPoint = inject;
|
nicholas@2498
|
2566 if (sort) {
|
nicholas@2498
|
2567 this.sortCommentBoxes();
|
nicholas@2498
|
2568 }
|
nicholas@2708
|
2569 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2570 inject.appendChild(box.trackComment);
|
nicholas@2708
|
2571 });
|
nicholas@2224
|
2572 };
|
nicholas@2224
|
2573
|
nicholas@2712
|
2574 commentBoxes.deleteCommentBoxes = function () {
|
nicholas@2708
|
2575 if (this.injectPoint !== null) {
|
nicholas@2708
|
2576 this.boxes.forEach(function (box) {
|
nicholas@2224
|
2577 this.injectPoint.removeChild(box.trackComment);
|
nicholas@2708
|
2578 }, this);
|
nicholas@2224
|
2579 this.injectPoint = null;
|
nicholas@2224
|
2580 }
|
nicholas@2224
|
2581 this.boxes = [];
|
nicholas@2224
|
2582 };
|
nicholas@2725
|
2583 commentBoxes.highlightById = function (id) {
|
nicholas@2725
|
2584 if (id === undefined || typeof id !== "number" || id >= this.boxes.length) {
|
nicholas@2725
|
2585 console.log("Error - Invalid id");
|
nicholas@2725
|
2586 id = -1;
|
nicholas@2725
|
2587 }
|
nicholas@2725
|
2588 this.boxes.forEach(function (a) {
|
nicholas@2725
|
2589 if (a.id === id) {
|
nicholas@2725
|
2590 a.highlight(true);
|
nicholas@2725
|
2591 } else {
|
nicholas@2725
|
2592 a.highlight(false);
|
nicholas@2725
|
2593 }
|
nicholas@2725
|
2594 });
|
nicholas@2725
|
2595 };
|
nicholas@2712
|
2596 return commentBoxes;
|
nicholas@2712
|
2597 })();
|
nicholas@2498
|
2598
|
nicholas@2498
|
2599 this.commentQuestions = [];
|
nicholas@2498
|
2600
|
nicholas@2498
|
2601 this.commentBox = function (commentQuestion) {
|
nicholas@2498
|
2602 this.specification = commentQuestion;
|
nicholas@2498
|
2603 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2604 this.holder = document.createElement('div');
|
nicholas@2498
|
2605 this.holder.className = 'comment-div';
|
nicholas@2498
|
2606 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2607 this.string = document.createElement('span');
|
nicholas@2498
|
2608 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2609 // Create the HTML5 comment box 'textarea'
|
nicholas@2498
|
2610 this.textArea = document.createElement('textarea');
|
nicholas@2498
|
2611 this.textArea.rows = '4';
|
nicholas@2498
|
2612 this.textArea.cols = '100';
|
nicholas@2498
|
2613 this.textArea.className = 'trackComment';
|
nicholas@2498
|
2614 var br = document.createElement('br');
|
nicholas@2498
|
2615 // Add to the holder.
|
nicholas@2498
|
2616 this.holder.appendChild(this.string);
|
nicholas@2498
|
2617 this.holder.appendChild(br);
|
nicholas@2498
|
2618 this.holder.appendChild(this.textArea);
|
nicholas@2498
|
2619
|
nicholas@2498
|
2620 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2621 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2622 root.id = this.specification.id;
|
nicholas@2498
|
2623 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2624 console.log("Question: " + this.string.textContent);
|
nicholas@2498
|
2625 console.log("Response: " + root.textContent);
|
nicholas@2224
|
2626 var question = storePoint.parent.document.createElement('question');
|
nicholas@2224
|
2627 question.textContent = this.string.textContent;
|
nicholas@2224
|
2628 var response = storePoint.parent.document.createElement('response');
|
nicholas@2224
|
2629 response.textContent = this.textArea.value;
|
nicholas@2224
|
2630 root.appendChild(question);
|
nicholas@2224
|
2631 root.appendChild(response);
|
nicholas@2224
|
2632 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2633 return root;
|
nicholas@2498
|
2634 };
|
nicholas@2498
|
2635 this.resize = function () {
|
nicholas@2498
|
2636 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2637 if (boxwidth >= 600) {
|
nicholas@2498
|
2638 boxwidth = 600;
|
nicholas@2498
|
2639 } else if (boxwidth < 400) {
|
nicholas@2498
|
2640 boxwidth = 400;
|
nicholas@2498
|
2641 }
|
nicholas@2498
|
2642 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2643 this.textArea.style.width = boxwidth - 6 + "px";
|
nicholas@2498
|
2644 };
|
nicholas@2498
|
2645 this.resize();
|
nicholas@3034
|
2646 this.check = function () {
|
nicholas@3034
|
2647 if (this.specification.mandatory && this.textArea.value.length == 0) {
|
nicholas@3034
|
2648 return false;
|
nicholas@3034
|
2649 }
|
nicholas@3034
|
2650 return true;
|
nicholas@3034
|
2651 }
|
nicholas@2498
|
2652 };
|
nicholas@2498
|
2653
|
nicholas@2498
|
2654 this.radioBox = function (commentQuestion) {
|
nicholas@2498
|
2655 this.specification = commentQuestion;
|
nicholas@2498
|
2656 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2657 this.holder = document.createElement('div');
|
nicholas@2498
|
2658 this.holder.className = 'comment-div';
|
nicholas@2498
|
2659 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2660 this.string = document.createElement('span');
|
nicholas@2498
|
2661 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2662 // Add to the holder.
|
nicholas@2498
|
2663 this.holder.appendChild(this.string);
|
nicholas@2498
|
2664 this.options = [];
|
nicholas@2498
|
2665 this.inputs = document.createElement('div');
|
nicholas@2711
|
2666 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2667
|
nicholas@2498
|
2668 var optCount = commentQuestion.options.length;
|
nicholas@2711
|
2669 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2670 var div = document.createElement('div');
|
nicholas@2711
|
2671 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2672
|
nicholas@2711
|
2673 var span = document.createElement('span');
|
nicholas@2711
|
2674 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2675 span.className = 'comment-radio-span';
|
nicholas@2711
|
2676 div.appendChild(span);
|
nicholas@2722
|
2677
|
nicholas@2498
|
2678 var input = document.createElement('input');
|
nicholas@2498
|
2679 input.type = 'radio';
|
nicholas@2498
|
2680 input.name = commentQuestion.id;
|
nicholas@2711
|
2681 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2682 input.className = 'comment-radio';
|
nicholas@2498
|
2683 div.appendChild(input);
|
nicholas@2722
|
2684
|
nicholas@2498
|
2685 this.inputs.appendChild(div);
|
nicholas@2498
|
2686 this.options.push(input);
|
nicholas@2498
|
2687 }
|
nicholas@2498
|
2688 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2689
|
nicholas@2498
|
2690 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2691 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2692 root.id = this.specification.id;
|
nicholas@2498
|
2693 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2694 var question = document.createElement('question');
|
nicholas@2498
|
2695 question.textContent = this.string.textContent;
|
nicholas@2498
|
2696 var response = document.createElement('response');
|
nicholas@2498
|
2697 var i = 0;
|
nicholas@2708
|
2698 while (this.options[i].checked === false) {
|
nicholas@2498
|
2699 i++;
|
nicholas@2498
|
2700 if (i >= this.options.length) {
|
nicholas@2498
|
2701 break;
|
nicholas@2498
|
2702 }
|
nicholas@2498
|
2703 }
|
nicholas@2498
|
2704 if (i >= this.options.length) {
|
nicholas@2498
|
2705 response.textContent = 'null';
|
nicholas@2498
|
2706 } else {
|
nicholas@2498
|
2707 response.textContent = this.options[i].getAttribute('setvalue');
|
nicholas@2498
|
2708 response.setAttribute('number', i);
|
nicholas@2498
|
2709 }
|
nicholas@2498
|
2710 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2711 console.log('Response: ' + response.textContent);
|
nicholas@2498
|
2712 root.appendChild(question);
|
nicholas@2498
|
2713 root.appendChild(response);
|
nicholas@2224
|
2714 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2715 return root;
|
nicholas@2498
|
2716 };
|
nicholas@2498
|
2717 this.resize = function () {
|
nicholas@2498
|
2718 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2719 if (boxwidth >= 600) {
|
nicholas@2498
|
2720 boxwidth = 600;
|
nicholas@2498
|
2721 } else if (boxwidth < 400) {
|
nicholas@2498
|
2722 boxwidth = 400;
|
nicholas@2498
|
2723 }
|
nicholas@2498
|
2724 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2725 };
|
nicholas@2498
|
2726 this.resize();
|
nicholas@3034
|
2727 this.check = function () {
|
nicholas@3034
|
2728 if (this.specification.mandatory) {
|
nicholas@3034
|
2729 var selected = this.options.find(function (o) {
|
nicholas@3034
|
2730 return o.checked;
|
nicholas@3034
|
2731 });
|
nicholas@3034
|
2732 if (selected === undefined) {
|
nicholas@3034
|
2733 return false;
|
nicholas@3034
|
2734 }
|
nicholas@3034
|
2735 }
|
nicholas@3034
|
2736 return true;
|
nicholas@3034
|
2737 };
|
nicholas@2498
|
2738 };
|
nicholas@2498
|
2739
|
nicholas@2498
|
2740 this.checkboxBox = function (commentQuestion) {
|
nicholas@2498
|
2741 this.specification = commentQuestion;
|
nicholas@2498
|
2742 // Create document objects to hold the comment boxes
|
nicholas@2498
|
2743 this.holder = document.createElement('div');
|
nicholas@2498
|
2744 this.holder.className = 'comment-div';
|
nicholas@2498
|
2745 // Create a string next to each comment asking for a comment
|
nicholas@2498
|
2746 this.string = document.createElement('span');
|
nicholas@2498
|
2747 this.string.innerHTML = commentQuestion.statement;
|
nicholas@2498
|
2748 // Add to the holder.
|
nicholas@2498
|
2749 this.holder.appendChild(this.string);
|
nicholas@2498
|
2750 this.options = [];
|
nicholas@2498
|
2751 this.inputs = document.createElement('div');
|
nicholas@2294
|
2752 this.inputs.className = "comment-checkbox-inputs-holder";
|
nicholas@2498
|
2753
|
nicholas@2498
|
2754 var optCount = commentQuestion.options.length;
|
nicholas@2498
|
2755 for (var i = 0; i < optCount; i++) {
|
nicholas@2498
|
2756 var div = document.createElement('div');
|
nicholas@2711
|
2757 div.className = "comment-checkbox-inputs-flex";
|
nicholas@2722
|
2758
|
nicholas@2711
|
2759 var span = document.createElement('span');
|
nicholas@2711
|
2760 span.textContent = commentQuestion.options[i].text;
|
nicholas@2711
|
2761 span.className = 'comment-radio-span';
|
nicholas@2711
|
2762 div.appendChild(span);
|
nicholas@2722
|
2763
|
nicholas@2498
|
2764 var input = document.createElement('input');
|
nicholas@2498
|
2765 input.type = 'checkbox';
|
nicholas@2498
|
2766 input.name = commentQuestion.id;
|
nicholas@2498
|
2767 input.setAttribute('setvalue', commentQuestion.options[i].name);
|
nicholas@2498
|
2768 input.className = 'comment-radio';
|
nicholas@2498
|
2769 div.appendChild(input);
|
nicholas@2722
|
2770
|
nicholas@2498
|
2771 this.inputs.appendChild(div);
|
nicholas@2498
|
2772 this.options.push(input);
|
nicholas@2498
|
2773 }
|
nicholas@2498
|
2774 this.holder.appendChild(this.inputs);
|
nicholas@2498
|
2775
|
nicholas@2498
|
2776 this.exportXMLDOM = function (storePoint) {
|
nicholas@2498
|
2777 var root = storePoint.parent.document.createElement('comment');
|
nicholas@2498
|
2778 root.id = this.specification.id;
|
nicholas@2498
|
2779 root.setAttribute('type', this.specification.type);
|
nicholas@2498
|
2780 var question = document.createElement('question');
|
nicholas@2498
|
2781 question.textContent = this.string.textContent;
|
nicholas@2498
|
2782 root.appendChild(question);
|
nicholas@2498
|
2783 console.log('Comment: ' + question.textContent);
|
nicholas@2498
|
2784 for (var i = 0; i < this.options.length; i++) {
|
nicholas@2498
|
2785 var response = document.createElement('response');
|
nicholas@2498
|
2786 response.textContent = this.options[i].checked;
|
nicholas@2498
|
2787 response.setAttribute('name', this.options[i].getAttribute('setvalue'));
|
nicholas@2498
|
2788 root.appendChild(response);
|
nicholas@2498
|
2789 console.log('Response ' + response.getAttribute('name') + ': ' + response.textContent);
|
nicholas@2498
|
2790 }
|
nicholas@2224
|
2791 storePoint.XMLDOM.appendChild(root);
|
nicholas@2498
|
2792 return root;
|
nicholas@2498
|
2793 };
|
nicholas@2498
|
2794 this.resize = function () {
|
nicholas@2498
|
2795 var boxwidth = (window.innerWidth - 100) / 2;
|
nicholas@2498
|
2796 if (boxwidth >= 600) {
|
nicholas@2498
|
2797 boxwidth = 600;
|
nicholas@2498
|
2798 } else if (boxwidth < 400) {
|
nicholas@2498
|
2799 boxwidth = 400;
|
nicholas@2498
|
2800 }
|
nicholas@2498
|
2801 this.holder.style.width = boxwidth + "px";
|
nicholas@2498
|
2802 };
|
nicholas@2498
|
2803 this.resize();
|
nicholas@3034
|
2804 this.check = function () {
|
nicholas@3034
|
2805 if (this.specification.mandatory) {
|
nicholas@3034
|
2806 var selected = this.options.filter(function (o) {
|
nicholas@3034
|
2807 return o.checked;
|
nicholas@3034
|
2808 });
|
nicholas@3034
|
2809 if (selected.length === 0) {
|
nicholas@3034
|
2810 return false;
|
nicholas@3034
|
2811 }
|
nicholas@3034
|
2812 }
|
nicholas@3034
|
2813 return true;
|
nicholas@3034
|
2814 };
|
nicholas@2498
|
2815 };
|
nicholas@2498
|
2816
|
n@2579
|
2817 this.sliderBox = function (commentQuestion) {
|
n@2579
|
2818 this.specification = commentQuestion;
|
n@2579
|
2819 this.holder = document.createElement("div");
|
n@2579
|
2820 this.holder.className = 'comment-div';
|
n@2579
|
2821 this.string = document.createElement("span");
|
n@2579
|
2822 this.string.innerHTML = commentQuestion.statement;
|
n@2579
|
2823 this.slider = document.createElement("input");
|
n@2579
|
2824 this.slider.type = "range";
|
n@2579
|
2825 this.slider.min = commentQuestion.min;
|
n@2579
|
2826 this.slider.max = commentQuestion.max;
|
n@2579
|
2827 this.slider.step = commentQuestion.step;
|
n@2579
|
2828 this.slider.value = commentQuestion.value;
|
n@2579
|
2829 var br = document.createElement('br');
|
n@2579
|
2830
|
n@2580
|
2831 var textHolder = document.createElement("div");
|
n@2580
|
2832 textHolder.className = "comment-slider-text-holder";
|
n@2580
|
2833
|
n@2580
|
2834 this.leftText = document.createElement("span");
|
n@2580
|
2835 this.leftText.textContent = commentQuestion.leftText;
|
n@2580
|
2836 this.rightText = document.createElement("span");
|
n@2580
|
2837 this.rightText.textContent = commentQuestion.rightText;
|
n@2580
|
2838 textHolder.appendChild(this.leftText);
|
n@2580
|
2839 textHolder.appendChild(this.rightText);
|
n@2580
|
2840
|
n@2579
|
2841 this.holder.appendChild(this.string);
|
n@2579
|
2842 this.holder.appendChild(br);
|
n@2579
|
2843 this.holder.appendChild(this.slider);
|
n@2580
|
2844 this.holder.appendChild(textHolder);
|
n@2579
|
2845
|
n@2579
|
2846 this.exportXMLDOM = function (storePoint) {
|
n@2579
|
2847 var root = storePoint.parent.document.createElement('comment');
|
n@2579
|
2848 root.id = this.specification.id;
|
n@2579
|
2849 root.setAttribute('type', this.specification.type);
|
n@2579
|
2850 console.log("Question: " + this.string.textContent);
|
n@2579
|
2851 console.log("Response: " + this.slider.value);
|
n@2579
|
2852 var question = storePoint.parent.document.createElement('question');
|
n@2579
|
2853 question.textContent = this.string.textContent;
|
n@2579
|
2854 var response = storePoint.parent.document.createElement('response');
|
n@2579
|
2855 response.textContent = this.slider.value;
|
n@2579
|
2856 root.appendChild(question);
|
n@2579
|
2857 root.appendChild(response);
|
n@2579
|
2858 storePoint.XMLDOM.appendChild(root);
|
n@2579
|
2859 return root;
|
n@2579
|
2860 };
|
n@2579
|
2861 this.resize = function () {
|
n@2579
|
2862 var boxwidth = (window.innerWidth - 100) / 2;
|
n@2579
|
2863 if (boxwidth >= 600) {
|
n@2579
|
2864 boxwidth = 600;
|
n@2579
|
2865 } else if (boxwidth < 400) {
|
n@2579
|
2866 boxwidth = 400;
|
n@2579
|
2867 }
|
n@2579
|
2868 this.holder.style.width = boxwidth + "px";
|
n@2579
|
2869 this.slider.style.width = boxwidth - 24 + "px";
|
n@2579
|
2870 };
|
n@2579
|
2871 this.resize();
|
nicholas@3034
|
2872 this.check = function () {
|
nicholas@3034
|
2873 return true;
|
nicholas@3034
|
2874 };
|
n@2579
|
2875 };
|
n@2579
|
2876
|
nicholas@2498
|
2877 this.createCommentQuestion = function (element) {
|
nicholas@2498
|
2878 var node;
|
nicholas@2498
|
2879 if (element.type == 'question') {
|
nicholas@2498
|
2880 node = new this.commentBox(element);
|
nicholas@2498
|
2881 } else if (element.type == 'radio') {
|
nicholas@2498
|
2882 node = new this.radioBox(element);
|
nicholas@2498
|
2883 } else if (element.type == 'checkbox') {
|
nicholas@2498
|
2884 node = new this.checkboxBox(element);
|
n@2579
|
2885 } else if (element.type == 'slider') {
|
n@2579
|
2886 node = new this.sliderBox(element);
|
nicholas@2498
|
2887 }
|
nicholas@2498
|
2888 this.commentQuestions.push(node);
|
nicholas@2498
|
2889 return node;
|
nicholas@2498
|
2890 };
|
nicholas@2498
|
2891
|
nicholas@2498
|
2892 this.deleteCommentQuestions = function () {
|
nicholas@2498
|
2893 this.commentQuestions = [];
|
nicholas@2498
|
2894 };
|
nicholas@2498
|
2895
|
nicholas@3034
|
2896 this.checkCommentQuestions = function () {
|
nicholas@3034
|
2897 var failed = this.commentQuestions.filter(function (a) {
|
nicholas@3034
|
2898 return a.check() === false;
|
nicholas@3034
|
2899 });
|
nicholas@3034
|
2900 if (failed.length === 0) {
|
nicholas@3034
|
2901 return true;
|
nicholas@3034
|
2902 }
|
nicholas@3034
|
2903 this.lightbox.post("Error", "Please answer the questions on the page.");
|
nicholas@3034
|
2904 return false;
|
nicholas@3034
|
2905 }
|
nicholas@3034
|
2906
|
nicholas@2498
|
2907 this.outsideReferenceDOM = function (audioObject, index, inject) {
|
nicholas@2224
|
2908 this.parent = audioObject;
|
nicholas@2224
|
2909 this.outsideReferenceHolder = document.createElement('button');
|
nicholas@2224
|
2910 this.outsideReferenceHolder.className = 'outside-reference';
|
nicholas@2498
|
2911 this.outsideReferenceHolder.setAttribute('track-id', index);
|
nicholas@2409
|
2912 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2913 this.outsideReferenceHolder.disabled = true;
|
nicholas@2708
|
2914 this.handleEvent = function (event) {
|
nicholas@2708
|
2915 audioEngineContext.play(this.parent.id);
|
nicholas@2224
|
2916 };
|
nicholas@2708
|
2917 this.outsideReferenceHolder.addEventListener("click", this);
|
nicholas@2224
|
2918 inject.appendChild(this.outsideReferenceHolder);
|
nicholas@2498
|
2919 this.enable = function () {
|
nicholas@2498
|
2920 if (this.parent.state == 1) {
|
nicholas@2224
|
2921 this.outsideReferenceHolder.disabled = false;
|
nicholas@2224
|
2922 }
|
nicholas@2224
|
2923 };
|
nicholas@2498
|
2924 this.updateLoading = function (progress) {
|
nicholas@2498
|
2925 if (progress != 100) {
|
nicholas@2224
|
2926 progress = String(progress);
|
nicholas@2224
|
2927 progress = progress.split('.')[0];
|
nicholas@2498
|
2928 this.outsideReferenceHolder.textContent = progress + '%';
|
nicholas@2224
|
2929 } else {
|
nicholas@2409
|
2930 this.outsideReferenceHolder.textContent = this.parent.specification.label || "Reference";
|
nicholas@2224
|
2931 }
|
nicholas@2224
|
2932 };
|
nicholas@2498
|
2933 this.startPlayback = function () {
|
nicholas@2224
|
2934 // Called when playback has begun
|
nicholas@2224
|
2935 $('.track-slider').removeClass('track-slider-playing');
|
nicholas@2224
|
2936 $('.comment-div').removeClass('comment-box-playing');
|
nicholas@2224
|
2937 this.outsideReferenceHolder.style.backgroundColor = "#FDD";
|
nicholas@2224
|
2938 };
|
nicholas@2498
|
2939 this.stopPlayback = function () {
|
nicholas@2224
|
2940 // Called when playback has stopped. This gets called even if playback never started!
|
nicholas@2224
|
2941 this.outsideReferenceHolder.style.backgroundColor = "";
|
nicholas@2224
|
2942 };
|
nicholas@2498
|
2943 this.exportXMLDOM = function (audioObject) {
|
nicholas@2224
|
2944 return null;
|
nicholas@2224
|
2945 };
|
nicholas@2498
|
2946 this.getValue = function () {
|
nicholas@2224
|
2947 return 0;
|
nicholas@2224
|
2948 };
|
nicholas@2498
|
2949 this.getPresentedId = function () {
|
nicholas@2409
|
2950 return this.parent.specification.label || "Reference";
|
nicholas@2224
|
2951 };
|
nicholas@2498
|
2952 this.canMove = function () {
|
nicholas@2224
|
2953 return false;
|
nicholas@2224
|
2954 };
|
nicholas@2498
|
2955 this.error = function () {
|
nicholas@2498
|
2956 // audioObject has an error!!
|
nicholas@2224
|
2957 this.outsideReferenceHolder.textContent = "Error";
|
nicholas@2224
|
2958 this.outsideReferenceHolder.style.backgroundColor = "#F00";
|
nicholas@2708
|
2959 };
|
nicholas@2708
|
2960 };
|
nicholas@2498
|
2961
|
nicholas@2712
|
2962 this.playhead = (function () {
|
nicholas@2722
|
2963 var playhead = {};
|
nicholas@2712
|
2964 playhead.object = document.createElement('div');
|
nicholas@2712
|
2965 playhead.object.className = 'playhead';
|
nicholas@2712
|
2966 playhead.object.align = 'left';
|
nicholas@2498
|
2967 var curTime = document.createElement('div');
|
nicholas@2498
|
2968 curTime.style.width = '50px';
|
nicholas@2712
|
2969 playhead.curTimeSpan = document.createElement('span');
|
nicholas@2712
|
2970 playhead.curTimeSpan.textContent = '00:00';
|
nicholas@2712
|
2971 curTime.appendChild(playhead.curTimeSpan);
|
nicholas@2712
|
2972 playhead.object.appendChild(curTime);
|
nicholas@2712
|
2973 playhead.scrubberTrack = document.createElement('div');
|
nicholas@2712
|
2974 playhead.scrubberTrack.className = 'playhead-scrub-track';
|
nicholas@2498
|
2975
|
nicholas@2712
|
2976 playhead.scrubberHead = document.createElement('div');
|
nicholas@2712
|
2977 playhead.scrubberHead.id = 'playhead-scrubber';
|
nicholas@2712
|
2978 playhead.scrubberTrack.appendChild(playhead.scrubberHead);
|
nicholas@2712
|
2979 playhead.object.appendChild(playhead.scrubberTrack);
|
nicholas@2498
|
2980
|
nicholas@2712
|
2981 playhead.timePerPixel = 0;
|
nicholas@2712
|
2982 playhead.maxTime = 0;
|
nicholas@2498
|
2983
|
nicholas@2712
|
2984 playhead.playbackObject = undefined;
|
nicholas@2498
|
2985
|
nicholas@2712
|
2986 playhead.setTimePerPixel = function (audioObject) {
|
nicholas@2498
|
2987 //maxTime must be in seconds
|
nicholas@2498
|
2988 this.playbackObject = audioObject;
|
nicholas@2498
|
2989 this.maxTime = audioObject.buffer.buffer.duration;
|
nicholas@2498
|
2990 var width = 490; //500 - 10, 5 each side of the tracker head
|
nicholas@2498
|
2991 this.timePerPixel = this.maxTime / 490;
|
nicholas@2498
|
2992 if (this.maxTime < 60) {
|
nicholas@2498
|
2993 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
2994 } else {
|
nicholas@2498
|
2995 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
2996 }
|
nicholas@2498
|
2997 };
|
nicholas@2498
|
2998
|
nicholas@2712
|
2999 playhead.update = function () {
|
nicholas@2498
|
3000 // Update the playhead position, startPlay must be called
|
nicholas@2498
|
3001 if (this.timePerPixel > 0) {
|
nicholas@2498
|
3002 var time = this.playbackObject.getCurrentPosition();
|
nicholas@2498
|
3003 if (time > 0 && time < this.maxTime) {
|
nicholas@2498
|
3004 var width = 490;
|
nicholas@2498
|
3005 var pix = Math.floor(time / this.timePerPixel);
|
nicholas@2498
|
3006 this.scrubberHead.style.left = pix + 'px';
|
nicholas@2498
|
3007 if (this.maxTime > 60.0) {
|
nicholas@2498
|
3008 var secs = time % 60;
|
nicholas@2498
|
3009 var mins = Math.floor((time - secs) / 60);
|
nicholas@2498
|
3010 secs = secs.toString();
|
nicholas@2498
|
3011 secs = secs.substr(0, 2);
|
nicholas@2498
|
3012 mins = mins.toString();
|
nicholas@2498
|
3013 this.curTimeSpan.textContent = mins + ':' + secs;
|
nicholas@2498
|
3014 } else {
|
nicholas@2498
|
3015 time = time.toString();
|
nicholas@2498
|
3016 this.curTimeSpan.textContent = time.substr(0, 4);
|
nicholas@2498
|
3017 }
|
nicholas@2498
|
3018 } else {
|
nicholas@2498
|
3019 this.scrubberHead.style.left = '0px';
|
nicholas@2498
|
3020 if (this.maxTime < 60) {
|
nicholas@2498
|
3021 this.curTimeSpan.textContent = '0.00';
|
nicholas@2498
|
3022 } else {
|
nicholas@2498
|
3023 this.curTimeSpan.textContent = '00:00';
|
nicholas@2498
|
3024 }
|
nicholas@2498
|
3025 }
|
nicholas@2498
|
3026 }
|
nicholas@2817
|
3027 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
3028 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2817
|
3029 }
|
nicholas@2498
|
3030 };
|
nicholas@2498
|
3031
|
nicholas@2712
|
3032 playhead.interval = undefined;
|
nicholas@2498
|
3033
|
nicholas@2712
|
3034 playhead.start = function () {
|
nicholas@2708
|
3035 if (this.playbackObject !== undefined && this.interval === undefined) {
|
nicholas@2817
|
3036 window.requestAnimationFrame(this.update.bind(this));
|
nicholas@2498
|
3037 }
|
nicholas@2498
|
3038 };
|
nicholas@2712
|
3039 playhead.stop = function () {
|
nicholas@2817
|
3040 this.timePerPixel = 0;
|
nicholas@2498
|
3041 };
|
nicholas@2712
|
3042 return playhead;
|
nicholas@2712
|
3043 })();
|
nicholas@2498
|
3044
|
nicholas@2712
|
3045 this.volume = (function () {
|
nicholas@2224
|
3046 // An in-built volume module which can be viewed on page
|
nicholas@2224
|
3047 // Includes trackers on page-by-page data
|
nicholas@2224
|
3048 // Volume does NOT reset to 0dB on each page load
|
nicholas@2712
|
3049 var volume = {};
|
nicholas@2712
|
3050 volume.valueLin = 1.0;
|
nicholas@2712
|
3051 volume.valueDB = 0.0;
|
nicholas@2712
|
3052 volume.root = document.createElement('div');
|
nicholas@2712
|
3053 volume.root.id = 'master-volume-root';
|
nicholas@2712
|
3054 volume.object = document.createElement('div');
|
nicholas@2712
|
3055 volume.object.className = 'master-volume-holder-float';
|
nicholas@2712
|
3056 volume.object.appendChild(volume.root);
|
nicholas@2712
|
3057 volume.slider = document.createElement('input');
|
nicholas@2712
|
3058 volume.slider.id = 'master-volume-control';
|
nicholas@2712
|
3059 volume.slider.type = 'range';
|
nicholas@2712
|
3060 volume.valueText = document.createElement('span');
|
nicholas@2712
|
3061 volume.valueText.id = 'master-volume-feedback';
|
nicholas@2712
|
3062 volume.valueText.textContent = '0dB';
|
nicholas@2498
|
3063
|
nicholas@2712
|
3064 volume.slider.min = -60;
|
nicholas@2712
|
3065 volume.slider.max = 12;
|
nicholas@2712
|
3066 volume.slider.value = 0;
|
nicholas@2712
|
3067 volume.slider.step = 1;
|
nicholas@3043
|
3068 volume.captured = false;
|
nicholas@2712
|
3069 volume.handleEvent = function (event) {
|
nicholas@3043
|
3070 if (event.type == "mousedown") {
|
nicholas@3043
|
3071 volume.captured = true;
|
nicholas@3043
|
3072 } else if (event.type == "mousemove" && volume.captured) {
|
nicholas@2669
|
3073 this.valueDB = Number(this.slider.value);
|
nicholas@2669
|
3074 this.valueLin = decibelToLinear(this.valueDB);
|
nicholas@2669
|
3075 this.valueText.textContent = this.valueDB + 'dB';
|
nicholas@2669
|
3076 audioEngineContext.outputGain.gain.value = this.valueLin;
|
nicholas@3043
|
3077 console.log(this.valueDB);
|
nicholas@2669
|
3078 } else if (event.type == "mouseup") {
|
nicholas@3043
|
3079 volume.captured = false;
|
nicholas@2669
|
3080 this.onmouseup();
|
nicholas@2669
|
3081 }
|
nicholas@3043
|
3082 //this.slider.value = this.valueDB;
|
nicholas@2669
|
3083
|
nicholas@2669
|
3084 if (event.stopPropagation) {
|
nicholas@2669
|
3085 event.stopPropagation();
|
nicholas@2669
|
3086 }
|
nicholas@2711
|
3087 };
|
nicholas@2712
|
3088 volume.onmouseup = function () {
|
nicholas@2224
|
3089 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2708
|
3090 if (storePoint.length === 0) {
|
nicholas@2224
|
3091 storePoint = storage.document.createElement('metricresult');
|
nicholas@2498
|
3092 storePoint.setAttribute('name', 'volumeTracker');
|
nicholas@2224
|
3093 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
nicholas@2498
|
3094 } else {
|
nicholas@2224
|
3095 storePoint = storePoint[0];
|
nicholas@2224
|
3096 }
|
nicholas@2224
|
3097 var node = storage.document.createElement('movement');
|
nicholas@2498
|
3098 node.setAttribute('test-time', audioEngineContext.timer.getTestTime());
|
nicholas@2669
|
3099 node.setAttribute('volume', this.valueDB);
|
nicholas@2498
|
3100 node.setAttribute('format', 'dBFS');
|
nicholas@2224
|
3101 storePoint.appendChild(node);
|
nicholas@2711
|
3102 };
|
nicholas@2712
|
3103 volume.slider.addEventListener("mousemove", volume);
|
nicholas@2712
|
3104 volume.root.addEventListener("mouseup", volume);
|
nicholas@3043
|
3105 volume.root.addEventListener("mousedown", volume);
|
nicholas@2498
|
3106
|
nicholas@2224
|
3107 var title = document.createElement('div');
|
nicholas@2224
|
3108 title.innerHTML = '<span>Master Volume Control</span>';
|
nicholas@2224
|
3109 title.style.fontSize = '0.75em';
|
nicholas@2224
|
3110 title.style.width = "100%";
|
nicholas@2224
|
3111 title.align = 'center';
|
nicholas@2712
|
3112 volume.root.appendChild(title);
|
nicholas@2498
|
3113
|
nicholas@2712
|
3114 volume.root.appendChild(volume.slider);
|
nicholas@2712
|
3115 volume.root.appendChild(volume.valueText);
|
nicholas@2498
|
3116
|
nicholas@2712
|
3117 volume.resize = function (event) {
|
nicholas@2352
|
3118 if (window.innerWidth < 1000) {
|
nicholas@2708
|
3119 this.object.className = "master-volume-holder-inline";
|
nicholas@2352
|
3120 } else {
|
nicholas@2352
|
3121 this.object.className = 'master-volume-holder-float';
|
nicholas@2352
|
3122 }
|
nicholas@2708
|
3123 };
|
nicholas@2712
|
3124 return volume;
|
nicholas@2712
|
3125 })();
|
nicholas@2498
|
3126
|
nicholas@2778
|
3127 this.imageHolder = (function () {
|
nicholas@2778
|
3128 var imageController = {};
|
nicholas@2778
|
3129 imageController.root = document.createElement("div");
|
nicholas@2778
|
3130 imageController.root.id = "imageController";
|
nicholas@2778
|
3131 imageController.img = document.createElement("img");
|
nicholas@2778
|
3132 imageController.root.appendChild(imageController.img);
|
nicholas@2778
|
3133 imageController.setImage = function (src) {
|
nicholas@2778
|
3134 imageController.img.src = "";
|
n@2785
|
3135 if (typeof src !== "string" || src.length === undefined) {
|
nicholas@2778
|
3136 return;
|
nicholas@2778
|
3137 }
|
nicholas@2778
|
3138 imageController.img.src = src;
|
n@2785
|
3139 };
|
nicholas@2778
|
3140 return imageController;
|
nicholas@2778
|
3141 })();
|
nicholas@2778
|
3142
|
nicholas@2224
|
3143 this.calibrationModuleObject = null;
|
nicholas@2498
|
3144 this.calibrationModule = function () {
|
nicholas@2224
|
3145 // This creates an on-page calibration module
|
nicholas@2224
|
3146 this.storeDOM = storage.document.createElement("calibration");
|
nicholas@2224
|
3147 storage.root.appendChild(this.storeDOM);
|
nicholas@2224
|
3148 // The calibration is a fixed state module
|
nicholas@2224
|
3149 this.calibrationNodes = [];
|
nicholas@2224
|
3150 this.holder = null;
|
nicholas@2498
|
3151 this.build = function (inject) {
|
nicholas@2224
|
3152 var f0 = 62.5;
|
nicholas@2224
|
3153 this.holder = document.createElement("div");
|
nicholas@2224
|
3154 this.holder.className = "calibration-holder";
|
nicholas@2224
|
3155 this.calibrationNodes = [];
|
nicholas@2498
|
3156 while (f0 < 20000) {
|
nicholas@2712
|
3157 /* jshint loopfunc: true */
|
nicholas@2224
|
3158 var obj = {
|
nicholas@2224
|
3159 root: document.createElement("div"),
|
nicholas@2224
|
3160 input: document.createElement("input"),
|
nicholas@2224
|
3161 oscillator: audioContext.createOscillator(),
|
nicholas@2224
|
3162 gain: audioContext.createGain(),
|
nicholas@2224
|
3163 f: f0,
|
nicholas@2224
|
3164 parent: this,
|
nicholas@2498
|
3165 handleEvent: function (event) {
|
nicholas@2498
|
3166 switch (event.type) {
|
nicholas@2224
|
3167 case "mouseenter":
|
nicholas@2224
|
3168 this.oscillator.start(0);
|
nicholas@2224
|
3169 break;
|
nicholas@2224
|
3170 case "mouseleave":
|
nicholas@2224
|
3171 this.oscillator.stop(0);
|
nicholas@2224
|
3172 this.oscillator = audioContext.createOscillator();
|
nicholas@2224
|
3173 this.oscillator.connect(this.gain);
|
nicholas@2224
|
3174 this.oscillator.frequency.value = this.f;
|
nicholas@2224
|
3175 break;
|
nicholas@2224
|
3176 case "mousemove":
|
nicholas@2498
|
3177 var value = Math.pow(10, this.input.value / 20);
|
nicholas@2224
|
3178 if (this.f == 1000) {
|
nicholas@2224
|
3179 audioEngineContext.outputGain.gain.value = value;
|
nicholas@2224
|
3180 interfaceContext.volume.slider.value = this.input.value;
|
nicholas@2224
|
3181 } else {
|
nicholas@2708
|
3182 this.gain.gain.value = value;
|
nicholas@2224
|
3183 }
|
nicholas@2224
|
3184 break;
|
nicholas@2224
|
3185 }
|
nicholas@2224
|
3186 },
|
nicholas@2498
|
3187 disconnect: function () {
|
nicholas@2224
|
3188 this.gain.disconnect();
|
nicholas@2224
|
3189 }
|
nicholas@2708
|
3190 };
|
nicholas@2224
|
3191 obj.root.className = "calibration-slider";
|
nicholas@2224
|
3192 obj.root.appendChild(obj.input);
|
nicholas@2224
|
3193 obj.oscillator.connect(obj.gain);
|
nicholas@2224
|
3194 obj.gain.connect(audioEngineContext.outputGain);
|
nicholas@2498
|
3195 obj.gain.gain.value = Math.random() * 2;
|
nicholas@2224
|
3196 obj.input.value = obj.gain.gain.value;
|
nicholas@2498
|
3197 obj.input.setAttribute('orient', 'vertical');
|
nicholas@2224
|
3198 obj.input.type = "range";
|
nicholas@2593
|
3199 obj.input.min = -12;
|
nicholas@2593
|
3200 obj.input.max = 0;
|
nicholas@2224
|
3201 obj.input.step = 0.25;
|
nicholas@2224
|
3202 if (f0 != 1000) {
|
nicholas@2498
|
3203 obj.input.value = (Math.random() * 12) - 6;
|
nicholas@2224
|
3204 } else {
|
nicholas@2224
|
3205 obj.input.value = 0;
|
nicholas@2498
|
3206 obj.root.style.backgroundColor = "rgb(255,125,125)";
|
nicholas@2224
|
3207 }
|
nicholas@2498
|
3208 obj.input.addEventListener("mousemove", obj);
|
nicholas@2498
|
3209 obj.input.addEventListener("mouseenter", obj);
|
nicholas@2498
|
3210 obj.input.addEventListener("mouseleave", obj);
|
nicholas@2498
|
3211 obj.gain.gain.value = Math.pow(10, obj.input.value / 20);
|
nicholas@2224
|
3212 obj.oscillator.frequency.value = f0;
|
nicholas@2224
|
3213 this.calibrationNodes.push(obj);
|
nicholas@2224
|
3214 this.holder.appendChild(obj.root);
|
nicholas@2224
|
3215 f0 *= 2;
|
nicholas@2224
|
3216 }
|
nicholas@2224
|
3217 inject.appendChild(this.holder);
|
nicholas@2708
|
3218 };
|
nicholas@2498
|
3219 this.collect = function () {
|
nicholas@2708
|
3220 this.calibrationNodes.forEach(function (obj) {
|
nicholas@2224
|
3221 var node = storage.document.createElement("calibrationresult");
|
nicholas@2498
|
3222 node.setAttribute("frequency", obj.f);
|
nicholas@2498
|
3223 node.setAttribute("range-min", obj.input.min);
|
nicholas@2498
|
3224 node.setAttribute("range-max", obj.input.max);
|
nicholas@2498
|
3225 node.setAttribute("gain-lin", obj.gain.gain.value);
|
nicholas@2224
|
3226 this.storeDOM.appendChild(node);
|
nicholas@2708
|
3227 }, this);
|
nicholas@2708
|
3228 };
|
nicholas@2708
|
3229 };
|
nicholas@2498
|
3230
|
nicholas@2498
|
3231
|
nicholas@2498
|
3232 // Global Checkers
|
nicholas@2498
|
3233 // These functions will help enforce the checkers
|
n@2789
|
3234 this.checkHiddenAnchor = function (message) {
|
nicholas@2708
|
3235 var anchors = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3236 return ao.specification.type === "anchor";
|
nicholas@2708
|
3237 });
|
nicholas@2708
|
3238 var state = anchors.some(function (ao) {
|
nicholas@2708
|
3239 return (ao.interfaceDOM.getValue() > (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3240 });
|
nicholas@2708
|
3241 if (state) {
|
nicholas@2708
|
3242 console.log('Anchor node not below marker value');
|
n@2789
|
3243 if (message) {
|
n@2789
|
3244 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3245 } else {
|
n@2789
|
3246 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3247 }
|
nicholas@2708
|
3248 this.storeErrorNode('Anchor node not below marker value');
|
nicholas@2708
|
3249 return false;
|
nicholas@2498
|
3250 }
|
nicholas@2498
|
3251 return true;
|
nicholas@2498
|
3252 };
|
nicholas@2498
|
3253
|
n@2789
|
3254 this.checkHiddenReference = function (message) {
|
nicholas@2708
|
3255 var references = audioEngineContext.audioObjects.filter(function (ao) {
|
nicholas@2708
|
3256 return ao.specification.type === "reference";
|
nicholas@2708
|
3257 });
|
nicholas@2708
|
3258 var state = references.some(function (ao) {
|
nicholas@2708
|
3259 return (ao.interfaceDOM.getValue() < (ao.specification.marker / 100) && ao.specification.marker > 0);
|
nicholas@2708
|
3260 });
|
nicholas@2708
|
3261 if (state) {
|
nicholas@2708
|
3262 console.log('Reference node not below marker value');
|
n@2789
|
3263 if (message) {
|
n@2789
|
3264 interfaceContext.lightbox.post("Message", message);
|
n@2789
|
3265 } else {
|
n@2789
|
3266 interfaceContext.lightbox.post("Message", 'Please keep listening');
|
n@2789
|
3267 }
|
nicholas@2708
|
3268 this.storeErrorNode('Reference node not below marker value');
|
nicholas@2708
|
3269 return false;
|
nicholas@2498
|
3270 }
|
nicholas@2498
|
3271 return true;
|
nicholas@2498
|
3272 };
|
nicholas@2498
|
3273
|
n@2789
|
3274 this.checkFragmentsFullyPlayed = function (message) {
|
nicholas@2498
|
3275 // Checks the entire file has been played back
|
nicholas@2498
|
3276 // NOTE ! This will return true IF playback is Looped!!!
|
nicholas@2498
|
3277 if (audioEngineContext.loopPlayback) {
|
nicholas@2498
|
3278 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
nicholas@2498
|
3279 return true;
|
nicholas@2498
|
3280 }
|
nicholas@2498
|
3281 var check_pass = true;
|
nicholas@2708
|
3282 var error_obj = [],
|
nicholas@2708
|
3283 i;
|
nicholas@2708
|
3284 for (i = 0; i < audioEngineContext.audioObjects.length; i++) {
|
nicholas@2498
|
3285 var object = audioEngineContext.audioObjects[i];
|
nicholas@2498
|
3286 var time = object.buffer.buffer.duration;
|
nicholas@2498
|
3287 var metric = object.metric;
|
nicholas@2498
|
3288 var passed = false;
|
nicholas@2498
|
3289 for (var j = 0; j < metric.listenTracker.length; j++) {
|
nicholas@2498
|
3290 var bt = metric.listenTracker[j].getElementsByTagName('testtime');
|
nicholas@2498
|
3291 var start_time = Number(bt[0].getAttribute('start'));
|
nicholas@2498
|
3292 var stop_time = Number(bt[0].getAttribute('stop'));
|
nicholas@2498
|
3293 var delta = stop_time - start_time;
|
nicholas@2498
|
3294 if (delta >= time) {
|
nicholas@2498
|
3295 passed = true;
|
nicholas@2498
|
3296 break;
|
nicholas@2498
|
3297 }
|
nicholas@2498
|
3298 }
|
nicholas@2708
|
3299 if (passed === false) {
|
nicholas@2498
|
3300 check_pass = false;
|
nicholas@2498
|
3301 console.log("Continue listening to track-" + object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3302 error_obj.push(object.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3303 }
|
nicholas@2498
|
3304 }
|
nicholas@2708
|
3305 if (check_pass === false) {
|
nicholas@2498
|
3306 var str_start = "You have not completely listened to fragments ";
|
nicholas@2708
|
3307 for (i = 0; i < error_obj.length; i++) {
|
nicholas@2498
|
3308 str_start += error_obj[i];
|
nicholas@2498
|
3309 if (i != error_obj.length - 1) {
|
nicholas@2498
|
3310 str_start += ', ';
|
nicholas@2498
|
3311 }
|
nicholas@2498
|
3312 }
|
nicholas@2498
|
3313 str_start += ". Please keep listening";
|
n@2789
|
3314 console.log(str_start);
|
n@2789
|
3315 this.storeErrorNode(str_start);
|
n@2789
|
3316 if (message) {
|
n@2789
|
3317 str_start = message;
|
n@2789
|
3318 }
|
nicholas@2498
|
3319 interfaceContext.lightbox.post("Error", str_start);
|
nicholas@2444
|
3320 return false;
|
nicholas@2498
|
3321 }
|
nicholas@2444
|
3322 return true;
|
nicholas@2498
|
3323 };
|
n@2789
|
3324 this.checkAllMoved = function (message) {
|
nicholas@2498
|
3325 var str = "You have not moved ";
|
nicholas@2498
|
3326 var failed = [];
|
nicholas@2708
|
3327 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3328 if (ao.metric.wasMoved === false && ao.interfaceDOM.canMove() === true) {
|
nicholas@2498
|
3329 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3330 }
|
nicholas@2708
|
3331 }, this);
|
nicholas@2708
|
3332 if (failed.length === 0) {
|
nicholas@2498
|
3333 return true;
|
nicholas@2498
|
3334 } else if (failed.length == 1) {
|
nicholas@2498
|
3335 str += 'track ' + failed[0];
|
nicholas@2498
|
3336 } else {
|
nicholas@2498
|
3337 str += 'tracks ';
|
nicholas@2498
|
3338 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3339 str += failed[i] + ', ';
|
nicholas@2498
|
3340 }
|
nicholas@2498
|
3341 str += 'and ' + failed[i];
|
nicholas@2498
|
3342 }
|
nicholas@2498
|
3343 str += '.';
|
nicholas@2498
|
3344 console.log(str);
|
nicholas@2224
|
3345 this.storeErrorNode(str);
|
n@2789
|
3346 if (message) {
|
n@2789
|
3347 str = message;
|
n@2789
|
3348 }
|
n@2789
|
3349 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3350 return false;
|
nicholas@2498
|
3351 };
|
n@2789
|
3352 this.checkAllPlayed = function (message) {
|
nicholas@2498
|
3353 var str = "You have not played ";
|
nicholas@2498
|
3354 var failed = [];
|
nicholas@2708
|
3355 audioEngineContext.audioObjects.forEach(function (ao) {
|
nicholas@2708
|
3356 if (ao.metric.wasListenedTo === false) {
|
nicholas@2498
|
3357 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3358 }
|
nicholas@2708
|
3359 }, this);
|
nicholas@2708
|
3360 if (failed.length === 0) {
|
nicholas@2498
|
3361 return true;
|
nicholas@2498
|
3362 } else if (failed.length == 1) {
|
nicholas@2498
|
3363 str += 'track ' + failed[0];
|
nicholas@2498
|
3364 } else {
|
nicholas@2498
|
3365 str += 'tracks ';
|
nicholas@2498
|
3366 for (var i = 0; i < failed.length - 1; i++) {
|
nicholas@2498
|
3367 str += failed[i] + ', ';
|
nicholas@2498
|
3368 }
|
nicholas@2498
|
3369 str += 'and ' + failed[i];
|
nicholas@2498
|
3370 }
|
nicholas@2498
|
3371 str += '.';
|
nicholas@2498
|
3372 console.log(str);
|
nicholas@2224
|
3373 this.storeErrorNode(str);
|
n@2789
|
3374 if (message) {
|
n@2789
|
3375 str = message;
|
n@2789
|
3376 }
|
n@2789
|
3377 interfaceContext.lightbox.post("Error", str);
|
nicholas@2498
|
3378 return false;
|
nicholas@2498
|
3379 };
|
n@2789
|
3380 this.checkAllCommented = function (message) {
|
nicholas@2540
|
3381 var str = "You have not commented on all the fragments.";
|
nicholas@2540
|
3382 var cont = true,
|
nicholas@2540
|
3383 boxes = this.commentBoxes.boxes,
|
nicholas@2540
|
3384 numBoxes = boxes.length,
|
nicholas@2540
|
3385 i;
|
nicholas@2540
|
3386 for (i = 0; i < numBoxes; i++) {
|
nicholas@2540
|
3387 if (boxes[i].trackCommentBox.value === "") {
|
nicholas@2540
|
3388 console.log(str);
|
nicholas@2540
|
3389 this.storeErrorNode(str);
|
n@2789
|
3390 if (message) {
|
n@2789
|
3391 str = message;
|
n@2789
|
3392 }
|
n@2789
|
3393 interfaceContext.lightbox.post("Error", str);
|
nicholas@2540
|
3394 return false;
|
nicholas@2540
|
3395 }
|
nicholas@2540
|
3396 }
|
nicholas@2540
|
3397 return true;
|
nicholas@2708
|
3398 };
|
n@2789
|
3399 this.checkScaleRange = function (message) {
|
nicholas@2310
|
3400 var page = testState.getCurrentTestPage();
|
nicholas@2708
|
3401 var interfaceObject = page.interfaces;
|
nicholas@2310
|
3402 var state = true;
|
nicholas@2310
|
3403 var str = "Please keep listening. ";
|
nicholas@2708
|
3404 if (interfaceObject === undefined) {
|
nicholas@2708
|
3405 return true;
|
nicholas@2310
|
3406 }
|
nicholas@2708
|
3407 interfaceObject = interfaceObject[0];
|
nicholas@2708
|
3408 var scales = (function () {
|
nicholas@2708
|
3409 var scaleRange = interfaceObject.options.find(function (a) {
|
nicholas@2708
|
3410 return a.name == "scalerange";
|
nicholas@2708
|
3411 });
|
nicholas@2708
|
3412 return {
|
nicholas@2708
|
3413 min: scaleRange.min,
|
nicholas@2708
|
3414 max: scaleRange.max
|
nicholas@2708
|
3415 };
|
nicholas@2708
|
3416 })();
|
nicholas@2708
|
3417 var range = audioEngineContext.audioObjects.reduce(function (a, b) {
|
nicholas@2742
|
3418 var v = b.interfaceDOM.getValue() * 100.0;
|
nicholas@2708
|
3419 return {
|
nicholas@2708
|
3420 min: Math.min(a.min, v),
|
nicholas@2708
|
3421 max: Math.max(a.max, v)
|
nicholas@2712
|
3422 };
|
nicholas@2708
|
3423 }, {
|
nicholas@2708
|
3424 min: 100,
|
nicholas@2708
|
3425 max: 0
|
nicholas@2708
|
3426 });
|
nicholas@2708
|
3427 if (range.min > scales.min) {
|
nicholas@2742
|
3428 str += "At least one fragment must be below the " + scales.min + " mark.";
|
nicholas@2708
|
3429 state = false;
|
nicholas@2712
|
3430 } else if (range.max < scales.max) {
|
nicholas@2742
|
3431 str += "At least one fragment must be above the " + scales.max + " mark.";
|
nicholas@2310
|
3432 state = false;
|
nicholas@2310
|
3433 }
|
nicholas@2708
|
3434 if (state === false) {
|
nicholas@2310
|
3435 console.log(str);
|
nicholas@2310
|
3436 this.storeErrorNode(str);
|
n@2789
|
3437 if (message) {
|
n@2789
|
3438 str = message;
|
n@2789
|
3439 }
|
nicholas@2498
|
3440 interfaceContext.lightbox.post("Error", str);
|
nicholas@2310
|
3441 }
|
nicholas@2310
|
3442 return state;
|
nicholas@2708
|
3443 };
|
nicholas@2826
|
3444 this.checkFragmentMinPlays = function () {
|
nicholas@2826
|
3445 var failedObjects = audioEngineContext.audioObjects.filter(function (a) {
|
nicholas@2826
|
3446 var minPlays = a.specification.minNumberPlays || a.specification.parent.minNumberPlays || specification.minNumberPlays;
|
nicholas@2826
|
3447 if (minPlays === undefined || a.numberOfPlays >= minPlays) {
|
nicholas@2826
|
3448 return false;
|
nicholas@2826
|
3449 }
|
nicholas@2826
|
3450 return true;
|
nicholas@2826
|
3451 });
|
nicholas@2826
|
3452 if (failedObjects.length === 0) {
|
nicholas@2827
|
3453 return true;
|
nicholas@2826
|
3454 }
|
nicholas@2826
|
3455 var failedString = [];
|
nicholas@2826
|
3456 failedObjects.forEach(function (a) {
|
nicholas@2826
|
3457 failedString.push(a.interfaceDOM.getPresentedId());
|
nicholas@2826
|
3458 });
|
nicholas@2826
|
3459 var str = "You have not played fragments " + failedString.join(", ") + " enough. Please keep listening";
|
nicholas@2826
|
3460 interfaceContext.lightbox.post("Message", str);
|
nicholas@2826
|
3461 this.storeErrorNode(str);
|
nicholas@2827
|
3462 return false;
|
nicholas@2826
|
3463 };
|
nicholas@2826
|
3464
|
nicholas@2498
|
3465
|
nicholas@2849
|
3466 this.sortFragmentsByScore = function () {
|
nicholas@2849
|
3467 var elements = audioEngineContext.audioObjects.filter(function (elem) {
|
nicholas@2849
|
3468 return elem.specification.type !== "outside-reference";
|
nicholas@2849
|
3469 });
|
nicholas@2849
|
3470 var indexes = [];
|
nicholas@2849
|
3471 var i = 0;
|
nicholas@2849
|
3472 while (indexes.push(i++) < elements.length);
|
nicholas@2849
|
3473 return indexes.sort(function (x, y) {
|
nicholas@2849
|
3474 var a = elements[x].interfaceDOM.getValue();
|
nicholas@2849
|
3475 var b = elements[y].interfaceDOM.getValue();
|
nicholas@2849
|
3476 if (a > b) {
|
nicholas@2849
|
3477 return 1;
|
nicholas@2849
|
3478 } else if (a < b) {
|
nicholas@2849
|
3479 return -1;
|
nicholas@2849
|
3480 }
|
nicholas@2849
|
3481 return 0;
|
nicholas@2849
|
3482 }, elements[0].interfaceDOM.getValue());
|
nicholas@2849
|
3483 };
|
nicholas@2849
|
3484
|
nicholas@2498
|
3485 this.storeErrorNode = function (errorMessage) {
|
nicholas@2224
|
3486 var time = audioEngineContext.timer.getTestTime();
|
nicholas@2224
|
3487 var node = storage.document.createElement('error');
|
nicholas@2498
|
3488 node.setAttribute('time', time);
|
nicholas@2224
|
3489 node.textContent = errorMessage;
|
nicholas@2224
|
3490 testState.currentStore.XMLDOM.appendChild(node);
|
nicholas@2224
|
3491 };
|
nicholas@2595
|
3492
|
nicholas@2595
|
3493 this.getLabel = function (labelType, index, labelStart) {
|
nicholas@2595
|
3494 /*
|
nicholas@2595
|
3495 Get the correct label based on type, index and offset
|
nicholas@2595
|
3496 */
|
nicholas@2595
|
3497
|
nicholas@2595
|
3498 function calculateLabel(labelType, index, offset) {
|
nicholas@2595
|
3499 if (labelType == "none") {
|
nicholas@2595
|
3500 return "";
|
nicholas@2595
|
3501 }
|
nicholas@2595
|
3502 switch (labelType) {
|
nicholas@2595
|
3503 case "letter":
|
nicholas@2596
|
3504 return String.fromCharCode((index + offset) % 26 + 97);
|
nicholas@2595
|
3505 case "capital":
|
nicholas@2607
|
3506 return String.fromCharCode((index + offset) % 26 + 65);
|
nicholas@2625
|
3507 case "samediff":
|
nicholas@2708
|
3508 if (index === 0) {
|
nicholas@2625
|
3509 return "Same";
|
nicholas@2625
|
3510 } else if (index == 1) {
|
nicholas@2625
|
3511 return "Difference";
|
nicholas@2625
|
3512 }
|
nicholas@2708
|
3513 return "";
|
nicholas@2595
|
3514 case "number":
|
nicholas@2595
|
3515 return String(index + offset);
|
nicholas@2595
|
3516 default:
|
nicholas@2595
|
3517 return "";
|
nicholas@2595
|
3518 }
|
nicholas@2595
|
3519 }
|
nicholas@2595
|
3520
|
nicholas@2708
|
3521 if (typeof labelStart !== "string" || labelStart.length === 0) {
|
nicholas@2595
|
3522 labelStart = String.fromCharCode(0);
|
nicholas@2595
|
3523 }
|
nicholas@2595
|
3524
|
nicholas@2595
|
3525 switch (labelType) {
|
nicholas@2595
|
3526 case "letter":
|
nicholas@2595
|
3527 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3528 if (labelStart < 97 || labelStart > 122) {
|
nicholas@2595
|
3529 labelStart = 97;
|
nicholas@2595
|
3530 }
|
nicholas@2595
|
3531 labelStart -= 97;
|
nicholas@2595
|
3532 break;
|
nicholas@2595
|
3533 case "capital":
|
nicholas@2595
|
3534 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3535 if (labelStart < 65 || labelStart > 90) {
|
nicholas@2595
|
3536 labelStart = 65;
|
nicholas@2595
|
3537 }
|
nicholas@2595
|
3538 labelStart -= 65;
|
nicholas@2595
|
3539 break;
|
nicholas@2595
|
3540 case "number":
|
nicholas@2608
|
3541 labelStart = Number(labelStart);
|
nicholas@2608
|
3542 if (!isFinite(labelStart)) {
|
nicholas@2595
|
3543 labelStart = 1;
|
nicholas@2595
|
3544 }
|
nicholas@2595
|
3545 break;
|
nicholas@2595
|
3546 default:
|
nicholas@2596
|
3547 labelStart = 0;
|
nicholas@2595
|
3548 }
|
nicholas@2595
|
3549 if (typeof index == "number") {
|
nicholas@2595
|
3550 return calculateLabel(labelType, index, labelStart);
|
nicholas@2595
|
3551 } else if (index.length && index.length > 0) {
|
nicholas@2595
|
3552 var a = [],
|
nicholas@2595
|
3553 l = index.length,
|
nicholas@2595
|
3554 i;
|
nicholas@2595
|
3555 for (i = 0; i < l; i++) {
|
nicholas@2595
|
3556 a[i] = calculateLabel(labelType, index[i], labelStart);
|
nicholas@2595
|
3557 }
|
nicholas@2595
|
3558 return a;
|
nicholas@2595
|
3559 } else {
|
nicholas@2595
|
3560 throw ("Invalid arguments");
|
nicholas@2595
|
3561 }
|
nicholas@2708
|
3562 };
|
nicholas@2649
|
3563
|
nicholas@2649
|
3564 this.getCombinedInterfaces = function (page) {
|
nicholas@2649
|
3565 // Combine the interfaces with the global interface nodes
|
nicholas@2649
|
3566 var global = specification.interfaces,
|
nicholas@2649
|
3567 local = page.interfaces;
|
nicholas@2649
|
3568 local.forEach(function (locInt) {
|
nicholas@2649
|
3569 // Iterate through the options nodes
|
nicholas@2649
|
3570 var addList = [];
|
nicholas@2649
|
3571 global.options.forEach(function (gopt) {
|
nicholas@2649
|
3572 var lopt = locInt.options.find(function (lopt) {
|
nicholas@2649
|
3573 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
|
nicholas@2649
|
3574 });
|
nicholas@2649
|
3575 if (!lopt) {
|
nicholas@2649
|
3576 // Global option doesn't exist locally
|
nicholas@2649
|
3577 addList.push(gopt);
|
nicholas@2649
|
3578 }
|
nicholas@2649
|
3579 });
|
nicholas@2649
|
3580 locInt.options = locInt.options.concat(addList);
|
nicholas@2649
|
3581 if (!locInt.scales && global.scales) {
|
nicholas@2649
|
3582 // Use the global default scales
|
nicholas@2649
|
3583 locInt.scales = global.scales;
|
nicholas@2649
|
3584 }
|
nicholas@2649
|
3585 });
|
nicholas@2649
|
3586 return local;
|
nicholas@2708
|
3587 };
|
nicholas@2224
|
3588 }
|
nicholas@2224
|
3589
|
nicholas@2498
|
3590 function Storage() {
|
nicholas@2498
|
3591 // Holds results in XML format until ready for collection
|
nicholas@2498
|
3592 this.globalPreTest = null;
|
nicholas@2498
|
3593 this.globalPostTest = null;
|
nicholas@2498
|
3594 this.testPages = [];
|
nicholas@2498
|
3595 this.document = null;
|
nicholas@2498
|
3596 this.root = null;
|
nicholas@2498
|
3597 this.state = 0;
|
nicholas@2733
|
3598 var pFilenamePrefix = "save";
|
nicholas@2498
|
3599
|
nicholas@2498
|
3600 this.initialise = function (existingStore) {
|
nicholas@2708
|
3601 if (existingStore === undefined) {
|
nicholas@2224
|
3602 // We need to get the sessionKey
|
nicholas@2510
|
3603 this.SessionKey.requestKey();
|
nicholas@2498
|
3604 this.document = document.implementation.createDocument(null, "waetresult", null);
|
nicholas@2224
|
3605 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3606 var projectDocument = specification.projectXML;
|
nicholas@2708
|
3607 projectDocument.setAttribute('file-name', specification.url);
|
nicholas@2708
|
3608 projectDocument.setAttribute('url', qualifyURL(specification.url));
|
nicholas@2224
|
3609 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3610 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3611 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3612 } else {
|
nicholas@2224
|
3613 this.document = existingStore;
|
nicholas@2294
|
3614 this.root = existingStore.firstChild;
|
nicholas@2224
|
3615 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3616 }
|
nicholas@2708
|
3617 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3618 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
|
nicholas@2498
|
3619 }
|
nicholas@2708
|
3620 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3621 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
|
nicholas@2498
|
3622 }
|
nicholas@2498
|
3623 };
|
nicholas@2498
|
3624
|
nicholas@2224
|
3625 this.SessionKey = {
|
nicholas@2224
|
3626 key: null,
|
nicholas@2224
|
3627 request: new XMLHttpRequest(),
|
nicholas@2224
|
3628 parent: this,
|
nicholas@2498
|
3629 handleEvent: function () {
|
nicholas@2224
|
3630 var parse = new DOMParser();
|
nicholas@2498
|
3631 var xml = parse.parseFromString(this.request.response, "text/xml");
|
nicholas@2708
|
3632 if (this.request.response.length === 0) {
|
nicholas@2515
|
3633 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2376
|
3634 return;
|
nicholas@2376
|
3635 }
|
nicholas@2498
|
3636 if (xml.getElementsByTagName("state").length > 0) {
|
nicholas@2498
|
3637 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
nicholas@2498
|
3638 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
nicholas@2498
|
3639 this.parent.root.setAttribute("key", this.key);
|
nicholas@2498
|
3640 this.parent.root.setAttribute("state", "empty");
|
nicholas@2516
|
3641 this.update();
|
nicholas@2515
|
3642 return;
|
nicholas@2514
|
3643 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
|
nicholas@2515
|
3644 this.key = null;
|
nicholas@2514
|
3645 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
|
nicholas@2515
|
3646 return;
|
nicholas@2498
|
3647 }
|
nicholas@2498
|
3648 }
|
nicholas@2515
|
3649 this.key = null;
|
nicholas@2515
|
3650 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2224
|
3651 },
|
nicholas@2510
|
3652 requestKey: function () {
|
nicholas@2510
|
3653 // For new servers, request a new key from the server
|
nicholas@2510
|
3654 var returnURL = "";
|
nicholas@2510
|
3655 if (typeof specification.projectReturn == "string") {
|
nicholas@2510
|
3656 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2510
|
3657 returnURL = specification.projectReturn;
|
nicholas@2510
|
3658 }
|
nicholas@2510
|
3659 }
|
nicholas@2940
|
3660 this.request.open("GET", returnURL + "php/requestKey.php?saveFilenamePrefix=" + this.parent.filenamePrefix, true);
|
nicholas@2510
|
3661 this.request.addEventListener("load", this);
|
nicholas@2510
|
3662 this.request.send();
|
nicholas@2510
|
3663 },
|
nicholas@2498
|
3664 update: function () {
|
nicholas@2708
|
3665 if (this.key === null) {
|
nicholas@2357
|
3666 console.log("Cannot save as key == null");
|
nicholas@2357
|
3667 return;
|
nicholas@2357
|
3668 }
|
nicholas@2498
|
3669 this.parent.root.setAttribute("state", "update");
|
nicholas@2224
|
3670 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3671 var returnURL = "";
|
nicholas@2302
|
3672 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
3673 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2302
|
3674 returnURL = specification.projectReturn;
|
nicholas@2302
|
3675 }
|
nicholas@2302
|
3676 }
|
n@2997
|
3677 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=" + this.parent.filenamePrefix + "&state=update");
|
nicholas@2224
|
3678 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2498
|
3679 xmlhttp.onerror = function () {
|
nicholas@2224
|
3680 console.log('Error updating file to server!');
|
nicholas@2224
|
3681 };
|
nicholas@2224
|
3682 var hold = document.createElement("div");
|
nicholas@2224
|
3683 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3684 hold.appendChild(clone);
|
nicholas@2498
|
3685 xmlhttp.onload = function () {
|
nicholas@2224
|
3686 if (this.status >= 300) {
|
nicholas@2224
|
3687 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3688 } else {
|
nicholas@2224
|
3689 var parser = new DOMParser();
|
nicholas@2224
|
3690 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3691 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3692 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3693 var file = response.getElementsByTagName("file")[0];
|
nicholas@2498
|
3694 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2224
|
3695 } else {
|
nicholas@2224
|
3696 var message = response.getElementsByTagName("message");
|
nicholas@2498
|
3697 console.log("Intermediate save: Error! " + message.textContent);
|
nicholas@2224
|
3698 }
|
nicholas@2224
|
3699 }
|
nicholas@2708
|
3700 };
|
nicholas@2224
|
3701 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
3702 },
|
nicholas@2723
|
3703 finish: function () {
|
nicholas@2723
|
3704 // Final upload to complete the test
|
nicholas@2723
|
3705 this.parent.finish();
|
nicholas@2723
|
3706 var hold = document.createElement("div");
|
nicholas@2723
|
3707 var clone = this.parent.root.cloneNode(true);
|
nicholas@2723
|
3708 hold.appendChild(clone);
|
nicholas@2733
|
3709 var saveURL = specification.returnURL + "php/save.php?key=" + this.key + "&saveFilenamePrefix=";
|
nicholas@2742
|
3710 if (this.parent.filenamePrefix.length === 0) {
|
nicholas@2733
|
3711 saveURL += "save";
|
nicholas@2733
|
3712 } else {
|
nicholas@2733
|
3713 saveURL += this.parent.filenamePrefix;
|
nicholas@2733
|
3714 }
|
n@2997
|
3715 saveURL += "&state=finish";
|
nicholas@2723
|
3716 return new Promise(function (resolve, reject) {
|
nicholas@2723
|
3717 var xmlhttp = new XMLHttpRequest();
|
nicholas@2723
|
3718 xmlhttp.open("POST", saveURL);
|
nicholas@2723
|
3719 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2723
|
3720 xmlhttp.onerror = function () {
|
nicholas@2723
|
3721 console.log('Error updating file to server!');
|
nicholas@2723
|
3722 createProjectSave("local");
|
nicholas@2723
|
3723 };
|
nicholas@2723
|
3724 xmlhttp.onload = function () {
|
nicholas@2723
|
3725 if (this.status >= 300) {
|
nicholas@2723
|
3726 console.log("WARNING - Could not update at this time");
|
nicholas@2723
|
3727 createProjectSave("local");
|
nicholas@2723
|
3728 } else {
|
nicholas@2723
|
3729 var parser = new DOMParser();
|
nicholas@2723
|
3730 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2723
|
3731 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2723
|
3732 if (response.getAttribute("state") == "OK") {
|
nicholas@2723
|
3733 var file = response.getElementsByTagName("file")[0];
|
nicholas@2723
|
3734 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2723
|
3735 resolve(response);
|
nicholas@2723
|
3736 } else {
|
nicholas@2723
|
3737 var message = response.getElementsByTagName("message");
|
nicholas@2723
|
3738 reject(message);
|
nicholas@2723
|
3739 }
|
nicholas@2723
|
3740 }
|
nicholas@2723
|
3741 };
|
nicholas@2723
|
3742 xmlhttp.send([hold.innerHTML]);
|
nicholas@2723
|
3743 });
|
nicholas@2224
|
3744 }
|
nicholas@2708
|
3745 };
|
nicholas@2498
|
3746
|
nicholas@2498
|
3747 this.createTestPageStore = function (specification) {
|
nicholas@2498
|
3748 var store = new this.pageNode(this, specification);
|
nicholas@2498
|
3749 this.testPages.push(store);
|
nicholas@2498
|
3750 return this.testPages[this.testPages.length - 1];
|
nicholas@2498
|
3751 };
|
nicholas@2498
|
3752
|
nicholas@2498
|
3753 this.surveyNode = function (parent, root, specification) {
|
nicholas@2498
|
3754 this.specification = specification;
|
nicholas@2498
|
3755 this.parent = parent;
|
nicholas@2224
|
3756 this.state = "empty";
|
nicholas@2498
|
3757 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2498
|
3758 this.XMLDOM.setAttribute('location', this.specification.location);
|
nicholas@2498
|
3759 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3760 this.specification.options.forEach(function (optNode) {
|
nicholas@2498
|
3761 if (optNode.type != 'statement') {
|
nicholas@2498
|
3762 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2498
|
3763 node.setAttribute("ref", optNode.id);
|
nicholas@2498
|
3764 node.setAttribute('type', optNode.type);
|
nicholas@2498
|
3765 this.XMLDOM.appendChild(node);
|
nicholas@2498
|
3766 }
|
nicholas@2708
|
3767 }, this);
|
nicholas@2498
|
3768 root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3769
|
nicholas@2498
|
3770 this.postResult = function (node) {
|
nicholas@2708
|
3771 function postNumber(doc, value) {
|
nicholas@2708
|
3772 var child = doc.createElement("response");
|
nicholas@2708
|
3773 child.textContent = value;
|
nicholas@2708
|
3774 return child;
|
nicholas@2708
|
3775 }
|
nicholas@2708
|
3776
|
nicholas@2708
|
3777 function postRadio(doc, node) {
|
nicholas@2708
|
3778 var child = doc.createElement('response');
|
nicholas@2708
|
3779 if (node.response !== null) {
|
nicholas@2708
|
3780 child.setAttribute('name', node.response.name);
|
nicholas@2708
|
3781 child.textContent = node.response.text;
|
nicholas@2708
|
3782 }
|
nicholas@2708
|
3783 return child;
|
nicholas@2708
|
3784 }
|
nicholas@2708
|
3785
|
nicholas@2708
|
3786 function postCheckbox(doc, node) {
|
nicholas@2708
|
3787 var checkNode = doc.createElement('response');
|
nicholas@2708
|
3788 checkNode.setAttribute('name', node.name);
|
nicholas@2708
|
3789 checkNode.setAttribute('checked', node.checked);
|
nicholas@2708
|
3790 return checkNode;
|
nicholas@2708
|
3791 }
|
nicholas@2498
|
3792 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2498
|
3793 // ID is the position
|
nicholas@2498
|
3794 if (node.specification.type == 'statement') {
|
nicholas@2498
|
3795 return;
|
nicholas@2498
|
3796 }
|
nicholas@2498
|
3797 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2708
|
3798 while (surveyresult !== null) {
|
nicholas@2498
|
3799 if (surveyresult.getAttribute("ref") == node.specification.id) {
|
nicholas@2224
|
3800 break;
|
nicholas@2224
|
3801 }
|
nicholas@2224
|
3802 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3803 }
|
nicholas@2775
|
3804 surveyresult.setAttribute("duration", node.elapsedTime);
|
nicholas@2498
|
3805 switch (node.specification.type) {
|
nicholas@2498
|
3806 case "number":
|
nicholas@2498
|
3807 case "question":
|
n@2583
|
3808 case "slider":
|
nicholas@2708
|
3809 surveyresult.appendChild(postNumber(this.parent.document, node.response));
|
nicholas@2464
|
3810 break;
|
nicholas@2498
|
3811 case "radio":
|
nicholas@2708
|
3812 surveyresult.appendChild(postRadio(this.parent.document, node));
|
nicholas@2498
|
3813 break;
|
nicholas@2498
|
3814 case "checkbox":
|
nicholas@2708
|
3815 if (node.response === undefined) {
|
nicholas@2498
|
3816 surveyresult.appendChild(this.parent.document.createElement('response'));
|
nicholas@2498
|
3817 break;
|
nicholas@2498
|
3818 }
|
nicholas@2498
|
3819 for (var i = 0; i < node.response.length; i++) {
|
nicholas@2708
|
3820 surveyresult.appendChild(postCheckbox(this.parent.document, node.response[i]));
|
nicholas@2498
|
3821 }
|
nicholas@2498
|
3822 break;
|
nicholas@2498
|
3823 }
|
nicholas@2498
|
3824 };
|
nicholas@2498
|
3825 this.complete = function () {
|
nicholas@2498
|
3826 this.state = "complete";
|
nicholas@2498
|
3827 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3828 };
|
nicholas@2498
|
3829 };
|
nicholas@2498
|
3830
|
nicholas@2498
|
3831 this.pageNode = function (parent, specification) {
|
nicholas@2498
|
3832 // Create one store per test page
|
nicholas@2498
|
3833 this.specification = specification;
|
nicholas@2498
|
3834 this.parent = parent;
|
nicholas@2498
|
3835 this.state = "empty";
|
nicholas@2498
|
3836 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2498
|
3837 this.XMLDOM.setAttribute('ref', specification.id);
|
nicholas@2498
|
3838 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
|
nicholas@2498
|
3839 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2708
|
3840 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3841 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
|
nicholas@2498
|
3842 }
|
nicholas@2708
|
3843 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3844 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
|
nicholas@2498
|
3845 }
|
nicholas@2498
|
3846
|
nicholas@2498
|
3847 // Add any page metrics
|
nicholas@2498
|
3848 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3849 this.XMLDOM.appendChild(page_metric);
|
nicholas@2498
|
3850
|
nicholas@2498
|
3851 // Add the audioelement
|
nicholas@2708
|
3852 this.specification.audioElements.forEach(function (element) {
|
nicholas@2498
|
3853 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2498
|
3854 aeNode.setAttribute('ref', element.id);
|
nicholas@2708
|
3855 if (element.name !== undefined) {
|
nicholas@2708
|
3856 aeNode.setAttribute('name', element.name);
|
nicholas@2708
|
3857 }
|
nicholas@2498
|
3858 aeNode.setAttribute('type', element.type);
|
nicholas@2498
|
3859 aeNode.setAttribute('url', element.url);
|
nicholas@2498
|
3860 aeNode.setAttribute('fqurl', qualifyURL(element.url));
|
nicholas@2498
|
3861 aeNode.setAttribute('gain', element.gain);
|
nicholas@2498
|
3862 if (element.type == 'anchor' || element.type == 'reference') {
|
nicholas@2498
|
3863 if (element.marker > 0) {
|
nicholas@2498
|
3864 aeNode.setAttribute('marker', element.marker);
|
nicholas@2464
|
3865 }
|
nicholas@2498
|
3866 }
|
nicholas@2498
|
3867 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3868 aeNode.appendChild(ae_metric);
|
nicholas@2498
|
3869 this.XMLDOM.appendChild(aeNode);
|
nicholas@2708
|
3870 }, this);
|
nicholas@2498
|
3871
|
nicholas@2498
|
3872 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3873
|
nicholas@2498
|
3874 this.complete = function () {
|
nicholas@2224
|
3875 this.state = "complete";
|
nicholas@2498
|
3876 this.XMLDOM.setAttribute("state", "complete");
|
nicholas@2708
|
3877 };
|
nicholas@2498
|
3878 };
|
nicholas@2498
|
3879 this.update = function () {
|
nicholas@2224
|
3880 this.SessionKey.update();
|
nicholas@2708
|
3881 };
|
nicholas@2498
|
3882 this.finish = function () {
|
nicholas@2498
|
3883 this.state = 1;
|
nicholas@2498
|
3884 this.root.setAttribute("state", "complete");
|
nicholas@2498
|
3885 return this.root;
|
nicholas@2498
|
3886 };
|
nicholas@2722
|
3887
|
nicholas@2722
|
3888 Object.defineProperties(this, {
|
nicholas@2722
|
3889 'filenamePrefix': {
|
nicholas@2722
|
3890 'get': function () {
|
nicholas@2722
|
3891 return pFilenamePrefix;
|
nicholas@2722
|
3892 },
|
nicholas@2722
|
3893 'set': function (value) {
|
nicholas@2722
|
3894 if (typeof value !== "string") {
|
nicholas@2722
|
3895 value = String(value);
|
nicholas@2722
|
3896 }
|
nicholas@2722
|
3897 pFilenamePrefix = value;
|
nicholas@2722
|
3898 return value;
|
nicholas@2722
|
3899 }
|
nicholas@2722
|
3900 }
|
nicholas@2725
|
3901 });
|
nicholas@2224
|
3902 }
|
nicholas@2384
|
3903
|
nicholas@2401
|
3904 var window_depedancy_callback;
|
nicholas@2498
|
3905 window_depedancy_callback = window.setInterval(function () {
|
nicholas@2401
|
3906 if (check_dependancies()) {
|
nicholas@2401
|
3907 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
3908 onload();
|
nicholas@2401
|
3909 } else {
|
nicholas@2401
|
3910 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
3911 }
|
nicholas@2498
|
3912 }, 100);
|