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