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