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