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