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