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