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