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