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