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