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@623
|
1815 this.processAttribute = function(attribute,schema,schemaRoot)
|
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@623
|
1821 schema = schemaRoot.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@623
|
1867 var attributeName = attributes[i].getAttribute('name') || attributes[i].getAttribute('ref');
|
n@453
|
1868 var projectAttr = setupNode.getAttribute(attributeName);
|
n@623
|
1869 projectAttr = this.processAttribute(projectAttr,attributes[i],this.schema);
|
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@623
|
1969 this.parent = null;
|
n@501
|
1970 this.schema = specification.schema.getAllElementsByName('survey')[0];
|
n@180
|
1971
|
n@374
|
1972 this.OptionNode = function() {
|
n@374
|
1973 this.type = undefined;
|
n@501
|
1974 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
|
n@374
|
1975 this.id = undefined;
|
n@597
|
1976 this.name = undefined;
|
n@374
|
1977 this.mandatory = undefined;
|
n@374
|
1978 this.statement = undefined;
|
n@374
|
1979 this.boxsize = undefined;
|
n@374
|
1980 this.options = [];
|
n@374
|
1981 this.min = undefined;
|
n@374
|
1982 this.max = undefined;
|
n@374
|
1983 this.step = undefined;
|
n@374
|
1984
|
n@501
|
1985 this.decode = function(parent,child)
|
n@374
|
1986 {
|
n@501
|
1987 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
1988 for (var i in attributeMap){
|
n@453
|
1989 if(isNaN(Number(i)) == true){break;}
|
n@453
|
1990 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
1991 var projectAttr = child.getAttribute(attributeName);
|
n@623
|
1992 projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
|
n@453
|
1993 switch(typeof projectAttr)
|
n@453
|
1994 {
|
n@453
|
1995 case "number":
|
n@453
|
1996 case "boolean":
|
n@453
|
1997 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
1998 break;
|
n@453
|
1999 case "string":
|
n@453
|
2000 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2001 break;
|
n@374
|
2002 }
|
n@453
|
2003 }
|
n@453
|
2004 this.statement = child.getElementsByTagName('statement')[0].textContent;
|
n@453
|
2005 if (this.type == "checkbox" || this.type == "radio") {
|
n@453
|
2006 var children = child.getElementsByTagName('option');
|
n@453
|
2007 if (children.length == null) {
|
n@374
|
2008 console.log('Malformed' +child.nodeName+ 'entry');
|
n@374
|
2009 this.statement = 'Malformed' +child.nodeName+ 'entry';
|
n@374
|
2010 this.type = 'statement';
|
n@374
|
2011 } else {
|
n@374
|
2012 this.options = [];
|
n@453
|
2013 for (var i in children)
|
n@453
|
2014 {
|
n@453
|
2015 if (isNaN(Number(i))==true){break;}
|
n@453
|
2016 this.options.push({
|
n@453
|
2017 name: children[i].getAttribute('name'),
|
n@453
|
2018 text: children[i].textContent
|
n@453
|
2019 });
|
n@374
|
2020 }
|
n@374
|
2021 }
|
n@191
|
2022 }
|
n@374
|
2023 };
|
n@374
|
2024
|
n@503
|
2025 this.exportXML = function(doc)
|
n@374
|
2026 {
|
n@544
|
2027 var node = doc.createElement('surveyentry');
|
n@453
|
2028 node.setAttribute('type',this.type);
|
n@503
|
2029 var statement = doc.createElement('statement');
|
n@453
|
2030 statement.textContent = this.statement;
|
n@453
|
2031 node.appendChild(statement);
|
n@620
|
2032 node.id = this.id;
|
n@620
|
2033 if (this.name != undefined) { node.setAttribute("name",this.name);}
|
n@620
|
2034 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
|
n@620
|
2035 node.id = this.id;
|
n@620
|
2036 if (this.name != undefined) {node.setAttribute("name",this.name);}
|
n@620
|
2037 switch(this.type)
|
n@620
|
2038 {
|
n@597
|
2039 case "checkbox":
|
n@597
|
2040 case "radio":
|
n@597
|
2041 for (var i=0; i<this.options.length; i++)
|
n@597
|
2042 {
|
n@597
|
2043 var option = this.options[i];
|
n@597
|
2044 var optionNode = doc.createElement("option");
|
n@597
|
2045 optionNode.setAttribute("name",option.name);
|
n@597
|
2046 optionNode.textContent = option.text;
|
n@597
|
2047 node.appendChild(optionNode);
|
n@597
|
2048 }
|
n@620
|
2049 case "number":
|
n@620
|
2050 if (this.min != undefined) {node.setAttribute("min", this.min);}
|
n@620
|
2051 if (this.max != undefined) {node.setAttribute("max", this.max);}
|
n@620
|
2052 case "question":
|
n@620
|
2053 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
|
n@620
|
2054 if (this.mandatory != undefined) {node.setAttribute("mandatory",this.mandatory);}
|
n@620
|
2055 default:
|
n@597
|
2056 break;
|
n@597
|
2057 }
|
n@374
|
2058 return node;
|
n@374
|
2059 };
|
n@374
|
2060 };
|
n@501
|
2061 this.decode = function(parent,xml) {
|
n@623
|
2062 this.parent = parent;
|
n@453
|
2063 this.location = xml.getAttribute('location');
|
n@453
|
2064 if (this.location == 'before'){this.location = 'pre';}
|
n@453
|
2065 else if (this.location == 'after'){this.location = 'post';}
|
n@453
|
2066 for (var i in xml.children)
|
n@453
|
2067 {
|
n@453
|
2068 if(isNaN(Number(i))==true){break;}
|
n@374
|
2069 var node = new this.OptionNode();
|
n@501
|
2070 node.decode(parent,xml.children[i]);
|
n@374
|
2071 this.options.push(node);
|
n@453
|
2072 }
|
n@453
|
2073 };
|
n@503
|
2074 this.encode = function(doc) {
|
n@503
|
2075 var node = doc.createElement('survey');
|
n@453
|
2076 node.setAttribute('location',this.location);
|
n@453
|
2077 for (var i=0; i<this.options.length; i++)
|
n@453
|
2078 {
|
n@503
|
2079 node.appendChild(this.options[i].exportXML(doc));
|
n@453
|
2080 }
|
n@453
|
2081 return node;
|
n@453
|
2082 };
|
n@453
|
2083 };
|
n@453
|
2084
|
n@453
|
2085 this.interfaceNode = function()
|
n@453
|
2086 {
|
n@453
|
2087 this.title = null;
|
n@453
|
2088 this.name = null;
|
n@453
|
2089 this.options = [];
|
n@453
|
2090 this.scales = [];
|
n@501
|
2091 this.schema = specification.schema.getAllElementsByName('interface')[1];
|
n@453
|
2092
|
n@501
|
2093 this.decode = function(parent,xml) {
|
n@453
|
2094 this.name = xml.getAttribute('name');
|
n@453
|
2095 var titleNode = xml.getElementsByTagName('title');
|
n@453
|
2096 if (titleNode.length == 1)
|
n@453
|
2097 {
|
n@453
|
2098 this.title = titleNode[0].textContent;
|
n@453
|
2099 }
|
n@453
|
2100 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
|
n@453
|
2101 // Extract interfaceoption node schema
|
n@501
|
2102 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
|
n@477
|
2103 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2104 for (var i=0; i<interfaceOptionNodes.length; i++)
|
n@453
|
2105 {
|
n@453
|
2106 var ioNode = interfaceOptionNodes[i];
|
n@453
|
2107 var option = {};
|
n@453
|
2108 for (var j=0; j<attributeMap.length; j++)
|
n@453
|
2109 {
|
n@453
|
2110 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
|
n@453
|
2111 var projectAttr = ioNode.getAttribute(attributeName);
|
n@623
|
2112 if(parent.processAttribute) {
|
n@623
|
2113 parent.processAttribute(projectAttr, attributeMap[j], parent.schema)
|
n@623
|
2114 } else {
|
n@623
|
2115 parent.parent.processAttribute(projectAttr, attributeMap[j], parent.parent.schema)
|
n@623
|
2116 }
|
n@453
|
2117 switch(typeof projectAttr)
|
n@453
|
2118 {
|
n@453
|
2119 case "number":
|
n@453
|
2120 case "boolean":
|
n@453
|
2121 eval('option.'+attributeName+' = '+projectAttr);
|
n@453
|
2122 break;
|
n@453
|
2123 case "string":
|
n@453
|
2124 eval('option.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2125 break;
|
n@453
|
2126 }
|
n@453
|
2127 }
|
n@453
|
2128 this.options.push(option);
|
n@453
|
2129 }
|
n@453
|
2130
|
n@453
|
2131 // Now the scales nodes
|
n@453
|
2132 var scaleParent = xml.getElementsByTagName('scales');
|
n@453
|
2133 if (scaleParent.length == 1) {
|
n@453
|
2134 scaleParent = scaleParent[0];
|
n@453
|
2135 for (var i=0; i<scaleParent.children.length; i++) {
|
n@453
|
2136 var child = scaleParent.children[i];
|
n@453
|
2137 this.scales.push({
|
n@453
|
2138 text: child.textContent,
|
n@453
|
2139 position: Number(child.getAttribute('position'))
|
n@453
|
2140 });
|
n@374
|
2141 }
|
n@180
|
2142 }
|
n@180
|
2143 };
|
n@453
|
2144
|
n@503
|
2145 this.encode = function(doc) {
|
n@503
|
2146 var node = doc.createElement("interface");
|
n@503
|
2147 if (typeof name == "string")
|
n@503
|
2148 node.setAttribute("name",this.name);
|
n@503
|
2149 for (var option of this.options)
|
n@503
|
2150 {
|
n@503
|
2151 var child = doc.createElement("interfaceoption");
|
n@503
|
2152 child.setAttribute("type",option.type);
|
n@503
|
2153 child.setAttribute("name",option.name);
|
n@503
|
2154 node.appendChild(child);
|
n@503
|
2155 }
|
n@503
|
2156 if (this.scales.length != 0) {
|
n@503
|
2157 var scales = doc.createElement("scales");
|
n@503
|
2158 for (var scale of this.scales)
|
n@503
|
2159 {
|
n@503
|
2160 var child = doc.createElement("scalelabel");
|
n@503
|
2161 child.setAttribute("position",scale.position);
|
n@503
|
2162 child.textContent = scale.text;
|
n@503
|
2163 scales.appendChild(child);
|
n@503
|
2164 }
|
n@503
|
2165 node.appendChild(scales);
|
n@503
|
2166 }
|
n@503
|
2167 return node;
|
n@453
|
2168 };
|
n@180
|
2169 };
|
n@180
|
2170
|
n@501
|
2171 this.metricNode = function() {
|
n@501
|
2172 this.enabled = [];
|
n@501
|
2173 this.decode = function(parent, xml) {
|
n@501
|
2174 var children = xml.getElementsByTagName('metricenable');
|
n@501
|
2175 for (var i in children) {
|
n@501
|
2176 if (isNaN(Number(i)) == true){break;}
|
n@501
|
2177 this.enabled.push(children[i].textContent);
|
n@501
|
2178 }
|
n@501
|
2179 }
|
n@503
|
2180 this.encode = function(doc) {
|
n@503
|
2181 var node = doc.createElement('metric');
|
n@501
|
2182 for (var i in this.enabled)
|
n@501
|
2183 {
|
n@501
|
2184 if (isNaN(Number(i)) == true){break;}
|
n@503
|
2185 var child = doc.createElement('metricenable');
|
n@501
|
2186 child.textContent = this.enabled[i];
|
n@501
|
2187 node.appendChild(child);
|
n@501
|
2188 }
|
n@501
|
2189 return node;
|
n@501
|
2190 }
|
n@501
|
2191 }
|
n@501
|
2192
|
n@453
|
2193 this.page = function() {
|
n@374
|
2194 this.presentedId = undefined;
|
n@374
|
2195 this.id = undefined;
|
n@374
|
2196 this.hostURL = undefined;
|
n@374
|
2197 this.randomiseOrder = undefined;
|
n@374
|
2198 this.loop = undefined;
|
n@453
|
2199 this.showElementComments = undefined;
|
n@374
|
2200 this.outsideReference = null;
|
n@410
|
2201 this.loudness = null;
|
n@603
|
2202 this.label = null;
|
n@453
|
2203 this.preTest = null;
|
n@453
|
2204 this.postTest = null;
|
n@374
|
2205 this.interfaces = [];
|
n@374
|
2206 this.commentBoxPrefix = "Comment on track";
|
n@374
|
2207 this.audioElements = [];
|
n@374
|
2208 this.commentQuestions = [];
|
n@501
|
2209 this.schema = specification.schema.getAllElementsByName("page")[0];
|
n@623
|
2210 this.parent = null;
|
n@374
|
2211
|
n@501
|
2212 this.decode = function(parent,xml)
|
n@374
|
2213 {
|
n@623
|
2214 this.parent = parent;
|
n@477
|
2215 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2216 for (var i=0; i<attributeMap.length; i++)
|
n@410
|
2217 {
|
n@453
|
2218 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2219 var projectAttr = xml.getAttribute(attributeName);
|
n@623
|
2220 projectAttr = parent.processAttribute(projectAttr,attributeMap[i],parent.schema);
|
n@453
|
2221 switch(typeof projectAttr)
|
nicholas@417
|
2222 {
|
n@453
|
2223 case "number":
|
n@453
|
2224 case "boolean":
|
n@453
|
2225 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2226 break;
|
n@453
|
2227 case "string":
|
n@453
|
2228 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2229 break;
|
n@374
|
2230 }
|
n@374
|
2231 }
|
n@374
|
2232
|
n@453
|
2233 // Get the Comment Box Prefix
|
n@453
|
2234 var CBP = xml.getElementsByTagName('commentboxprefix');
|
n@453
|
2235 if (CBP.length != 0) {
|
n@453
|
2236 this.commentBoxPrefix = CBP[0].textContent;
|
n@427
|
2237 }
|
n@427
|
2238
|
n@453
|
2239 // Now decode the interfaces
|
n@453
|
2240 var interfaceNode = xml.getElementsByTagName('interface');
|
n@453
|
2241 for (var i=0; i<interfaceNode.length; i++)
|
n@453
|
2242 {
|
n@453
|
2243 var node = new parent.interfaceNode();
|
n@477
|
2244 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
|
n@453
|
2245 this.interfaces.push(node);
|
n@453
|
2246 }
|
n@380
|
2247
|
n@453
|
2248 // Now process the survey node options
|
n@453
|
2249 var survey = xml.getElementsByTagName('survey');
|
n@477
|
2250 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
|
n@453
|
2251 for (var i in survey) {
|
n@453
|
2252 if (isNaN(Number(i)) == true){break;}
|
n@453
|
2253 var location = survey[i].getAttribute('location');
|
n@453
|
2254 if (location == 'pre' || location == 'before')
|
n@453
|
2255 {
|
n@453
|
2256 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
|
n@453
|
2257 else {
|
n@453
|
2258 this.preTest = new parent.surveyNode();
|
n@453
|
2259 this.preTest.decode(parent,survey[i],surveySchema);
|
n@453
|
2260 }
|
n@453
|
2261 } else if (location == 'post' || location == 'after') {
|
n@453
|
2262 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
|
n@453
|
2263 else {
|
n@453
|
2264 this.postTest = new parent.surveyNode();
|
n@453
|
2265 this.postTest.decode(parent,survey[i],surveySchema);
|
n@453
|
2266 }
|
n@453
|
2267 }
|
n@453
|
2268 }
|
n@453
|
2269
|
n@453
|
2270 // Now process the audioelement tags
|
n@453
|
2271 var audioElements = xml.getElementsByTagName('audioelement');
|
n@453
|
2272 for (var i=0; i<audioElements.length; i++)
|
n@453
|
2273 {
|
n@453
|
2274 var node = new this.audioElementNode();
|
n@501
|
2275 node.decode(this,audioElements[i]);
|
n@453
|
2276 this.audioElements.push(node);
|
n@453
|
2277 }
|
n@453
|
2278
|
n@453
|
2279 // Now decode the commentquestions
|
n@453
|
2280 var commentQuestions = xml.getElementsByTagName('commentquestion');
|
n@453
|
2281 for (var i=0; i<commentQuestions.length; i++)
|
n@453
|
2282 {
|
n@374
|
2283 var node = new this.commentQuestionNode();
|
n@501
|
2284 node.decode(parent,commentQuestions[i]);
|
n@374
|
2285 this.commentQuestions.push(node);
|
n@180
|
2286 }
|
n@180
|
2287 };
|
n@180
|
2288
|
n@374
|
2289 this.encode = function(root)
|
n@374
|
2290 {
|
n@503
|
2291 var AHNode = root.createElement("page");
|
n@503
|
2292 // First decode the attributes
|
n@503
|
2293 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
|
n@503
|
2294 for (var i=0; i<attributes.length; i++)
|
n@503
|
2295 {
|
n@503
|
2296 var name = attributes[i].getAttribute("name");
|
n@503
|
2297 if (name == undefined) {
|
n@503
|
2298 name = attributes[i].getAttribute("ref");
|
n@503
|
2299 }
|
n@503
|
2300 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
2301 {
|
n@503
|
2302 eval("AHNode.setAttribute('"+name+"',this."+name+")");
|
n@503
|
2303 }
|
n@503
|
2304 }
|
n@410
|
2305 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
|
n@503
|
2306 // <commentboxprefix>
|
n@503
|
2307 var commentboxprefix = root.createElement("commentboxprefix");
|
n@503
|
2308 commentboxprefix.textContent = this.commentBoxPrefix;
|
n@503
|
2309 AHNode.appendChild(commentboxprefix);
|
n@503
|
2310
|
n@374
|
2311 for (var i=0; i<this.interfaces.length; i++)
|
n@324
|
2312 {
|
n@374
|
2313 AHNode.appendChild(this.interfaces[i].encode(root));
|
n@374
|
2314 }
|
n@374
|
2315
|
n@374
|
2316 for (var i=0; i<this.audioElements.length; i++) {
|
n@374
|
2317 AHNode.appendChild(this.audioElements[i].encode(root));
|
n@374
|
2318 }
|
n@374
|
2319 // Create <CommentQuestion>
|
n@374
|
2320 for (var i=0; i<this.commentQuestions.length; i++)
|
n@374
|
2321 {
|
n@503
|
2322 AHNode.appendChild(this.commentQuestions[i].encode(root));
|
n@374
|
2323 }
|
n@374
|
2324
|
n@503
|
2325 AHNode.appendChild(this.preTest.encode(root));
|
n@503
|
2326 AHNode.appendChild(this.postTest.encode(root));
|
n@374
|
2327 return AHNode;
|
n@374
|
2328 };
|
n@374
|
2329
|
n@453
|
2330 this.commentQuestionNode = function() {
|
n@453
|
2331 this.id = null;
|
n@597
|
2332 this.name = undefined;
|
n@453
|
2333 this.type = undefined;
|
n@374
|
2334 this.options = [];
|
n@453
|
2335 this.statement = undefined;
|
n@501
|
2336 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
|
n@501
|
2337 this.decode = function(parent,xml)
|
n@374
|
2338 {
|
n@453
|
2339 this.id = xml.id;
|
n@597
|
2340 this.name = xml.getAttribute('name');
|
n@453
|
2341 this.type = xml.getAttribute('type');
|
n@453
|
2342 this.statement = xml.getElementsByTagName('statement')[0].textContent;
|
n@453
|
2343 var optNodes = xml.getElementsByTagName('option');
|
n@453
|
2344 for (var i=0; i<optNodes.length; i++)
|
n@453
|
2345 {
|
n@453
|
2346 var optNode = optNodes[i];
|
n@453
|
2347 this.options.push({
|
n@453
|
2348 name: optNode.getAttribute('name'),
|
n@453
|
2349 text: optNode.textContent
|
n@453
|
2350 });
|
n@374
|
2351 }
|
n@374
|
2352 };
|
n@453
|
2353
|
n@374
|
2354 this.encode = function(root)
|
n@374
|
2355 {
|
n@503
|
2356 var node = root.createElement("commentquestion");
|
n@503
|
2357 node.id = this.id;
|
n@503
|
2358 node.setAttribute("type",this.type);
|
n@597
|
2359 if (this.name != undefined){node.setAttribute("name",this.name);}
|
n@503
|
2360 var statement = root.createElement("statement");
|
n@503
|
2361 statement.textContent = this.statement;
|
n@503
|
2362 node.appendChild(statement);
|
n@503
|
2363 for (var option of this.options)
|
n@503
|
2364 {
|
n@503
|
2365 var child = root.createElement("option");
|
n@503
|
2366 child.setAttribute("name",option.name);
|
n@503
|
2367 child.textContent = option.text;
|
n@503
|
2368 node.appendChild(child);
|
n@503
|
2369 }
|
n@503
|
2370 return node;
|
n@374
|
2371 };
|
n@374
|
2372 };
|
n@374
|
2373
|
n@374
|
2374 this.audioElementNode = function() {
|
n@374
|
2375 this.url = null;
|
n@374
|
2376 this.id = null;
|
n@597
|
2377 this.name = null;
|
n@374
|
2378 this.parent = null;
|
n@453
|
2379 this.type = null;
|
n@525
|
2380 this.marker = null;
|
n@374
|
2381 this.enforce = false;
|
n@564
|
2382 this.gain = 0.0;
|
n@501
|
2383 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
|
n@453
|
2384 this.parent = null;
|
n@501
|
2385 this.decode = function(parent,xml)
|
n@374
|
2386 {
|
n@374
|
2387 this.parent = parent;
|
n@477
|
2388 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2389 for (var i=0; i<attributeMap.length; i++)
|
n@400
|
2390 {
|
n@453
|
2391 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2392 var projectAttr = xml.getAttribute(attributeName);
|
n@623
|
2393 projectAttr = parent.parent.processAttribute(projectAttr,attributeMap[i],parent.parent.schema);
|
n@453
|
2394 switch(typeof projectAttr)
|
n@374
|
2395 {
|
n@453
|
2396 case "number":
|
n@453
|
2397 case "boolean":
|
n@453
|
2398 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2399 break;
|
n@453
|
2400 case "string":
|
n@453
|
2401 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2402 break;
|
n@324
|
2403 }
|
n@324
|
2404 }
|
n@453
|
2405
|
n@374
|
2406 };
|
n@374
|
2407 this.encode = function(root)
|
n@374
|
2408 {
|
n@503
|
2409 var AENode = root.createElement("audioelement");
|
n@503
|
2410 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
|
n@503
|
2411 for (var i=0; i<attributes.length; i++)
|
n@503
|
2412 {
|
n@503
|
2413 var name = attributes[i].getAttribute("name");
|
n@503
|
2414 if (name == undefined) {
|
n@503
|
2415 name = attributes[i].getAttribute("ref");
|
n@503
|
2416 }
|
n@503
|
2417 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
2418 {
|
n@503
|
2419 eval("AENode.setAttribute('"+name+"',this."+name+")");
|
n@503
|
2420 }
|
n@503
|
2421 }
|
n@374
|
2422 return AENode;
|
n@374
|
2423 };
|
n@180
|
2424 };
|
n@180
|
2425 };
|
n@180
|
2426 }
|
n@374
|
2427
|
n@182
|
2428 function Interface(specificationObject) {
|
n@180
|
2429 // This handles the bindings between the interface and the audioEngineContext;
|
n@182
|
2430 this.specification = specificationObject;
|
n@182
|
2431 this.insertPoint = document.getElementById("topLevelBody");
|
n@180
|
2432
|
n@453
|
2433 this.newPage = function(audioHolderObject,store)
|
n@375
|
2434 {
|
n@500
|
2435 audioEngineContext.newTestPage(audioHolderObject,store);
|
n@550
|
2436 interfaceContext.commentBoxes.deleteCommentBoxes();
|
n@375
|
2437 interfaceContext.deleteCommentQuestions();
|
n@453
|
2438 loadTest(audioHolderObject,store);
|
n@375
|
2439 };
|
n@375
|
2440
|
n@182
|
2441 // Bounded by interface!!
|
n@182
|
2442 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
n@182
|
2443 // For example, APE returns the slider position normalised in a <value> tag.
|
n@182
|
2444 this.interfaceObjects = [];
|
n@182
|
2445 this.interfaceObject = function(){};
|
n@182
|
2446
|
n@302
|
2447 this.resizeWindow = function(event)
|
n@302
|
2448 {
|
n@395
|
2449 popup.resize(event);
|
n@302
|
2450 for(var i=0; i<this.commentBoxes.length; i++)
|
n@302
|
2451 {this.commentBoxes[i].resize();}
|
n@302
|
2452 for(var i=0; i<this.commentQuestions.length; i++)
|
n@302
|
2453 {this.commentQuestions[i].resize();}
|
n@302
|
2454 try
|
n@302
|
2455 {
|
n@302
|
2456 resizeWindow(event);
|
n@302
|
2457 }
|
n@302
|
2458 catch(err)
|
n@302
|
2459 {
|
n@302
|
2460 console.log("Warning - Interface does not have Resize option");
|
n@302
|
2461 console.log(err);
|
n@302
|
2462 }
|
n@302
|
2463 };
|
n@302
|
2464
|
n@356
|
2465 this.returnNavigator = function()
|
n@356
|
2466 {
|
n@491
|
2467 var node = storage.document.createElement("navigator");
|
n@491
|
2468 var platform = storage.document.createElement("platform");
|
n@356
|
2469 platform.textContent = navigator.platform;
|
n@491
|
2470 var vendor = storage.document.createElement("vendor");
|
n@356
|
2471 vendor.textContent = navigator.vendor;
|
n@491
|
2472 var userAgent = storage.document.createElement("uagent");
|
n@356
|
2473 userAgent.textContent = navigator.userAgent;
|
n@491
|
2474 var screen = storage.document.createElement("window");
|
n@491
|
2475 screen.setAttribute('innerWidth',window.innerWidth);
|
n@491
|
2476 screen.setAttribute('innerHeight',window.innerHeight);
|
n@356
|
2477 node.appendChild(platform);
|
n@356
|
2478 node.appendChild(vendor);
|
n@356
|
2479 node.appendChild(userAgent);
|
n@491
|
2480 node.appendChild(screen);
|
n@356
|
2481 return node;
|
n@356
|
2482 };
|
n@356
|
2483
|
n@550
|
2484 this.commentBoxes = new function() {
|
n@550
|
2485 this.boxes = [];
|
n@550
|
2486 this.injectPoint = null;
|
n@550
|
2487 this.elementCommentBox = function(audioObject) {
|
n@550
|
2488 var element = audioObject.specification;
|
n@550
|
2489 this.audioObject = audioObject;
|
n@550
|
2490 this.id = audioObject.id;
|
n@550
|
2491 var audioHolderObject = audioObject.specification.parent;
|
n@550
|
2492 // Create document objects to hold the comment boxes
|
n@550
|
2493 this.trackComment = document.createElement('div');
|
n@550
|
2494 this.trackComment.className = 'comment-div';
|
n@550
|
2495 this.trackComment.id = 'comment-div-'+audioObject.id;
|
n@550
|
2496 // Create a string next to each comment asking for a comment
|
n@550
|
2497 this.trackString = document.createElement('span');
|
n@550
|
2498 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
|
n@550
|
2499 // Create the HTML5 comment box 'textarea'
|
n@550
|
2500 this.trackCommentBox = document.createElement('textarea');
|
n@550
|
2501 this.trackCommentBox.rows = '4';
|
n@550
|
2502 this.trackCommentBox.cols = '100';
|
n@550
|
2503 this.trackCommentBox.name = 'trackComment'+audioObject.id;
|
n@550
|
2504 this.trackCommentBox.className = 'trackComment';
|
n@550
|
2505 var br = document.createElement('br');
|
n@550
|
2506 // Add to the holder.
|
n@550
|
2507 this.trackComment.appendChild(this.trackString);
|
n@550
|
2508 this.trackComment.appendChild(br);
|
n@550
|
2509 this.trackComment.appendChild(this.trackCommentBox);
|
n@550
|
2510
|
n@550
|
2511 this.exportXMLDOM = function() {
|
n@550
|
2512 var root = document.createElement('comment');
|
n@550
|
2513 var question = document.createElement('question');
|
n@550
|
2514 question.textContent = this.trackString.textContent;
|
n@550
|
2515 var response = document.createElement('response');
|
n@550
|
2516 response.textContent = this.trackCommentBox.value;
|
n@550
|
2517 console.log("Comment frag-"+this.id+": "+response.textContent);
|
n@550
|
2518 root.appendChild(question);
|
n@550
|
2519 root.appendChild(response);
|
n@550
|
2520 return root;
|
n@550
|
2521 };
|
n@550
|
2522 this.resize = function()
|
n@550
|
2523 {
|
n@550
|
2524 var boxwidth = (window.innerWidth-100)/2;
|
n@550
|
2525 if (boxwidth >= 600)
|
n@550
|
2526 {
|
n@550
|
2527 boxwidth = 600;
|
n@550
|
2528 }
|
n@550
|
2529 else if (boxwidth < 400)
|
n@550
|
2530 {
|
n@550
|
2531 boxwidth = 400;
|
n@550
|
2532 }
|
n@550
|
2533 this.trackComment.style.width = boxwidth+"px";
|
n@550
|
2534 this.trackCommentBox.style.width = boxwidth-6+"px";
|
n@550
|
2535 };
|
n@550
|
2536 this.resize();
|
n@550
|
2537 };
|
n@550
|
2538 this.createCommentBox = function(audioObject) {
|
n@550
|
2539 var node = new this.elementCommentBox(audioObject);
|
n@550
|
2540 this.boxes.push(node);
|
n@550
|
2541 audioObject.commentDOM = node;
|
n@550
|
2542 return node;
|
n@550
|
2543 };
|
n@550
|
2544 this.sortCommentBoxes = function() {
|
n@550
|
2545 this.boxes.sort(function(a,b){return a.id - b.id;});
|
n@550
|
2546 };
|
n@550
|
2547
|
n@550
|
2548 this.showCommentBoxes = function(inject, sort) {
|
n@550
|
2549 this.injectPoint = inject;
|
n@550
|
2550 if (sort) {this.sortCommentBoxes();}
|
n@550
|
2551 for (var box of this.boxes) {
|
n@550
|
2552 inject.appendChild(box.trackComment);
|
n@550
|
2553 }
|
n@550
|
2554 };
|
n@550
|
2555
|
n@550
|
2556 this.deleteCommentBoxes = function() {
|
n@550
|
2557 if (this.injectPoint != null) {
|
n@550
|
2558 for (var box of this.boxes) {
|
n@550
|
2559 this.injectPoint.removeChild(box.trackComment);
|
n@550
|
2560 }
|
n@550
|
2561 this.injectPoint = null;
|
n@550
|
2562 }
|
n@550
|
2563 this.boxes = [];
|
n@550
|
2564 };
|
n@550
|
2565 }
|
n@182
|
2566
|
n@193
|
2567 this.commentQuestions = [];
|
n@193
|
2568
|
n@193
|
2569 this.commentBox = function(commentQuestion) {
|
n@193
|
2570 this.specification = commentQuestion;
|
n@193
|
2571 // Create document objects to hold the comment boxes
|
n@193
|
2572 this.holder = document.createElement('div');
|
n@193
|
2573 this.holder.className = 'comment-div';
|
n@193
|
2574 // Create a string next to each comment asking for a comment
|
n@193
|
2575 this.string = document.createElement('span');
|
n@453
|
2576 this.string.innerHTML = commentQuestion.statement;
|
n@193
|
2577 // Create the HTML5 comment box 'textarea'
|
n@193
|
2578 this.textArea = document.createElement('textarea');
|
n@193
|
2579 this.textArea.rows = '4';
|
n@193
|
2580 this.textArea.cols = '100';
|
n@193
|
2581 this.textArea.className = 'trackComment';
|
n@193
|
2582 var br = document.createElement('br');
|
n@193
|
2583 // Add to the holder.
|
n@193
|
2584 this.holder.appendChild(this.string);
|
n@193
|
2585 this.holder.appendChild(br);
|
n@193
|
2586 this.holder.appendChild(this.textArea);
|
n@193
|
2587
|
n@520
|
2588 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2589 var root = storePoint.parent.document.createElement('comment');
|
n@193
|
2590 root.id = this.specification.id;
|
n@193
|
2591 root.setAttribute('type',this.specification.type);
|
b@254
|
2592 console.log("Question: "+this.string.textContent);
|
b@254
|
2593 console.log("Response: "+root.textContent);
|
n@520
|
2594 var question = storePoint.parent.document.createElement('question');
|
n@520
|
2595 question.textContent = this.string.textContent;
|
n@520
|
2596 var response = storePoint.parent.document.createElement('response');
|
n@520
|
2597 response.textContent = this.textArea.value;
|
n@520
|
2598 root.appendChild(question);
|
n@520
|
2599 root.appendChild(response);
|
n@520
|
2600 storePoint.XMLDOM.appendChild(root);
|
n@193
|
2601 return root;
|
n@193
|
2602 };
|
n@302
|
2603 this.resize = function()
|
n@302
|
2604 {
|
n@302
|
2605 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2606 if (boxwidth >= 600)
|
n@302
|
2607 {
|
n@302
|
2608 boxwidth = 600;
|
n@302
|
2609 }
|
n@302
|
2610 else if (boxwidth < 400)
|
n@302
|
2611 {
|
n@302
|
2612 boxwidth = 400;
|
n@302
|
2613 }
|
n@302
|
2614 this.holder.style.width = boxwidth+"px";
|
n@302
|
2615 this.textArea.style.width = boxwidth-6+"px";
|
n@302
|
2616 };
|
n@302
|
2617 this.resize();
|
n@193
|
2618 };
|
n@193
|
2619
|
n@193
|
2620 this.radioBox = function(commentQuestion) {
|
n@193
|
2621 this.specification = commentQuestion;
|
n@193
|
2622 // Create document objects to hold the comment boxes
|
n@193
|
2623 this.holder = document.createElement('div');
|
n@193
|
2624 this.holder.className = 'comment-div';
|
n@193
|
2625 // Create a string next to each comment asking for a comment
|
n@193
|
2626 this.string = document.createElement('span');
|
n@193
|
2627 this.string.innerHTML = commentQuestion.statement;
|
n@193
|
2628 var br = document.createElement('br');
|
n@193
|
2629 // Add to the holder.
|
n@193
|
2630 this.holder.appendChild(this.string);
|
n@193
|
2631 this.holder.appendChild(br);
|
n@193
|
2632 this.options = [];
|
n@193
|
2633 this.inputs = document.createElement('div');
|
n@193
|
2634 this.span = document.createElement('div');
|
n@193
|
2635 this.inputs.align = 'center';
|
n@193
|
2636 this.inputs.style.marginLeft = '12px';
|
n@193
|
2637 this.span.style.marginLeft = '12px';
|
n@193
|
2638 this.span.align = 'center';
|
n@193
|
2639 this.span.style.marginTop = '15px';
|
n@193
|
2640
|
n@193
|
2641 var optCount = commentQuestion.options.length;
|
n@453
|
2642 for (var optNode of commentQuestion.options)
|
n@193
|
2643 {
|
n@193
|
2644 var div = document.createElement('div');
|
n@301
|
2645 div.style.width = '80px';
|
n@193
|
2646 div.style.float = 'left';
|
n@193
|
2647 var input = document.createElement('input');
|
n@193
|
2648 input.type = 'radio';
|
n@193
|
2649 input.name = commentQuestion.id;
|
n@453
|
2650 input.setAttribute('setvalue',optNode.name);
|
n@193
|
2651 input.className = 'comment-radio';
|
n@193
|
2652 div.appendChild(input);
|
n@193
|
2653 this.inputs.appendChild(div);
|
n@193
|
2654
|
n@193
|
2655
|
n@193
|
2656 div = document.createElement('div');
|
n@301
|
2657 div.style.width = '80px';
|
n@193
|
2658 div.style.float = 'left';
|
n@193
|
2659 div.align = 'center';
|
n@193
|
2660 var span = document.createElement('span');
|
n@453
|
2661 span.textContent = optNode.text;
|
n@193
|
2662 span.className = 'comment-radio-span';
|
n@193
|
2663 div.appendChild(span);
|
n@193
|
2664 this.span.appendChild(div);
|
n@193
|
2665 this.options.push(input);
|
n@193
|
2666 }
|
n@193
|
2667 this.holder.appendChild(this.span);
|
n@193
|
2668 this.holder.appendChild(this.inputs);
|
n@193
|
2669
|
n@520
|
2670 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2671 var root = storePoint.parent.document.createElement('comment');
|
n@193
|
2672 root.id = this.specification.id;
|
n@193
|
2673 root.setAttribute('type',this.specification.type);
|
n@193
|
2674 var question = document.createElement('question');
|
n@193
|
2675 question.textContent = this.string.textContent;
|
n@193
|
2676 var response = document.createElement('response');
|
n@193
|
2677 var i=0;
|
n@193
|
2678 while(this.options[i].checked == false) {
|
n@193
|
2679 i++;
|
n@193
|
2680 if (i >= this.options.length) {
|
n@193
|
2681 break;
|
n@193
|
2682 }
|
n@193
|
2683 }
|
n@193
|
2684 if (i >= this.options.length) {
|
n@193
|
2685 response.textContent = 'null';
|
n@193
|
2686 } else {
|
n@193
|
2687 response.textContent = this.options[i].getAttribute('setvalue');
|
n@193
|
2688 response.setAttribute('number',i);
|
n@193
|
2689 }
|
n@195
|
2690 console.log('Comment: '+question.textContent);
|
n@195
|
2691 console.log('Response: '+response.textContent);
|
n@193
|
2692 root.appendChild(question);
|
n@193
|
2693 root.appendChild(response);
|
n@520
|
2694 storePoint.XMLDOM.appendChild(root);
|
n@193
|
2695 return root;
|
n@193
|
2696 };
|
n@302
|
2697 this.resize = function()
|
n@302
|
2698 {
|
n@302
|
2699 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2700 if (boxwidth >= 600)
|
n@302
|
2701 {
|
n@302
|
2702 boxwidth = 600;
|
n@302
|
2703 }
|
n@302
|
2704 else if (boxwidth < 400)
|
n@302
|
2705 {
|
n@302
|
2706 boxwidth = 400;
|
n@302
|
2707 }
|
n@302
|
2708 this.holder.style.width = boxwidth+"px";
|
n@302
|
2709 var text = this.holder.children[2];
|
n@302
|
2710 var options = this.holder.children[3];
|
n@302
|
2711 var optCount = options.children.length;
|
n@302
|
2712 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
n@302
|
2713 var options = options.firstChild;
|
n@302
|
2714 var text = text.firstChild;
|
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 while(options.nextSibling != undefined)
|
n@302
|
2720 {
|
n@302
|
2721 options = options.nextSibling;
|
n@302
|
2722 text = text.nextSibling;
|
n@302
|
2723 options.style.marginRight = spanMargin;
|
n@302
|
2724 options.style.marginLeft = spanMargin;
|
n@302
|
2725 text.style.marginRight = spanMargin;
|
n@302
|
2726 text.style.marginLeft = spanMargin;
|
n@302
|
2727 }
|
n@302
|
2728 };
|
n@302
|
2729 this.resize();
|
n@193
|
2730 };
|
n@193
|
2731
|
n@195
|
2732 this.checkboxBox = function(commentQuestion) {
|
n@195
|
2733 this.specification = commentQuestion;
|
n@195
|
2734 // Create document objects to hold the comment boxes
|
n@195
|
2735 this.holder = document.createElement('div');
|
n@195
|
2736 this.holder.className = 'comment-div';
|
n@195
|
2737 // Create a string next to each comment asking for a comment
|
n@195
|
2738 this.string = document.createElement('span');
|
n@195
|
2739 this.string.innerHTML = commentQuestion.statement;
|
n@195
|
2740 var br = document.createElement('br');
|
n@195
|
2741 // Add to the holder.
|
n@195
|
2742 this.holder.appendChild(this.string);
|
n@195
|
2743 this.holder.appendChild(br);
|
n@195
|
2744 this.options = [];
|
n@195
|
2745 this.inputs = document.createElement('div');
|
n@195
|
2746 this.span = document.createElement('div');
|
n@195
|
2747 this.inputs.align = 'center';
|
n@195
|
2748 this.inputs.style.marginLeft = '12px';
|
n@195
|
2749 this.span.style.marginLeft = '12px';
|
n@195
|
2750 this.span.align = 'center';
|
n@195
|
2751 this.span.style.marginTop = '15px';
|
n@195
|
2752
|
n@195
|
2753 var optCount = commentQuestion.options.length;
|
n@195
|
2754 for (var i=0; i<optCount; i++)
|
n@195
|
2755 {
|
n@195
|
2756 var div = document.createElement('div');
|
n@301
|
2757 div.style.width = '80px';
|
n@195
|
2758 div.style.float = 'left';
|
n@195
|
2759 var input = document.createElement('input');
|
n@195
|
2760 input.type = 'checkbox';
|
n@195
|
2761 input.name = commentQuestion.id;
|
n@195
|
2762 input.setAttribute('setvalue',commentQuestion.options[i].name);
|
n@195
|
2763 input.className = 'comment-radio';
|
n@195
|
2764 div.appendChild(input);
|
n@195
|
2765 this.inputs.appendChild(div);
|
n@195
|
2766
|
n@195
|
2767
|
n@195
|
2768 div = document.createElement('div');
|
n@301
|
2769 div.style.width = '80px';
|
n@195
|
2770 div.style.float = 'left';
|
n@195
|
2771 div.align = 'center';
|
n@195
|
2772 var span = document.createElement('span');
|
n@195
|
2773 span.textContent = commentQuestion.options[i].text;
|
n@195
|
2774 span.className = 'comment-radio-span';
|
n@195
|
2775 div.appendChild(span);
|
n@195
|
2776 this.span.appendChild(div);
|
n@195
|
2777 this.options.push(input);
|
n@195
|
2778 }
|
n@195
|
2779 this.holder.appendChild(this.span);
|
n@195
|
2780 this.holder.appendChild(this.inputs);
|
n@195
|
2781
|
n@520
|
2782 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2783 var root = storePoint.parent.document.createElement('comment');
|
n@195
|
2784 root.id = this.specification.id;
|
n@195
|
2785 root.setAttribute('type',this.specification.type);
|
n@195
|
2786 var question = document.createElement('question');
|
n@195
|
2787 question.textContent = this.string.textContent;
|
n@195
|
2788 root.appendChild(question);
|
n@195
|
2789 console.log('Comment: '+question.textContent);
|
n@195
|
2790 for (var i=0; i<this.options.length; i++) {
|
n@195
|
2791 var response = document.createElement('response');
|
n@195
|
2792 response.textContent = this.options[i].checked;
|
n@195
|
2793 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
|
n@195
|
2794 root.appendChild(response);
|
n@195
|
2795 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
|
n@195
|
2796 }
|
n@520
|
2797 storePoint.XMLDOM.appendChild(root);
|
n@195
|
2798 return root;
|
n@195
|
2799 };
|
n@302
|
2800 this.resize = function()
|
n@302
|
2801 {
|
n@302
|
2802 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2803 if (boxwidth >= 600)
|
n@302
|
2804 {
|
n@302
|
2805 boxwidth = 600;
|
n@302
|
2806 }
|
n@302
|
2807 else if (boxwidth < 400)
|
n@302
|
2808 {
|
n@302
|
2809 boxwidth = 400;
|
n@302
|
2810 }
|
n@302
|
2811 this.holder.style.width = boxwidth+"px";
|
n@302
|
2812 var text = this.holder.children[2];
|
n@302
|
2813 var options = this.holder.children[3];
|
n@302
|
2814 var optCount = options.children.length;
|
n@302
|
2815 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
n@302
|
2816 var options = options.firstChild;
|
n@302
|
2817 var text = text.firstChild;
|
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 while(options.nextSibling != undefined)
|
n@302
|
2823 {
|
n@302
|
2824 options = options.nextSibling;
|
n@302
|
2825 text = text.nextSibling;
|
n@302
|
2826 options.style.marginRight = spanMargin;
|
n@302
|
2827 options.style.marginLeft = spanMargin;
|
n@302
|
2828 text.style.marginRight = spanMargin;
|
n@302
|
2829 text.style.marginLeft = spanMargin;
|
n@302
|
2830 }
|
n@302
|
2831 };
|
n@302
|
2832 this.resize();
|
n@195
|
2833 };
|
nicholas@211
|
2834
|
n@193
|
2835 this.createCommentQuestion = function(element) {
|
n@193
|
2836 var node;
|
n@453
|
2837 if (element.type == 'question') {
|
n@193
|
2838 node = new this.commentBox(element);
|
n@193
|
2839 } else if (element.type == 'radio') {
|
n@193
|
2840 node = new this.radioBox(element);
|
n@195
|
2841 } else if (element.type == 'checkbox') {
|
n@195
|
2842 node = new this.checkboxBox(element);
|
n@193
|
2843 }
|
n@193
|
2844 this.commentQuestions.push(node);
|
n@193
|
2845 return node;
|
n@193
|
2846 };
|
n@201
|
2847
|
nicholas@237
|
2848 this.deleteCommentQuestions = function()
|
nicholas@237
|
2849 {
|
nicholas@237
|
2850 this.commentQuestions = [];
|
nicholas@237
|
2851 };
|
nicholas@237
|
2852
|
n@201
|
2853 this.playhead = new function()
|
n@201
|
2854 {
|
n@201
|
2855 this.object = document.createElement('div');
|
n@201
|
2856 this.object.className = 'playhead';
|
n@201
|
2857 this.object.align = 'left';
|
n@201
|
2858 var curTime = document.createElement('div');
|
n@201
|
2859 curTime.style.width = '50px';
|
n@201
|
2860 this.curTimeSpan = document.createElement('span');
|
n@201
|
2861 this.curTimeSpan.textContent = '00:00';
|
n@201
|
2862 curTime.appendChild(this.curTimeSpan);
|
n@201
|
2863 this.object.appendChild(curTime);
|
n@201
|
2864 this.scrubberTrack = document.createElement('div');
|
n@201
|
2865 this.scrubberTrack.className = 'playhead-scrub-track';
|
n@201
|
2866
|
n@201
|
2867 this.scrubberHead = document.createElement('div');
|
n@201
|
2868 this.scrubberHead.id = 'playhead-scrubber';
|
n@201
|
2869 this.scrubberTrack.appendChild(this.scrubberHead);
|
n@201
|
2870 this.object.appendChild(this.scrubberTrack);
|
n@201
|
2871
|
n@201
|
2872 this.timePerPixel = 0;
|
n@201
|
2873 this.maxTime = 0;
|
n@201
|
2874
|
n@204
|
2875 this.playbackObject;
|
n@204
|
2876
|
n@204
|
2877 this.setTimePerPixel = function(audioObject) {
|
n@201
|
2878 //maxTime must be in seconds
|
n@204
|
2879 this.playbackObject = audioObject;
|
n@379
|
2880 this.maxTime = audioObject.buffer.buffer.duration;
|
n@201
|
2881 var width = 490; //500 - 10, 5 each side of the tracker head
|
n@204
|
2882 this.timePerPixel = this.maxTime/490;
|
n@204
|
2883 if (this.maxTime < 60) {
|
n@201
|
2884 this.curTimeSpan.textContent = '0.00';
|
n@201
|
2885 } else {
|
n@201
|
2886 this.curTimeSpan.textContent = '00:00';
|
n@201
|
2887 }
|
n@201
|
2888 };
|
n@201
|
2889
|
n@204
|
2890 this.update = function() {
|
n@201
|
2891 // Update the playhead position, startPlay must be called
|
n@201
|
2892 if (this.timePerPixel > 0) {
|
n@204
|
2893 var time = this.playbackObject.getCurrentPosition();
|
n@498
|
2894 if (time > 0 && time < this.maxTime) {
|
nicholas@267
|
2895 var width = 490;
|
nicholas@267
|
2896 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@267
|
2897 this.scrubberHead.style.left = pix+'px';
|
nicholas@267
|
2898 if (this.maxTime > 60.0) {
|
nicholas@267
|
2899 var secs = time%60;
|
nicholas@267
|
2900 var mins = Math.floor((time-secs)/60);
|
nicholas@267
|
2901 secs = secs.toString();
|
nicholas@267
|
2902 secs = secs.substr(0,2);
|
nicholas@267
|
2903 mins = mins.toString();
|
nicholas@267
|
2904 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@267
|
2905 } else {
|
nicholas@267
|
2906 time = time.toString();
|
nicholas@267
|
2907 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@267
|
2908 }
|
n@201
|
2909 } else {
|
nicholas@267
|
2910 this.scrubberHead.style.left = '0px';
|
nicholas@267
|
2911 if (this.maxTime < 60) {
|
nicholas@267
|
2912 this.curTimeSpan.textContent = '0.00';
|
nicholas@267
|
2913 } else {
|
nicholas@267
|
2914 this.curTimeSpan.textContent = '00:00';
|
nicholas@267
|
2915 }
|
n@201
|
2916 }
|
n@201
|
2917 }
|
n@201
|
2918 };
|
n@204
|
2919
|
n@204
|
2920 this.interval = undefined;
|
n@204
|
2921
|
n@204
|
2922 this.start = function() {
|
n@204
|
2923 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@267
|
2924 if (this.maxTime < 60) {
|
nicholas@267
|
2925 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@267
|
2926 } else {
|
nicholas@267
|
2927 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@267
|
2928 }
|
n@204
|
2929 }
|
n@204
|
2930 };
|
n@204
|
2931 this.stop = function() {
|
n@204
|
2932 clearInterval(this.interval);
|
n@204
|
2933 this.interval = undefined;
|
n@527
|
2934 this.scrubberHead.style.left = '0px';
|
nicholas@267
|
2935 if (this.maxTime < 60) {
|
nicholas@267
|
2936 this.curTimeSpan.textContent = '0.00';
|
nicholas@267
|
2937 } else {
|
nicholas@267
|
2938 this.curTimeSpan.textContent = '00:00';
|
nicholas@267
|
2939 }
|
n@204
|
2940 };
|
n@201
|
2941 };
|
n@483
|
2942
|
n@483
|
2943 this.volume = new function()
|
n@483
|
2944 {
|
n@483
|
2945 // An in-built volume module which can be viewed on page
|
n@483
|
2946 // Includes trackers on page-by-page data
|
n@483
|
2947 // Volume does NOT reset to 0dB on each page load
|
n@483
|
2948 this.valueLin = 1.0;
|
n@483
|
2949 this.valueDB = 0.0;
|
n@483
|
2950 this.object = document.createElement('div');
|
n@483
|
2951 this.object.id = 'master-volume-holder';
|
n@483
|
2952 this.slider = document.createElement('input');
|
n@483
|
2953 this.slider.id = 'master-volume-control';
|
n@483
|
2954 this.slider.type = 'range';
|
n@483
|
2955 this.valueText = document.createElement('span');
|
n@483
|
2956 this.valueText.id = 'master-volume-feedback';
|
n@483
|
2957 this.valueText.textContent = '0dB';
|
n@483
|
2958
|
n@483
|
2959 this.slider.min = -60;
|
n@483
|
2960 this.slider.max = 12;
|
n@483
|
2961 this.slider.value = 0;
|
n@483
|
2962 this.slider.step = 1;
|
n@483
|
2963 this.slider.onmousemove = function(event)
|
n@483
|
2964 {
|
n@483
|
2965 interfaceContext.volume.valueDB = event.currentTarget.value;
|
n@483
|
2966 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
n@483
|
2967 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
n@483
|
2968 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
n@483
|
2969 }
|
n@483
|
2970 this.slider.onmouseup = function(event)
|
n@483
|
2971 {
|
n@526
|
2972 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
n@483
|
2973 if (storePoint.length == 0)
|
n@483
|
2974 {
|
n@483
|
2975 storePoint = storage.document.createElement('metricresult');
|
n@483
|
2976 storePoint.setAttribute('name','volumeTracker');
|
n@526
|
2977 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
n@483
|
2978 }
|
n@483
|
2979 else {
|
n@483
|
2980 storePoint = storePoint[0];
|
n@483
|
2981 }
|
n@483
|
2982 var node = storage.document.createElement('movement');
|
n@483
|
2983 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
n@483
|
2984 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
n@483
|
2985 node.setAttribute('format','dBFS');
|
n@483
|
2986 storePoint.appendChild(node);
|
n@483
|
2987 }
|
n@483
|
2988
|
n@484
|
2989 var title = document.createElement('div');
|
n@484
|
2990 title.innerHTML = '<span>Master Volume Control</span>';
|
n@484
|
2991 title.style.fontSize = '0.75em';
|
n@484
|
2992 title.style.width = "100%";
|
n@484
|
2993 title.align = 'center';
|
n@484
|
2994 this.object.appendChild(title);
|
n@484
|
2995
|
n@483
|
2996 this.object.appendChild(this.slider);
|
n@483
|
2997 this.object.appendChild(this.valueText);
|
n@483
|
2998 }
|
nicholas@235
|
2999 // Global Checkers
|
nicholas@235
|
3000 // These functions will help enforce the checkers
|
nicholas@235
|
3001 this.checkHiddenAnchor = function()
|
nicholas@235
|
3002 {
|
n@453
|
3003 for (var ao of audioEngineContext.audioObjects)
|
nicholas@235
|
3004 {
|
n@453
|
3005 if (ao.specification.type == "anchor")
|
nicholas@235
|
3006 {
|
n@454
|
3007 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
n@453
|
3008 // Anchor is not set below
|
n@453
|
3009 console.log('Anchor node not below marker value');
|
n@453
|
3010 alert('Please keep listening');
|
n@498
|
3011 this.storeErrorNode('Anchor node not below marker value');
|
n@453
|
3012 return false;
|
n@453
|
3013 }
|
nicholas@235
|
3014 }
|
nicholas@235
|
3015 }
|
nicholas@235
|
3016 return true;
|
nicholas@235
|
3017 };
|
nicholas@235
|
3018
|
nicholas@235
|
3019 this.checkHiddenReference = function()
|
nicholas@235
|
3020 {
|
n@453
|
3021 for (var ao of audioEngineContext.audioObjects)
|
nicholas@235
|
3022 {
|
n@453
|
3023 if (ao.specification.type == "reference")
|
nicholas@235
|
3024 {
|
n@454
|
3025 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
n@453
|
3026 // Anchor is not set below
|
n@498
|
3027 console.log('Reference node not above marker value');
|
n@498
|
3028 this.storeErrorNode('Reference node not above marker value');
|
n@453
|
3029 alert('Please keep listening');
|
n@453
|
3030 return false;
|
n@453
|
3031 }
|
nicholas@235
|
3032 }
|
nicholas@235
|
3033 }
|
nicholas@235
|
3034 return true;
|
nicholas@235
|
3035 };
|
n@366
|
3036
|
n@366
|
3037 this.checkFragmentsFullyPlayed = function ()
|
n@366
|
3038 {
|
n@366
|
3039 // Checks the entire file has been played back
|
n@366
|
3040 // NOTE ! This will return true IF playback is Looped!!!
|
n@366
|
3041 if (audioEngineContext.loopPlayback)
|
n@366
|
3042 {
|
n@366
|
3043 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
n@366
|
3044 return true;
|
n@366
|
3045 }
|
n@366
|
3046 var check_pass = true;
|
n@366
|
3047 var error_obj = [];
|
n@366
|
3048 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
n@366
|
3049 {
|
n@366
|
3050 var object = audioEngineContext.audioObjects[i];
|
nicholas@415
|
3051 var time = object.buffer.buffer.duration;
|
n@366
|
3052 var metric = object.metric;
|
n@366
|
3053 var passed = false;
|
n@366
|
3054 for (var j=0; j<metric.listenTracker.length; j++)
|
n@366
|
3055 {
|
n@366
|
3056 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
n@366
|
3057 var start_time = Number(bt[0].getAttribute('start'));
|
n@366
|
3058 var stop_time = Number(bt[0].getAttribute('stop'));
|
n@366
|
3059 var delta = stop_time - start_time;
|
n@366
|
3060 if (delta >= time)
|
n@366
|
3061 {
|
n@366
|
3062 passed = true;
|
n@366
|
3063 break;
|
n@366
|
3064 }
|
n@366
|
3065 }
|
n@366
|
3066 if (passed == false)
|
n@366
|
3067 {
|
n@366
|
3068 check_pass = false;
|
n@598
|
3069 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
|
n@598
|
3070 error_obj.push(object.interfaceDOM.getPresentedId());
|
n@366
|
3071 }
|
n@366
|
3072 }
|
n@366
|
3073 if (check_pass == false)
|
n@366
|
3074 {
|
nicholas@415
|
3075 var str_start = "You have not completely listened to fragments ";
|
n@366
|
3076 for (var i=0; i<error_obj.length; i++)
|
n@366
|
3077 {
|
n@366
|
3078 str_start += error_obj[i];
|
n@366
|
3079 if (i != error_obj.length-1)
|
n@366
|
3080 {
|
n@366
|
3081 str_start += ', ';
|
n@366
|
3082 }
|
n@366
|
3083 }
|
n@366
|
3084 str_start += ". Please keep listening";
|
n@366
|
3085 console.log("[ALERT]: "+str_start);
|
n@498
|
3086 this.storeErrorNode("[ALERT]: "+str_start);
|
n@366
|
3087 alert(str_start);
|
n@366
|
3088 }
|
n@366
|
3089 };
|
nicholas@421
|
3090 this.checkAllMoved = function()
|
nicholas@421
|
3091 {
|
nicholas@421
|
3092 var str = "You have not moved ";
|
nicholas@421
|
3093 var failed = [];
|
n@469
|
3094 for (var ao of audioEngineContext.audioObjects)
|
nicholas@421
|
3095 {
|
n@469
|
3096 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@421
|
3097 {
|
n@469
|
3098 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@421
|
3099 }
|
nicholas@421
|
3100 }
|
nicholas@421
|
3101 if (failed.length == 0)
|
nicholas@421
|
3102 {
|
nicholas@421
|
3103 return true;
|
nicholas@421
|
3104 } else if (failed.length == 1)
|
nicholas@421
|
3105 {
|
nicholas@421
|
3106 str += 'track '+failed[0];
|
nicholas@421
|
3107 } else {
|
nicholas@421
|
3108 str += 'tracks ';
|
nicholas@421
|
3109 for (var i=0; i<failed.length-1; i++)
|
nicholas@421
|
3110 {
|
nicholas@421
|
3111 str += failed[i]+', ';
|
nicholas@421
|
3112 }
|
nicholas@421
|
3113 str += 'and '+failed[i];
|
nicholas@421
|
3114 }
|
nicholas@421
|
3115 str +='.';
|
nicholas@421
|
3116 alert(str);
|
nicholas@421
|
3117 console.log(str);
|
n@498
|
3118 this.storeErrorNode(str);
|
nicholas@421
|
3119 return false;
|
nicholas@421
|
3120 };
|
nicholas@421
|
3121 this.checkAllPlayed = function()
|
nicholas@421
|
3122 {
|
nicholas@421
|
3123 var str = "You have not played ";
|
nicholas@421
|
3124 var failed = [];
|
n@469
|
3125 for (var ao of audioEngineContext.audioObjects)
|
nicholas@421
|
3126 {
|
n@469
|
3127 if(ao.metric.wasListenedTo == false)
|
nicholas@421
|
3128 {
|
n@469
|
3129 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@421
|
3130 }
|
nicholas@421
|
3131 }
|
nicholas@421
|
3132 if (failed.length == 0)
|
nicholas@421
|
3133 {
|
nicholas@421
|
3134 return true;
|
nicholas@421
|
3135 } else if (failed.length == 1)
|
nicholas@421
|
3136 {
|
nicholas@421
|
3137 str += 'track '+failed[0];
|
nicholas@421
|
3138 } else {
|
nicholas@421
|
3139 str += 'tracks ';
|
nicholas@421
|
3140 for (var i=0; i<failed.length-1; i++)
|
nicholas@421
|
3141 {
|
nicholas@421
|
3142 str += failed[i]+', ';
|
nicholas@421
|
3143 }
|
nicholas@421
|
3144 str += 'and '+failed[i];
|
nicholas@421
|
3145 }
|
nicholas@421
|
3146 str +='.';
|
nicholas@421
|
3147 alert(str);
|
nicholas@421
|
3148 console.log(str);
|
n@498
|
3149 this.storeErrorNode(str);
|
nicholas@421
|
3150 return false;
|
nicholas@421
|
3151 };
|
n@498
|
3152
|
n@498
|
3153 this.storeErrorNode = function(errorMessage)
|
n@498
|
3154 {
|
n@498
|
3155 var time = audioEngineContext.timer.getTestTime();
|
n@498
|
3156 var node = storage.document.createElement('error');
|
n@498
|
3157 node.setAttribute('time',time);
|
n@498
|
3158 node.textContent = errorMessage;
|
n@498
|
3159 testState.currentStore.XMLDOM.appendChild(node);
|
n@498
|
3160 };
|
n@453
|
3161 }
|
n@453
|
3162
|
n@453
|
3163 function Storage()
|
n@453
|
3164 {
|
n@453
|
3165 // Holds results in XML format until ready for collection
|
n@453
|
3166 this.globalPreTest = null;
|
n@453
|
3167 this.globalPostTest = null;
|
n@453
|
3168 this.testPages = [];
|
n@602
|
3169 this.document = null;
|
n@602
|
3170 this.root = null;
|
n@453
|
3171 this.state = 0;
|
n@453
|
3172
|
n@602
|
3173 this.initialise = function(existingStore)
|
n@453
|
3174 {
|
n@602
|
3175 if (existingStore == undefined) {
|
n@584
|
3176 // We need to get the sessionKey
|
n@584
|
3177 this.SessionKey.generateKey();
|
n@602
|
3178 this.document = document.implementation.createDocument(null,"waetresult");
|
n@602
|
3179 this.root = this.document.childNodes[0];
|
n@589
|
3180 var projectDocument = specification.projectXML;
|
n@589
|
3181 projectDocument.setAttribute('file-name',url);
|
n@589
|
3182 this.root.appendChild(projectDocument);
|
n@589
|
3183 this.root.appendChild(returnDateNode());
|
n@589
|
3184 this.root.appendChild(interfaceContext.returnNavigator());
|
n@584
|
3185 } else {
|
n@602
|
3186 this.document = existingStore;
|
n@602
|
3187 this.root = existingStore.children[0];
|
n@602
|
3188 this.SessionKey.key = this.root.getAttribute("key");
|
n@584
|
3189 }
|
n@589
|
3190 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
n@602
|
3191 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
n@453
|
3192 };
|
n@584
|
3193
|
n@584
|
3194 this.SessionKey = {
|
n@584
|
3195 key: null,
|
n@584
|
3196 request: new XMLHttpRequest(),
|
n@584
|
3197 parent: this,
|
n@584
|
3198 handleEvent: function() {
|
n@584
|
3199 var parse = new DOMParser();
|
n@584
|
3200 var xml = parse.parseFromString(this.request.response,"text/xml");
|
n@584
|
3201 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
|
n@584
|
3202 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
n@589
|
3203 this.parent.root.setAttribute("key",this.key);
|
n@589
|
3204 this.parent.root.setAttribute("state","empty");
|
n@584
|
3205 } else {
|
n@584
|
3206 this.generateKey();
|
n@584
|
3207 }
|
n@584
|
3208 },
|
n@584
|
3209 generateKey: function() {
|
n@584
|
3210 var temp_key = randomString(32);
|
n@584
|
3211 this.request.open("GET","keygen.php?key="+temp_key,true);
|
n@584
|
3212 this.request.addEventListener("load",this);
|
n@584
|
3213 this.request.send();
|
n@587
|
3214 },
|
n@589
|
3215 update: function() {
|
n@589
|
3216 this.parent.root.setAttribute("state","update");
|
n@589
|
3217 var xmlhttp = new XMLHttpRequest();
|
n@589
|
3218 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
|
n@589
|
3219 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
n@589
|
3220 xmlhttp.onerror = function(){
|
n@589
|
3221 console.log('Error updating file to server!');
|
n@589
|
3222 };
|
n@589
|
3223 var hold = document.createElement("div");
|
n@589
|
3224 var clone = this.parent.root.cloneNode(true);
|
n@589
|
3225 hold.appendChild(clone);
|
n@589
|
3226 xmlhttp.onload = function() {
|
n@589
|
3227 if (this.status >= 300) {
|
n@589
|
3228 console.log("WARNING - Could not update at this time");
|
n@589
|
3229 } else {
|
n@589
|
3230 var parser = new DOMParser();
|
n@589
|
3231 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
n@589
|
3232 var response = xmlDoc.getElementsByTagName('response')[0];
|
n@589
|
3233 if (response.getAttribute("state") == "OK") {
|
n@589
|
3234 var file = response.getElementsByTagName("file")[0];
|
n@589
|
3235 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
|
n@589
|
3236 } else {
|
n@589
|
3237 var message = response.getElementsByTagName("message");
|
n@589
|
3238 console.log("Intermediate save: Error! "+message.textContent);
|
n@589
|
3239 }
|
n@587
|
3240 }
|
n@587
|
3241 }
|
n@589
|
3242 xmlhttp.send([hold.innerHTML]);
|
n@584
|
3243 }
|
n@584
|
3244 }
|
n@453
|
3245
|
n@453
|
3246 this.createTestPageStore = function(specification)
|
n@453
|
3247 {
|
n@453
|
3248 var store = new this.pageNode(this,specification);
|
n@453
|
3249 this.testPages.push(store);
|
n@453
|
3250 return this.testPages[this.testPages.length-1];
|
n@453
|
3251 };
|
n@453
|
3252
|
n@453
|
3253 this.surveyNode = function(parent,root,specification)
|
n@453
|
3254 {
|
n@453
|
3255 this.specification = specification;
|
n@453
|
3256 this.parent = parent;
|
n@602
|
3257 this.state = "empty";
|
n@453
|
3258 this.XMLDOM = this.parent.document.createElement('survey');
|
n@453
|
3259 this.XMLDOM.setAttribute('location',this.specification.location);
|
n@602
|
3260 this.XMLDOM.setAttribute("state",this.state);
|
n@453
|
3261 for (var optNode of this.specification.options)
|
n@453
|
3262 {
|
n@453
|
3263 if (optNode.type != 'statement')
|
n@453
|
3264 {
|
n@453
|
3265 var node = this.parent.document.createElement('surveyresult');
|
n@602
|
3266 node.setAttribute("ref",optNode.id);
|
n@453
|
3267 node.setAttribute('type',optNode.type);
|
n@453
|
3268 this.XMLDOM.appendChild(node);
|
n@453
|
3269 }
|
n@453
|
3270 }
|
n@453
|
3271 root.appendChild(this.XMLDOM);
|
n@453
|
3272
|
n@453
|
3273 this.postResult = function(node)
|
n@453
|
3274 {
|
n@453
|
3275 // From popup: node is the popupOption node containing both spec. and results
|
n@453
|
3276 // ID is the position
|
n@453
|
3277 if (node.specification.type == 'statement'){return;}
|
n@602
|
3278 var surveyresult = this.XMLDOM.children[0];
|
n@602
|
3279 while(surveyresult != null) {
|
n@602
|
3280 if (surveyresult.getAttribute("ref") == node.specification.id)
|
n@602
|
3281 {
|
n@602
|
3282 break;
|
n@602
|
3283 }
|
n@602
|
3284 surveyresult = surveyresult.nextElementSibling;
|
n@602
|
3285 }
|
n@453
|
3286 switch(node.specification.type)
|
n@453
|
3287 {
|
n@453
|
3288 case "number":
|
n@453
|
3289 case "question":
|
n@453
|
3290 var child = this.parent.document.createElement('response');
|
n@453
|
3291 child.textContent = node.response;
|
n@453
|
3292 surveyresult.appendChild(child);
|
n@453
|
3293 break;
|
n@453
|
3294 case "radio":
|
n@453
|
3295 var child = this.parent.document.createElement('response');
|
n@453
|
3296 child.setAttribute('name',node.response.name);
|
n@453
|
3297 child.textContent = node.response.text;
|
n@453
|
3298 surveyresult.appendChild(child);
|
n@453
|
3299 break;
|
n@453
|
3300 case "checkbox":
|
n@453
|
3301 for (var i=0; i<node.response.length; i++)
|
n@453
|
3302 {
|
n@453
|
3303 var checkNode = this.parent.document.createElement('response');
|
n@476
|
3304 checkNode.setAttribute('name',node.response[i].name);
|
n@476
|
3305 checkNode.setAttribute('checked',node.response[i].checked);
|
n@455
|
3306 surveyresult.appendChild(checkNode);
|
n@453
|
3307 }
|
n@453
|
3308 break;
|
n@453
|
3309 }
|
n@453
|
3310 };
|
n@602
|
3311 this.complete = function() {
|
n@602
|
3312 this.state = "complete";
|
n@602
|
3313 this.XMLDOM.setAttribute("state",this.state);
|
n@602
|
3314 }
|
n@453
|
3315 };
|
n@453
|
3316
|
n@453
|
3317 this.pageNode = function(parent,specification)
|
n@453
|
3318 {
|
n@453
|
3319 // Create one store per test page
|
n@453
|
3320 this.specification = specification;
|
n@453
|
3321 this.parent = parent;
|
n@602
|
3322 this.state = "empty";
|
n@453
|
3323 this.XMLDOM = this.parent.document.createElement('page');
|
n@602
|
3324 this.XMLDOM.setAttribute('ref',specification.id);
|
n@453
|
3325 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
n@602
|
3326 this.XMLDOM.setAttribute("state",this.state);
|
n@474
|
3327 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
n@474
|
3328 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
n@453
|
3329
|
n@453
|
3330 // Add any page metrics
|
n@453
|
3331 var page_metric = this.parent.document.createElement('metric');
|
n@453
|
3332 this.XMLDOM.appendChild(page_metric);
|
n@453
|
3333
|
n@453
|
3334 // Add the audioelement
|
n@453
|
3335 for (var element of this.specification.audioElements)
|
n@453
|
3336 {
|
n@453
|
3337 var aeNode = this.parent.document.createElement('audioelement');
|
n@602
|
3338 aeNode.setAttribute('ref',element.id);
|
n@602
|
3339 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
|
n@453
|
3340 aeNode.setAttribute('type',element.type);
|
n@453
|
3341 aeNode.setAttribute('url', element.url);
|
n@453
|
3342 aeNode.setAttribute('gain', element.gain);
|
n@453
|
3343 if (element.type == 'anchor' || element.type == 'reference')
|
n@453
|
3344 {
|
n@453
|
3345 if (element.marker > 0)
|
n@453
|
3346 {
|
n@453
|
3347 aeNode.setAttribute('marker',element.marker);
|
n@453
|
3348 }
|
n@453
|
3349 }
|
n@453
|
3350 var ae_metric = this.parent.document.createElement('metric');
|
n@453
|
3351 aeNode.appendChild(ae_metric);
|
n@453
|
3352 this.XMLDOM.appendChild(aeNode);
|
n@453
|
3353 }
|
n@453
|
3354
|
n@453
|
3355 this.parent.root.appendChild(this.XMLDOM);
|
n@602
|
3356
|
n@602
|
3357 this.complete = function() {
|
n@602
|
3358 this.state = "complete";
|
n@602
|
3359 this.XMLDOM.setAttribute("state","complete");
|
n@602
|
3360 }
|
n@453
|
3361 };
|
n@589
|
3362 this.update = function() {
|
n@589
|
3363 this.SessionKey.update();
|
n@589
|
3364 }
|
n@453
|
3365 this.finish = function()
|
n@453
|
3366 {
|
n@453
|
3367 if (this.state == 0)
|
n@453
|
3368 {
|
n@589
|
3369 this.update();
|
n@453
|
3370 }
|
n@453
|
3371 this.state = 1;
|
n@453
|
3372 return this.root;
|
n@453
|
3373 };
|
n@453
|
3374 }
|