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