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