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@2678
|
1217 for (var i = 0; i < specification.pages.length; i++) {
|
nicholas@2678
|
1218 var page = specification.pages[i];
|
nicholas@2224
|
1219 if (page.alwaysInclude) {
|
nicholas@2224
|
1220 pageInclude.push(page);
|
nicholas@2224
|
1221 } else {
|
nicholas@2224
|
1222 pagePool.push(page);
|
nicholas@2224
|
1223 }
|
nicholas@2498
|
1224 }
|
nicholas@2498
|
1225
|
nicholas@2224
|
1226 // Find how many are left to get
|
nicholas@2224
|
1227 var numPages = specification.poolSize;
|
nicholas@2224
|
1228 if (numPages > pagePool.length) {
|
nicholas@2224
|
1229 console.log("WARNING - You have specified more pages in <setup poolSize> than you have created!!");
|
nicholas@2224
|
1230 numPages = specification.pages.length;
|
nicholas@2224
|
1231 }
|
nicholas@2678
|
1232 if (specification.poolSize === 0) {
|
nicholas@2224
|
1233 numPages = specification.pages.length;
|
nicholas@2224
|
1234 }
|
nicholas@2224
|
1235 numPages -= pageInclude.length;
|
nicholas@2498
|
1236
|
nicholas@2224
|
1237 if (numPages > 0) {
|
nicholas@2224
|
1238 // Go find the rest of the pages from the pool
|
nicholas@2224
|
1239 var subarr = null;
|
nicholas@2224
|
1240 if (specification.randomiseOrder) {
|
nicholas@2224
|
1241 // Append a random sub-array
|
nicholas@2498
|
1242 subarr = randomSubArray(pagePool, numPages);
|
nicholas@2224
|
1243 } else {
|
nicholas@2224
|
1244 // Append the matching number
|
nicholas@2498
|
1245 subarr = pagePool.slice(0, numPages);
|
nicholas@2224
|
1246 }
|
nicholas@2224
|
1247 pageInclude = pageInclude.concat(subarr);
|
nicholas@2224
|
1248 }
|
nicholas@2498
|
1249
|
nicholas@2224
|
1250 // We now have our selected pages in pageInclude array
|
nicholas@2498
|
1251 if (specification.randomiseOrder) {
|
nicholas@2498
|
1252 pageInclude = randomiseOrder(pageInclude);
|
nicholas@2498
|
1253 }
|
nicholas@2498
|
1254 for (var i = 0; i < pageInclude.length; i++) {
|
nicholas@2224
|
1255 pageInclude[i].presentedId = i;
|
nicholas@2498
|
1256 this.stateMap.push(pageInclude[i]);
|
nicholas@2224
|
1257 // For each selected page, we must get the sub pool
|
nicholas@2678
|
1258 if (pageInclude[i].poolSize !== 0 && pageInclude[i].poolSize !== pageInclude[i].audioElements.length) {
|
nicholas@2224
|
1259 var elemInclude = [];
|
nicholas@2224
|
1260 var elemPool = [];
|
nicholas@2224
|
1261 for (var elem of pageInclude[i].audioElements) {
|
nicholas@2559
|
1262 if (elem.alwaysInclude || elem.type != "normal") {
|
nicholas@2224
|
1263 elemInclude.push(elem);
|
nicholas@2224
|
1264 } else {
|
nicholas@2224
|
1265 elemPool.push(elem);
|
nicholas@2224
|
1266 }
|
nicholas@2224
|
1267 }
|
nicholas@2224
|
1268 var numElems = pageInclude[i].poolSize - elemInclude.length;
|
nicholas@2498
|
1269 pageInclude[i].audioElements = elemInclude.concat(randomSubArray(elemPool, numElems));
|
nicholas@2224
|
1270 }
|
nicholas@2224
|
1271 storage.createTestPageStore(pageInclude[i]);
|
nicholas@2224
|
1272 audioEngineContext.loadPageData(pageInclude[i]);
|
nicholas@2498
|
1273 }
|
nicholas@2498
|
1274
|
nicholas@2678
|
1275 if (specification.preTest !== null) {
|
nicholas@2498
|
1276 this.preTestSurvey = specification.preTest;
|
nicholas@2498
|
1277 }
|
nicholas@2678
|
1278 if (specification.postTest !== null) {
|
nicholas@2498
|
1279 this.postTestSurvey = specification.postTest;
|
nicholas@2498
|
1280 }
|
nicholas@2498
|
1281
|
nicholas@2498
|
1282 if (this.stateMap.length > 0) {
|
nicholas@2678
|
1283 if (this.stateIndex !== null) {
|
nicholas@2498
|
1284 console.log('NOTE - State already initialise');
|
nicholas@2498
|
1285 }
|
nicholas@2498
|
1286 this.stateIndex = -2;
|
nicholas@2224
|
1287 console.log('Starting test...');
|
nicholas@2498
|
1288 } else {
|
nicholas@2498
|
1289 console.log('FATAL - StateMap not correctly constructed. EMPTY_STATE_MAP');
|
nicholas@2498
|
1290 }
|
nicholas@2498
|
1291 };
|
nicholas@2498
|
1292 this.advanceState = function () {
|
nicholas@2678
|
1293 if (this.stateIndex === null) {
|
nicholas@2498
|
1294 this.initialise();
|
nicholas@2498
|
1295 }
|
nicholas@2357
|
1296 if (this.stateIndex > -2) {
|
nicholas@2357
|
1297 storage.update();
|
nicholas@2357
|
1298 }
|
nicholas@2498
|
1299 if (this.stateIndex == -2) {
|
nicholas@2224
|
1300 this.stateIndex++;
|
nicholas@2678
|
1301 if (this.preTestSurvey !== null) {
|
nicholas@2498
|
1302 popup.initState(this.preTestSurvey, storage.globalPreTest);
|
nicholas@2498
|
1303 } else {
|
nicholas@2498
|
1304 this.advanceState();
|
nicholas@2498
|
1305 }
|
nicholas@2498
|
1306 } else if (this.stateIndex == -1) {
|
nicholas@2224
|
1307 this.stateIndex++;
|
nicholas@2224
|
1308 if (specification.calibration) {
|
nicholas@2224
|
1309 popup.showPopup();
|
nicholas@2224
|
1310 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
|
1311 interfaceContext.calibrationModuleObject = new interfaceContext.calibrationModule();
|
nicholas@2224
|
1312 interfaceContext.calibrationModuleObject.build(popup.popupResponse);
|
nicholas@2224
|
1313 popup.hidePreviousButton();
|
nicholas@2224
|
1314 } else {
|
nicholas@2224
|
1315 this.advanceState();
|
nicholas@2224
|
1316 }
|
nicholas@2498
|
1317 } else if (this.stateIndex == this.stateMap.length) {
|
nicholas@2498
|
1318 // All test pages complete, post test
|
nicholas@2498
|
1319 console.log('Ending test ...');
|
nicholas@2498
|
1320 this.stateIndex++;
|
nicholas@2678
|
1321 if (this.postTestSurvey === null) {
|
nicholas@2498
|
1322 this.advanceState();
|
nicholas@2498
|
1323 } else {
|
nicholas@2498
|
1324 popup.initState(this.postTestSurvey, storage.globalPostTest);
|
nicholas@2498
|
1325 }
|
nicholas@2498
|
1326 } else if (this.stateIndex > this.stateMap.length) {
|
nicholas@2498
|
1327 createProjectSave(specification.projectReturn);
|
nicholas@2498
|
1328 } else {
|
nicholas@2224
|
1329 popup.hidePopup();
|
nicholas@2678
|
1330 if (this.currentStateMap === null) {
|
nicholas@2498
|
1331 this.currentStateMap = this.stateMap[this.stateIndex];
|
nicholas@2349
|
1332 // Find and extract the outside reference
|
nicholas@2498
|
1333 var elements = [],
|
nicholas@2498
|
1334 ref = [];
|
nicholas@2399
|
1335 var elem;
|
nicholas@2498
|
1336 while (elem = this.currentStateMap.audioElements.pop()) {
|
nicholas@2399
|
1337 if (elem.type == "outside-reference") {
|
nicholas@2399
|
1338 ref.push(elem);
|
nicholas@2498
|
1339 } else {
|
nicholas@2399
|
1340 elements.push(elem);
|
nicholas@2399
|
1341 }
|
nicholas@2349
|
1342 }
|
nicholas@2443
|
1343 elements = elements.reverse();
|
nicholas@2498
|
1344 if (this.currentStateMap.randomiseOrder) {
|
nicholas@2498
|
1345 elements = randomiseOrder(elements);
|
nicholas@2498
|
1346 }
|
nicholas@2399
|
1347 this.currentStateMap.audioElements = elements.concat(ref);
|
nicholas@2498
|
1348
|
nicholas@2224
|
1349 this.currentStore = storage.testPages[this.stateIndex];
|
nicholas@2678
|
1350 if (this.currentStateMap.preTest !== null) {
|
nicholas@2498
|
1351 this.currentStatePosition = 'pre';
|
nicholas@2498
|
1352 popup.initState(this.currentStateMap.preTest, storage.testPages[this.stateIndex].preTest);
|
nicholas@2498
|
1353 } else {
|
nicholas@2498
|
1354 this.currentStatePosition = 'test';
|
nicholas@2498
|
1355 }
|
nicholas@2498
|
1356 interfaceContext.newPage(this.currentStateMap, storage.testPages[this.stateIndex]);
|
nicholas@2498
|
1357 return;
|
nicholas@2498
|
1358 }
|
nicholas@2498
|
1359 switch (this.currentStatePosition) {
|
nicholas@2498
|
1360 case 'pre':
|
nicholas@2498
|
1361 this.currentStatePosition = 'test';
|
nicholas@2498
|
1362 break;
|
nicholas@2498
|
1363 case 'test':
|
nicholas@2498
|
1364 this.currentStatePosition = 'post';
|
nicholas@2498
|
1365 // Save the data
|
nicholas@2498
|
1366 this.testPageCompleted();
|
nicholas@2678
|
1367 if (this.currentStateMap.postTest === null) {
|
nicholas@2498
|
1368 this.advanceState();
|
nicholas@2498
|
1369 return;
|
nicholas@2498
|
1370 } else {
|
nicholas@2498
|
1371 popup.initState(this.currentStateMap.postTest, storage.testPages[this.stateIndex].postTest);
|
nicholas@2498
|
1372 }
|
nicholas@2498
|
1373 break;
|
nicholas@2498
|
1374 case 'post':
|
nicholas@2498
|
1375 this.stateIndex++;
|
nicholas@2498
|
1376 this.currentStateMap = null;
|
nicholas@2498
|
1377 this.advanceState();
|
nicholas@2498
|
1378 break;
|
nicholas@2678
|
1379 }
|
nicholas@2498
|
1380 }
|
nicholas@2498
|
1381 };
|
nicholas@2498
|
1382
|
nicholas@2498
|
1383 this.testPageCompleted = function () {
|
nicholas@2498
|
1384 // Function called each time a test page has been completed
|
nicholas@2498
|
1385 var storePoint = storage.testPages[this.stateIndex];
|
nicholas@2498
|
1386 // First get the test metric
|
nicholas@2498
|
1387
|
nicholas@2498
|
1388 var metric = storePoint.XMLDOM.getElementsByTagName('metric')[0];
|
nicholas@2498
|
1389 if (audioEngineContext.metric.enableTestTimer) {
|
nicholas@2498
|
1390 var testTime = storePoint.parent.document.createElement('metricresult');
|
nicholas@2498
|
1391 testTime.id = 'testTime';
|
nicholas@2498
|
1392 testTime.textContent = audioEngineContext.timer.testDuration;
|
nicholas@2498
|
1393 metric.appendChild(testTime);
|
nicholas@2498
|
1394 }
|
nicholas@2498
|
1395
|
nicholas@2498
|
1396 var audioObjects = audioEngineContext.audioObjects;
|
nicholas@2498
|
1397 for (var ao of audioEngineContext.audioObjects) {
|
nicholas@2498
|
1398 ao.exportXMLDOM();
|
nicholas@2498
|
1399 }
|
nicholas@2498
|
1400 for (var element of interfaceContext.commentQuestions) {
|
nicholas@2498
|
1401 element.exportXMLDOM(storePoint);
|
nicholas@2498
|
1402 }
|
nicholas@2498
|
1403 pageXMLSave(storePoint.XMLDOM, this.currentStateMap);
|
nicholas@2224
|
1404 storePoint.complete();
|
nicholas@2498
|
1405 };
|
nicholas@2498
|
1406
|
nicholas@2498
|
1407 this.getCurrentTestPage = function () {
|
nicholas@2498
|
1408 if (this.stateIndex >= 0 && this.stateIndex < this.stateMap.length) {
|
nicholas@2310
|
1409 return this.currentStateMap;
|
nicholas@2310
|
1410 } else {
|
nicholas@2310
|
1411 return null;
|
nicholas@2310
|
1412 }
|
nicholas@2678
|
1413 };
|
nicholas@2498
|
1414 this.getCurrentTestPageStore = function () {
|
nicholas@2498
|
1415 if (this.stateIndex >= 0 && this.stateIndex < this.stateMap.length) {
|
nicholas@2312
|
1416 return this.currentStore;
|
nicholas@2312
|
1417 } else {
|
nicholas@2312
|
1418 return null;
|
nicholas@2312
|
1419 }
|
nicholas@2678
|
1420 };
|
nicholas@2224
|
1421 }
|
nicholas@2224
|
1422
|
nicholas@2224
|
1423 function AudioEngine(specification) {
|
nicholas@2498
|
1424
|
nicholas@2498
|
1425 // Create two output paths, the main outputGain and fooGain.
|
nicholas@2498
|
1426 // Output gain is default to 1 and any items for playback route here
|
nicholas@2498
|
1427 // Foo gain is used for analysis to ensure paths get processed, but are not heard
|
nicholas@2498
|
1428 // because web audio will optimise and any route which does not go to the destination gets ignored.
|
nicholas@2498
|
1429 this.outputGain = audioContext.createGain();
|
nicholas@2498
|
1430 this.fooGain = audioContext.createGain();
|
nicholas@2508
|
1431 this.fooGain.gain.value = 0;
|
nicholas@2498
|
1432
|
nicholas@2498
|
1433 // Use this to detect playback state: 0 - stopped, 1 - playing
|
nicholas@2498
|
1434 this.status = 0;
|
nicholas@2498
|
1435
|
nicholas@2498
|
1436 // Connect both gains to output
|
nicholas@2498
|
1437 this.outputGain.connect(audioContext.destination);
|
nicholas@2498
|
1438 this.fooGain.connect(audioContext.destination);
|
nicholas@2498
|
1439
|
nicholas@2498
|
1440 // Create the timer Object
|
nicholas@2498
|
1441 this.timer = new timer();
|
nicholas@2498
|
1442 // Create session metrics
|
nicholas@2498
|
1443 this.metric = new sessionMetrics(this, specification);
|
nicholas@2498
|
1444
|
nicholas@2498
|
1445 this.loopPlayback = false;
|
nicholas@2351
|
1446 this.synchPlayback = false;
|
nicholas@2351
|
1447 this.pageSpecification = null;
|
nicholas@2498
|
1448
|
nicholas@2498
|
1449 this.pageStore = null;
|
nicholas@2498
|
1450
|
nicholas@2508
|
1451 // Chrome 53+ Error solution
|
nicholas@2508
|
1452 // Empty buffer for keep-alive
|
nicholas@2508
|
1453 var nullBuffer = audioContext.createBuffer(1, audioContext.sampleRate, audioContext.sampleRate);
|
nicholas@2508
|
1454 this.nullBufferSource = audioContext.createBufferSource();
|
nicholas@2508
|
1455 this.nullBufferSource.buffer = nullBuffer;
|
nicholas@2508
|
1456 this.nullBufferSource.loop = true;
|
nicholas@2508
|
1457 this.nullBufferSource.start(0);
|
nicholas@2508
|
1458
|
nicholas@2498
|
1459 // Create store for new audioObjects
|
nicholas@2498
|
1460 this.audioObjects = [];
|
nicholas@2498
|
1461
|
nicholas@2498
|
1462 this.buffers = [];
|
nicholas@2498
|
1463 this.bufferObj = function () {
|
nicholas@2617
|
1464 var urls = [];
|
nicholas@2498
|
1465 this.buffer = null;
|
nicholas@2498
|
1466 this.users = [];
|
nicholas@2224
|
1467 this.progress = 0;
|
nicholas@2224
|
1468 this.status = 0;
|
nicholas@2498
|
1469 this.ready = function () {
|
nicholas@2498
|
1470 if (this.status >= 2) {
|
nicholas@2224
|
1471 this.status = 3;
|
nicholas@2224
|
1472 }
|
nicholas@2498
|
1473 for (var i = 0; i < this.users.length; i++) {
|
nicholas@2498
|
1474 this.users[i].state = 1;
|
nicholas@2678
|
1475 if (this.users[i].interfaceDOM !== null) {
|
nicholas@2498
|
1476 this.users[i].bufferLoaded(this);
|
nicholas@2498
|
1477 }
|
nicholas@2498
|
1478 }
|
nicholas@2498
|
1479 };
|
nicholas@2617
|
1480 this.setUrls = function (obj) {
|
nicholas@2617
|
1481 // Obj must be an array of pairs:
|
nicholas@2617
|
1482 // [{sampleRate, url}]
|
nicholas@2617
|
1483 var localFs = audioContext.sampleRate,
|
nicholas@2617
|
1484 list = [],
|
nicholas@2617
|
1485 i;
|
nicholas@2617
|
1486 for (i = 0; i < obj.length; i++) {
|
nicholas@2617
|
1487 if (obj[i].sampleRate == localFs) {
|
nicholas@2617
|
1488 list.push(obj.splice(i, 1)[0]);
|
nicholas@2617
|
1489 }
|
nicholas@2617
|
1490 }
|
nicholas@2617
|
1491 list = list.concat(obj);
|
nicholas@2617
|
1492 urls = list;
|
nicholas@2617
|
1493 };
|
nicholas@2617
|
1494 this.hasUrl = function (checkUrl) {
|
nicholas@2617
|
1495 var l = urls.length,
|
nicholas@2617
|
1496 i;
|
nicholas@2617
|
1497 for (i = 0; i < l; i++) {
|
nicholas@2617
|
1498 if (urls[i].url == checkUrl) {
|
nicholas@2617
|
1499 return true;
|
nicholas@2617
|
1500 }
|
nicholas@2617
|
1501 }
|
nicholas@2617
|
1502 return false;
|
nicholas@2678
|
1503 };
|
nicholas@2617
|
1504 this.getMedia = function () {
|
nicholas@2615
|
1505 var self = this;
|
nicholas@2616
|
1506 var currentUrlIndex = 0;
|
nicholas@2498
|
1507
|
nicholas@2615
|
1508 function get(fqurl) {
|
nicholas@2615
|
1509 return new Promise(function (resolve, reject) {
|
nicholas@2615
|
1510 var req = new XMLHttpRequest();
|
nicholas@2615
|
1511 req.open('GET', fqurl, true);
|
nicholas@2615
|
1512 req.responseType = 'arraybuffer';
|
nicholas@2615
|
1513 req.onload = function () {
|
nicholas@2615
|
1514 if (req.status == 200) {
|
nicholas@2615
|
1515 resolve(req.response);
|
nicholas@2615
|
1516 }
|
nicholas@2615
|
1517 };
|
nicholas@2615
|
1518 req.onerror = function () {
|
nicholas@2615
|
1519 reject(new Error(req.statusText));
|
nicholas@2615
|
1520 };
|
nicholas@2615
|
1521
|
nicholas@2615
|
1522 req.addEventListener("progress", progressCallback.bind(self));
|
nicholas@2615
|
1523 req.send();
|
nicholas@2615
|
1524 });
|
nicholas@2615
|
1525 }
|
nicholas@2615
|
1526
|
nicholas@2615
|
1527 function getNextURL() {
|
nicholas@2615
|
1528 currentUrlIndex++;
|
nicholas@2615
|
1529 var self = this;
|
nicholas@2617
|
1530 if (currentUrlIndex >= urls.length) {
|
nicholas@2615
|
1531 processError();
|
nicholas@2615
|
1532 } else {
|
nicholas@2617
|
1533 return get(urls[currentUrlIndex].url).then(processAudio.bind(self)).catch(getNextURL.bind(self));
|
nicholas@2615
|
1534 }
|
nicholas@2615
|
1535 }
|
nicholas@2498
|
1536
|
nicholas@2498
|
1537 // Create callback to decode the data asynchronously
|
nicholas@2615
|
1538 function processAudio(response) {
|
nicholas@2615
|
1539 var self = this;
|
nicholas@2615
|
1540 return audioContext.decodeAudioData(response, function (decodedData) {
|
nicholas@2615
|
1541 self.buffer = decodedData;
|
nicholas@2615
|
1542 self.status = 2;
|
nicholas@2615
|
1543 calculateLoudness(self, "I");
|
nicholas@2615
|
1544 return true;
|
nicholas@2498
|
1545 }, function (e) {
|
nicholas@2403
|
1546 var waveObj = new WAVE();
|
nicholas@2678
|
1547 if (waveObj.open(response) === 0) {
|
nicholas@2615
|
1548 self.buffer = audioContext.createBuffer(waveObj.num_channels, waveObj.num_samples, waveObj.sample_rate);
|
nicholas@2498
|
1549 for (var c = 0; c < waveObj.num_channels; c++) {
|
nicholas@2615
|
1550 var buffer_ptr = self.buffer.getChannelData(c);
|
nicholas@2498
|
1551 for (var n = 0; n < waveObj.num_samples; n++) {
|
nicholas@2403
|
1552 buffer_ptr[n] = waveObj.decoded_data[c][n];
|
nicholas@2224
|
1553 }
|
nicholas@2224
|
1554 }
|
nicholas@2403
|
1555 }
|
nicholas@2678
|
1556 if (self.buffer !== undefined) {
|
nicholas@2615
|
1557 self.status = 2;
|
nicholas@2615
|
1558 calculateLoudness(self, "I");
|
nicholas@2615
|
1559 return true;
|
nicholas@2403
|
1560 }
|
nicholas@2678
|
1561 waveObj = undefined;
|
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@2678
|
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@2678
|
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@2678
|
1614 if (preSilenceTime === undefined) {
|
nicholas@2498
|
1615 preSilenceTime = 0;
|
nicholas@2498
|
1616 }
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
1703 if (this.status === 1) {
|
nicholas@2498
|
1704 this.timer.startTest();
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
2020 if (interfaceXML !== null) {
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
2244 if (this.parent.commentDOM === null) {
|
nicholas@2498
|
2245 flag.textContent = 'false';
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
2858 };
|
nicholas@2678
|
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@2678
|
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@2678
|
2981 };
|
nicholas@2498
|
2982 this.slider.onmouseup = function (event) {
|
nicholas@2224
|
2983 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
3014 };
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
3101 };
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
3212 if (ao.metric.wasListenedTo === false) {
|
nicholas@2498
|
3213 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@2498
|
3214 }
|
nicholas@2498
|
3215 }
|
nicholas@2678
|
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@2678
|
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@2678
|
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@2678
|
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@2678
|
3304 if (index === 0) {
|
nicholas@2625
|
3305 return "Same";
|
nicholas@2625
|
3306 } else if (index == 1) {
|
nicholas@2625
|
3307 return "Difference";
|
nicholas@2625
|
3308 }
|
nicholas@2678
|
3309 return "";
|
nicholas@2595
|
3310 case "number":
|
nicholas@2595
|
3311 return String(index + offset);
|
nicholas@2595
|
3312 default:
|
nicholas@2595
|
3313 return "";
|
nicholas@2595
|
3314 }
|
nicholas@2595
|
3315 }
|
nicholas@2595
|
3316
|
nicholas@2678
|
3317 if (typeof labelStart !== "string" || labelStart.length === 0) {
|
nicholas@2595
|
3318 labelStart = String.fromCharCode(0);
|
nicholas@2595
|
3319 }
|
nicholas@2595
|
3320
|
nicholas@2595
|
3321 switch (labelType) {
|
nicholas@2595
|
3322 case "letter":
|
nicholas@2595
|
3323 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3324 if (labelStart < 97 || labelStart > 122) {
|
nicholas@2595
|
3325 labelStart = 97;
|
nicholas@2595
|
3326 }
|
nicholas@2595
|
3327 labelStart -= 97;
|
nicholas@2595
|
3328 break;
|
nicholas@2595
|
3329 case "capital":
|
nicholas@2595
|
3330 labelStart = labelStart.charCodeAt(0);
|
nicholas@2596
|
3331 if (labelStart < 65 || labelStart > 90) {
|
nicholas@2595
|
3332 labelStart = 65;
|
nicholas@2595
|
3333 }
|
nicholas@2595
|
3334 labelStart -= 65;
|
nicholas@2595
|
3335 break;
|
nicholas@2595
|
3336 case "number":
|
nicholas@2608
|
3337 labelStart = Number(labelStart);
|
nicholas@2608
|
3338 if (!isFinite(labelStart)) {
|
nicholas@2595
|
3339 labelStart = 1;
|
nicholas@2595
|
3340 }
|
nicholas@2595
|
3341 break;
|
nicholas@2595
|
3342 case "none":
|
nicholas@2595
|
3343 default:
|
nicholas@2596
|
3344 labelStart = 0;
|
nicholas@2595
|
3345 }
|
nicholas@2595
|
3346 if (typeof index == "number") {
|
nicholas@2595
|
3347 return calculateLabel(labelType, index, labelStart);
|
nicholas@2595
|
3348 } else if (index.length && index.length > 0) {
|
nicholas@2595
|
3349 var a = [],
|
nicholas@2595
|
3350 l = index.length,
|
nicholas@2595
|
3351 i;
|
nicholas@2595
|
3352 for (i = 0; i < l; i++) {
|
nicholas@2595
|
3353 a[i] = calculateLabel(labelType, index[i], labelStart);
|
nicholas@2595
|
3354 }
|
nicholas@2595
|
3355 return a;
|
nicholas@2595
|
3356 } else {
|
nicholas@2595
|
3357 throw ("Invalid arguments");
|
nicholas@2595
|
3358 }
|
nicholas@2678
|
3359 };
|
nicholas@2649
|
3360
|
nicholas@2649
|
3361 this.getCombinedInterfaces = function (page) {
|
nicholas@2649
|
3362 // Combine the interfaces with the global interface nodes
|
nicholas@2649
|
3363 var global = specification.interfaces,
|
nicholas@2649
|
3364 local = page.interfaces;
|
nicholas@2649
|
3365 local.forEach(function (locInt) {
|
nicholas@2649
|
3366 // Iterate through the options nodes
|
nicholas@2649
|
3367 var addList = [];
|
nicholas@2649
|
3368 global.options.forEach(function (gopt) {
|
nicholas@2649
|
3369 var lopt = locInt.options.find(function (lopt) {
|
nicholas@2649
|
3370 return (lopt.name == gopt.name) && (lopt.type == gopt.type);
|
nicholas@2649
|
3371 });
|
nicholas@2649
|
3372 if (!lopt) {
|
nicholas@2649
|
3373 // Global option doesn't exist locally
|
nicholas@2649
|
3374 addList.push(gopt);
|
nicholas@2649
|
3375 }
|
nicholas@2649
|
3376 });
|
nicholas@2649
|
3377 locInt.options = locInt.options.concat(addList);
|
nicholas@2649
|
3378 if (!locInt.scales && global.scales) {
|
nicholas@2649
|
3379 // Use the global default scales
|
nicholas@2649
|
3380 locInt.scales = global.scales;
|
nicholas@2649
|
3381 }
|
nicholas@2649
|
3382 });
|
nicholas@2649
|
3383 return local;
|
nicholas@2678
|
3384 };
|
nicholas@2224
|
3385 }
|
nicholas@2224
|
3386
|
nicholas@2498
|
3387 function Storage() {
|
nicholas@2498
|
3388 // Holds results in XML format until ready for collection
|
nicholas@2498
|
3389 this.globalPreTest = null;
|
nicholas@2498
|
3390 this.globalPostTest = null;
|
nicholas@2498
|
3391 this.testPages = [];
|
nicholas@2498
|
3392 this.document = null;
|
nicholas@2498
|
3393 this.root = null;
|
nicholas@2498
|
3394 this.state = 0;
|
nicholas@2498
|
3395
|
nicholas@2498
|
3396 this.initialise = function (existingStore) {
|
nicholas@2678
|
3397 if (existingStore === undefined) {
|
nicholas@2224
|
3398 // We need to get the sessionKey
|
nicholas@2510
|
3399 this.SessionKey.requestKey();
|
nicholas@2498
|
3400 this.document = document.implementation.createDocument(null, "waetresult", null);
|
nicholas@2224
|
3401 this.root = this.document.childNodes[0];
|
nicholas@2224
|
3402 var projectDocument = specification.projectXML;
|
nicholas@2498
|
3403 projectDocument.setAttribute('file-name', url);
|
nicholas@2498
|
3404 projectDocument.setAttribute('url', qualifyURL(url));
|
nicholas@2224
|
3405 this.root.appendChild(projectDocument);
|
nicholas@2224
|
3406 this.root.appendChild(interfaceContext.returnDateNode());
|
nicholas@2224
|
3407 this.root.appendChild(interfaceContext.returnNavigator());
|
nicholas@2224
|
3408 } else {
|
nicholas@2224
|
3409 this.document = existingStore;
|
nicholas@2294
|
3410 this.root = existingStore.firstChild;
|
nicholas@2224
|
3411 this.SessionKey.key = this.root.getAttribute("key");
|
nicholas@2224
|
3412 }
|
nicholas@2678
|
3413 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3414 this.globalPreTest = new this.surveyNode(this, this.root, specification.preTest);
|
nicholas@2498
|
3415 }
|
nicholas@2678
|
3416 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3417 this.globalPostTest = new this.surveyNode(this, this.root, specification.postTest);
|
nicholas@2498
|
3418 }
|
nicholas@2498
|
3419 };
|
nicholas@2498
|
3420
|
nicholas@2224
|
3421 this.SessionKey = {
|
nicholas@2224
|
3422 key: null,
|
nicholas@2224
|
3423 request: new XMLHttpRequest(),
|
nicholas@2224
|
3424 parent: this,
|
nicholas@2498
|
3425 handleEvent: function () {
|
nicholas@2224
|
3426 var parse = new DOMParser();
|
nicholas@2498
|
3427 var xml = parse.parseFromString(this.request.response, "text/xml");
|
nicholas@2678
|
3428 if (this.request.response.length === 0) {
|
nicholas@2515
|
3429 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2376
|
3430 return;
|
nicholas@2376
|
3431 }
|
nicholas@2498
|
3432 if (xml.getElementsByTagName("state").length > 0) {
|
nicholas@2498
|
3433 if (xml.getElementsByTagName("state")[0].textContent == "OK") {
|
nicholas@2498
|
3434 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
nicholas@2498
|
3435 this.parent.root.setAttribute("key", this.key);
|
nicholas@2498
|
3436 this.parent.root.setAttribute("state", "empty");
|
nicholas@2516
|
3437 this.update();
|
nicholas@2515
|
3438 return;
|
nicholas@2514
|
3439 } else if (xml.getElementsByTagName("state")[0].textContent == "ERROR") {
|
nicholas@2515
|
3440 this.key = null;
|
nicholas@2514
|
3441 console.error("Could not generate server key. Server responded with error message: \"" + xml.getElementsByTagName("message")[0].textContent + "\"");
|
nicholas@2515
|
3442 return;
|
nicholas@2498
|
3443 }
|
nicholas@2498
|
3444 }
|
nicholas@2515
|
3445 this.key = null;
|
nicholas@2515
|
3446 console.error("An unspecified error occured, no server key could be generated");
|
nicholas@2224
|
3447 },
|
nicholas@2510
|
3448 requestKey: function () {
|
nicholas@2510
|
3449 // For new servers, request a new key from the server
|
nicholas@2510
|
3450 var returnURL = "";
|
nicholas@2510
|
3451 if (typeof specification.projectReturn == "string") {
|
nicholas@2510
|
3452 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2510
|
3453 returnURL = specification.projectReturn;
|
nicholas@2510
|
3454 }
|
nicholas@2510
|
3455 }
|
nicholas@2510
|
3456 this.request.open("GET", returnURL + "php/requestKey.php", true);
|
nicholas@2510
|
3457 this.request.addEventListener("load", this);
|
nicholas@2510
|
3458 this.request.send();
|
nicholas@2510
|
3459 },
|
nicholas@2498
|
3460 update: function () {
|
nicholas@2678
|
3461 if (this.key === null) {
|
nicholas@2357
|
3462 console.log("Cannot save as key == null");
|
nicholas@2357
|
3463 return;
|
nicholas@2357
|
3464 }
|
nicholas@2498
|
3465 this.parent.root.setAttribute("state", "update");
|
nicholas@2224
|
3466 var xmlhttp = new XMLHttpRequest();
|
nicholas@2302
|
3467 var returnURL = "";
|
nicholas@2302
|
3468 if (typeof specification.projectReturn == "string") {
|
nicholas@2498
|
3469 if (specification.projectReturn.substr(0, 4) == "http") {
|
nicholas@2302
|
3470 returnURL = specification.projectReturn;
|
nicholas@2302
|
3471 }
|
nicholas@2302
|
3472 }
|
nicholas@2498
|
3473 xmlhttp.open("POST", returnURL + "php/save.php?key=" + this.key);
|
nicholas@2224
|
3474 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
nicholas@2498
|
3475 xmlhttp.onerror = function () {
|
nicholas@2224
|
3476 console.log('Error updating file to server!');
|
nicholas@2224
|
3477 };
|
nicholas@2224
|
3478 var hold = document.createElement("div");
|
nicholas@2224
|
3479 var clone = this.parent.root.cloneNode(true);
|
nicholas@2224
|
3480 hold.appendChild(clone);
|
nicholas@2498
|
3481 xmlhttp.onload = function () {
|
nicholas@2224
|
3482 if (this.status >= 300) {
|
nicholas@2224
|
3483 console.log("WARNING - Could not update at this time");
|
nicholas@2224
|
3484 } else {
|
nicholas@2224
|
3485 var parser = new DOMParser();
|
nicholas@2224
|
3486 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
nicholas@2224
|
3487 var response = xmlDoc.getElementsByTagName('response')[0];
|
nicholas@2224
|
3488 if (response.getAttribute("state") == "OK") {
|
nicholas@2224
|
3489 var file = response.getElementsByTagName("file")[0];
|
nicholas@2498
|
3490 console.log("Intermediate save: OK, written " + file.getAttribute("bytes") + "B");
|
nicholas@2224
|
3491 } else {
|
nicholas@2224
|
3492 var message = response.getElementsByTagName("message");
|
nicholas@2498
|
3493 console.log("Intermediate save: Error! " + message.textContent);
|
nicholas@2224
|
3494 }
|
nicholas@2224
|
3495 }
|
nicholas@2678
|
3496 };
|
nicholas@2224
|
3497 xmlhttp.send([hold.innerHTML]);
|
nicholas@2224
|
3498 }
|
nicholas@2678
|
3499 };
|
nicholas@2498
|
3500
|
nicholas@2498
|
3501 this.createTestPageStore = function (specification) {
|
nicholas@2498
|
3502 var store = new this.pageNode(this, specification);
|
nicholas@2498
|
3503 this.testPages.push(store);
|
nicholas@2498
|
3504 return this.testPages[this.testPages.length - 1];
|
nicholas@2498
|
3505 };
|
nicholas@2498
|
3506
|
nicholas@2498
|
3507 this.surveyNode = function (parent, root, specification) {
|
nicholas@2498
|
3508 this.specification = specification;
|
nicholas@2498
|
3509 this.parent = parent;
|
nicholas@2224
|
3510 this.state = "empty";
|
nicholas@2498
|
3511 this.XMLDOM = this.parent.document.createElement('survey');
|
nicholas@2498
|
3512 this.XMLDOM.setAttribute('location', this.specification.location);
|
nicholas@2498
|
3513 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2498
|
3514 for (var optNode of this.specification.options) {
|
nicholas@2498
|
3515 if (optNode.type != 'statement') {
|
nicholas@2498
|
3516 var node = this.parent.document.createElement('surveyresult');
|
nicholas@2498
|
3517 node.setAttribute("ref", optNode.id);
|
nicholas@2498
|
3518 node.setAttribute('type', optNode.type);
|
nicholas@2498
|
3519 this.XMLDOM.appendChild(node);
|
nicholas@2498
|
3520 }
|
nicholas@2498
|
3521 }
|
nicholas@2498
|
3522 root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3523
|
nicholas@2498
|
3524 this.postResult = function (node) {
|
nicholas@2498
|
3525 // From popup: node is the popupOption node containing both spec. and results
|
nicholas@2498
|
3526 // ID is the position
|
nicholas@2498
|
3527 if (node.specification.type == 'statement') {
|
nicholas@2498
|
3528 return;
|
nicholas@2498
|
3529 }
|
nicholas@2498
|
3530 var surveyresult = this.XMLDOM.firstChild;
|
nicholas@2678
|
3531 while (surveyresult !== null) {
|
nicholas@2498
|
3532 if (surveyresult.getAttribute("ref") == node.specification.id) {
|
nicholas@2224
|
3533 break;
|
nicholas@2224
|
3534 }
|
nicholas@2224
|
3535 surveyresult = surveyresult.nextElementSibling;
|
nicholas@2224
|
3536 }
|
nicholas@2498
|
3537 switch (node.specification.type) {
|
nicholas@2498
|
3538 case "number":
|
nicholas@2498
|
3539 case "question":
|
n@2583
|
3540 case "slider":
|
nicholas@2498
|
3541 var child = this.parent.document.createElement('response');
|
nicholas@2498
|
3542 child.textContent = node.response;
|
nicholas@2498
|
3543 surveyresult.appendChild(child);
|
nicholas@2464
|
3544 break;
|
nicholas@2498
|
3545 case "radio":
|
nicholas@2498
|
3546 var child = this.parent.document.createElement('response');
|
nicholas@2571
|
3547 if (node.response !== null) {
|
nicholas@2571
|
3548 child.setAttribute('name', node.response.name);
|
nicholas@2571
|
3549 child.textContent = node.response.text;
|
nicholas@2571
|
3550 }
|
nicholas@2498
|
3551 surveyresult.appendChild(child);
|
nicholas@2498
|
3552 break;
|
nicholas@2498
|
3553 case "checkbox":
|
nicholas@2678
|
3554 if (node.response === undefined) {
|
nicholas@2498
|
3555 surveyresult.appendChild(this.parent.document.createElement('response'));
|
nicholas@2498
|
3556 break;
|
nicholas@2498
|
3557 }
|
nicholas@2498
|
3558 for (var i = 0; i < node.response.length; i++) {
|
nicholas@2498
|
3559 var checkNode = this.parent.document.createElement('response');
|
nicholas@2498
|
3560 checkNode.setAttribute('name', node.response[i].name);
|
nicholas@2498
|
3561 checkNode.setAttribute('checked', node.response[i].checked);
|
nicholas@2498
|
3562 surveyresult.appendChild(checkNode);
|
nicholas@2498
|
3563 }
|
nicholas@2498
|
3564 break;
|
nicholas@2498
|
3565 }
|
nicholas@2498
|
3566 };
|
nicholas@2498
|
3567 this.complete = function () {
|
nicholas@2498
|
3568 this.state = "complete";
|
nicholas@2498
|
3569 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2678
|
3570 };
|
nicholas@2498
|
3571 };
|
nicholas@2498
|
3572
|
nicholas@2498
|
3573 this.pageNode = function (parent, specification) {
|
nicholas@2498
|
3574 // Create one store per test page
|
nicholas@2498
|
3575 this.specification = specification;
|
nicholas@2498
|
3576 this.parent = parent;
|
nicholas@2498
|
3577 this.state = "empty";
|
nicholas@2498
|
3578 this.XMLDOM = this.parent.document.createElement('page');
|
nicholas@2498
|
3579 this.XMLDOM.setAttribute('ref', specification.id);
|
nicholas@2498
|
3580 this.XMLDOM.setAttribute('presentedId', specification.presentedId);
|
nicholas@2498
|
3581 this.XMLDOM.setAttribute("state", this.state);
|
nicholas@2678
|
3582 if (specification.preTest !== undefined) {
|
nicholas@2498
|
3583 this.preTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.preTest);
|
nicholas@2498
|
3584 }
|
nicholas@2678
|
3585 if (specification.postTest !== undefined) {
|
nicholas@2498
|
3586 this.postTest = new this.parent.surveyNode(this.parent, this.XMLDOM, this.specification.postTest);
|
nicholas@2498
|
3587 }
|
nicholas@2498
|
3588
|
nicholas@2498
|
3589 // Add any page metrics
|
nicholas@2498
|
3590 var page_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3591 this.XMLDOM.appendChild(page_metric);
|
nicholas@2498
|
3592
|
nicholas@2498
|
3593 // Add the audioelement
|
nicholas@2498
|
3594 for (var element of this.specification.audioElements) {
|
nicholas@2498
|
3595 var aeNode = this.parent.document.createElement('audioelement');
|
nicholas@2498
|
3596 aeNode.setAttribute('ref', element.id);
|
nicholas@2678
|
3597 if (element.name !== undefined) {
|
nicholas@2678
|
3598 aeNode.setAttribute('name', element.name);
|
nicholas@2678
|
3599 }
|
nicholas@2498
|
3600 aeNode.setAttribute('type', element.type);
|
nicholas@2498
|
3601 aeNode.setAttribute('url', element.url);
|
nicholas@2498
|
3602 aeNode.setAttribute('fqurl', qualifyURL(element.url));
|
nicholas@2498
|
3603 aeNode.setAttribute('gain', element.gain);
|
nicholas@2498
|
3604 if (element.type == 'anchor' || element.type == 'reference') {
|
nicholas@2498
|
3605 if (element.marker > 0) {
|
nicholas@2498
|
3606 aeNode.setAttribute('marker', element.marker);
|
nicholas@2464
|
3607 }
|
nicholas@2498
|
3608 }
|
nicholas@2498
|
3609 var ae_metric = this.parent.document.createElement('metric');
|
nicholas@2498
|
3610 aeNode.appendChild(ae_metric);
|
nicholas@2498
|
3611 this.XMLDOM.appendChild(aeNode);
|
nicholas@2498
|
3612 }
|
nicholas@2498
|
3613
|
nicholas@2498
|
3614 this.parent.root.appendChild(this.XMLDOM);
|
nicholas@2498
|
3615
|
nicholas@2498
|
3616 this.complete = function () {
|
nicholas@2224
|
3617 this.state = "complete";
|
nicholas@2498
|
3618 this.XMLDOM.setAttribute("state", "complete");
|
nicholas@2678
|
3619 };
|
nicholas@2498
|
3620 };
|
nicholas@2498
|
3621 this.update = function () {
|
nicholas@2224
|
3622 this.SessionKey.update();
|
nicholas@2678
|
3623 };
|
nicholas@2498
|
3624 this.finish = function () {
|
nicholas@2678
|
3625 if (this.state === 0) {
|
nicholas@2224
|
3626 this.update();
|
nicholas@2498
|
3627 }
|
nicholas@2498
|
3628 this.state = 1;
|
nicholas@2498
|
3629 this.root.setAttribute("state", "complete");
|
nicholas@2498
|
3630 return this.root;
|
nicholas@2498
|
3631 };
|
nicholas@2224
|
3632 }
|
nicholas@2384
|
3633
|
nicholas@2401
|
3634 var window_depedancy_callback;
|
nicholas@2498
|
3635 window_depedancy_callback = window.setInterval(function () {
|
nicholas@2401
|
3636 if (check_dependancies()) {
|
nicholas@2401
|
3637 window.clearInterval(window_depedancy_callback);
|
nicholas@2401
|
3638 onload();
|
nicholas@2401
|
3639 } else {
|
nicholas@2401
|
3640 document.getElementById("topLevelBody").innerHTML = "<h1>Loading Resources</h1>";
|
nicholas@2401
|
3641 }
|
nicholas@2498
|
3642 }, 100);
|