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