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