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@558
|
805 if (specification.testPages <= i && specification.testPages != 0) {break;}
|
n@453
|
806 this.stateMap.push(pageHolder[i]);
|
n@453
|
807 }
|
n@558
|
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@561
|
1183 this.status = 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@553
|
1365 if (event.currentTarget != null) {
|
n@553
|
1366 event.currentTarget.owner.stop(audioContext.currentTime+1);
|
n@553
|
1367 }
|
n@202
|
1368 };
|
n@202
|
1369 if (this.bufferNode.loop == false) {
|
n@202
|
1370 this.metric.startListening(audioEngineContext.timer.getTestTime());
|
n@489
|
1371 this.outputGain.gain.setValueAtTime(this.onplayGain,startTime);
|
n@489
|
1372 this.interfaceDOM.startPlayback();
|
n@489
|
1373 } else {
|
n@489
|
1374 this.outputGain.gain.setValueAtTime(0.0,startTime);
|
n@489
|
1375 }
|
n@202
|
1376 this.bufferNode.start(startTime);
|
n@560
|
1377 this.bufferNode.playbackStartTime = audioEngineContext.timer.getTestTime();
|
nicholas@110
|
1378 }
|
n@16
|
1379 };
|
nicholas@1
|
1380
|
n@489
|
1381 this.stop = function(stopTime) {
|
n@489
|
1382 this.outputGain.gain.cancelScheduledValues(audioContext.currentTime);
|
n@97
|
1383 if (this.bufferNode != undefined)
|
n@97
|
1384 {
|
n@203
|
1385 this.metric.stopListening(audioEngineContext.timer.getTestTime(),this.getCurrentPosition());
|
n@489
|
1386 this.bufferNode.stop(stopTime);
|
n@97
|
1387 this.bufferNode = undefined;
|
n@97
|
1388 }
|
n@489
|
1389 this.outputGain.gain.value = 0.0;
|
n@489
|
1390 this.interfaceDOM.stopPlayback();
|
n@16
|
1391 };
|
n@164
|
1392
|
n@164
|
1393 this.getCurrentPosition = function() {
|
n@164
|
1394 var time = audioEngineContext.timer.getTestTime();
|
n@164
|
1395 if (this.bufferNode != undefined) {
|
n@575
|
1396 var position = (time - this.bufferNode.playbackStartTime)%this.buffer.buffer.duration;
|
n@575
|
1397 if (isNaN(position)){return 0;}
|
n@575
|
1398 return position;
|
n@164
|
1399 } else {
|
n@164
|
1400 return 0;
|
n@164
|
1401 }
|
n@164
|
1402 };
|
n@183
|
1403
|
n@183
|
1404 this.exportXMLDOM = function() {
|
n@453
|
1405 var file = storage.document.createElement('file');
|
nicholas@387
|
1406 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
|
nicholas@387
|
1407 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
|
nicholas@387
|
1408 file.setAttribute('sampleCount',this.buffer.buffer.length);
|
nicholas@387
|
1409 file.setAttribute('duration',this.buffer.buffer.duration);
|
n@453
|
1410 this.storeDOM.appendChild(file);
|
n@453
|
1411 if (this.specification.type != 'outside-reference') {
|
n@383
|
1412 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
n@469
|
1413 if (interfaceXML != null)
|
n@469
|
1414 {
|
n@469
|
1415 if (interfaceXML.length == undefined) {
|
n@469
|
1416 this.storeDOM.appendChild(interfaceXML);
|
n@469
|
1417 } else {
|
n@469
|
1418 for (var i=0; i<interfaceXML.length; i++)
|
n@469
|
1419 {
|
n@469
|
1420 this.storeDOM.appendChild(interfaceXML[i]);
|
n@469
|
1421 }
|
n@383
|
1422 }
|
n@383
|
1423 }
|
n@459
|
1424 if (this.commentDOM != null) {
|
n@459
|
1425 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
n@459
|
1426 }
|
nicholas@236
|
1427 }
|
n@453
|
1428 var nodes = this.metric.exportXMLDOM();
|
n@453
|
1429 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
|
n@453
|
1430 for (var i=0; i<nodes.length; i++)
|
n@453
|
1431 {
|
n@453
|
1432 mroot.appendChild(nodes[i]);
|
n@453
|
1433 }
|
n@183
|
1434 };
|
n@49
|
1435 }
|
n@49
|
1436
|
n@49
|
1437 function timer()
|
n@49
|
1438 {
|
n@49
|
1439 /* Timer object used in audioEngine to keep track of session timings
|
n@49
|
1440 * Uses the timer of the web audio API, so sample resolution
|
n@49
|
1441 */
|
n@49
|
1442 this.testStarted = false;
|
n@49
|
1443 this.testStartTime = 0;
|
n@49
|
1444 this.testDuration = 0;
|
n@49
|
1445 this.minimumTestTime = 0; // No minimum test time
|
n@49
|
1446 this.startTest = function()
|
n@49
|
1447 {
|
n@49
|
1448 if (this.testStarted == false)
|
n@49
|
1449 {
|
n@49
|
1450 this.testStartTime = audioContext.currentTime;
|
n@49
|
1451 this.testStarted = true;
|
n@49
|
1452 this.updateTestTime();
|
n@52
|
1453 audioEngineContext.metric.initialiseTest();
|
n@49
|
1454 }
|
n@49
|
1455 };
|
n@49
|
1456 this.stopTest = function()
|
n@49
|
1457 {
|
n@49
|
1458 if (this.testStarted)
|
n@49
|
1459 {
|
n@49
|
1460 this.testDuration = this.getTestTime();
|
n@49
|
1461 this.testStarted = false;
|
n@49
|
1462 } else {
|
n@49
|
1463 console.log('ERR: Test tried to end before beginning');
|
n@49
|
1464 }
|
n@49
|
1465 };
|
n@49
|
1466 this.updateTestTime = function()
|
n@49
|
1467 {
|
n@49
|
1468 if (this.testStarted)
|
n@49
|
1469 {
|
n@49
|
1470 this.testDuration = audioContext.currentTime - this.testStartTime;
|
n@49
|
1471 }
|
n@49
|
1472 };
|
n@49
|
1473 this.getTestTime = function()
|
n@49
|
1474 {
|
n@49
|
1475 this.updateTestTime();
|
n@49
|
1476 return this.testDuration;
|
n@49
|
1477 };
|
n@49
|
1478 }
|
n@49
|
1479
|
n@377
|
1480 function sessionMetrics(engine,specification)
|
n@49
|
1481 {
|
n@49
|
1482 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
n@49
|
1483 */
|
n@49
|
1484 this.engine = engine;
|
n@49
|
1485 this.lastClicked = -1;
|
n@49
|
1486 this.data = -1;
|
n@113
|
1487 this.reset = function() {
|
n@113
|
1488 this.lastClicked = -1;
|
n@113
|
1489 this.data = -1;
|
n@113
|
1490 };
|
n@377
|
1491
|
n@377
|
1492 this.enableElementInitialPosition = false;
|
n@377
|
1493 this.enableElementListenTracker = false;
|
n@377
|
1494 this.enableElementTimer = false;
|
n@377
|
1495 this.enableElementTracker = false;
|
n@377
|
1496 this.enableFlagListenedTo = false;
|
n@377
|
1497 this.enableFlagMoved = false;
|
n@377
|
1498 this.enableTestTimer = false;
|
n@377
|
1499 // Obtain the metrics enabled
|
n@453
|
1500 for (var i=0; i<specification.metrics.enabled.length; i++)
|
n@377
|
1501 {
|
n@453
|
1502 var node = specification.metrics.enabled[i];
|
n@453
|
1503 switch(node)
|
n@377
|
1504 {
|
n@377
|
1505 case 'testTimer':
|
n@377
|
1506 this.enableTestTimer = true;
|
n@377
|
1507 break;
|
n@377
|
1508 case 'elementTimer':
|
n@377
|
1509 this.enableElementTimer = true;
|
n@377
|
1510 break;
|
n@377
|
1511 case 'elementTracker':
|
n@377
|
1512 this.enableElementTracker = true;
|
n@377
|
1513 break;
|
n@377
|
1514 case 'elementListenTracker':
|
n@377
|
1515 this.enableElementListenTracker = true;
|
n@377
|
1516 break;
|
n@377
|
1517 case 'elementInitialPosition':
|
n@377
|
1518 this.enableElementInitialPosition = true;
|
n@377
|
1519 break;
|
n@377
|
1520 case 'elementFlagListenedTo':
|
n@377
|
1521 this.enableFlagListenedTo = true;
|
n@377
|
1522 break;
|
n@377
|
1523 case 'elementFlagMoved':
|
n@377
|
1524 this.enableFlagMoved = true;
|
n@377
|
1525 break;
|
n@377
|
1526 case 'elementFlagComments':
|
n@377
|
1527 this.enableFlagComments = true;
|
n@377
|
1528 break;
|
n@377
|
1529 }
|
n@377
|
1530 }
|
n@52
|
1531 this.initialiseTest = function(){};
|
n@49
|
1532 }
|
n@49
|
1533
|
n@139
|
1534 function metricTracker(caller)
|
n@49
|
1535 {
|
n@49
|
1536 /* Custom object to track and collect metric data
|
n@49
|
1537 * Used only inside the audioObjects object.
|
n@49
|
1538 */
|
n@49
|
1539
|
n@49
|
1540 this.listenedTimer = 0;
|
n@49
|
1541 this.listenStart = 0;
|
nicholas@110
|
1542 this.listenHold = false;
|
n@51
|
1543 this.initialPosition = -1;
|
n@49
|
1544 this.movementTracker = [];
|
n@164
|
1545 this.listenTracker =[];
|
n@49
|
1546 this.wasListenedTo = false;
|
n@49
|
1547 this.wasMoved = false;
|
n@49
|
1548 this.hasComments = false;
|
n@139
|
1549 this.parent = caller;
|
n@49
|
1550
|
n@453
|
1551 this.initialise = function(position)
|
n@49
|
1552 {
|
n@51
|
1553 if (this.initialPosition == -1) {
|
n@51
|
1554 this.initialPosition = position;
|
n@454
|
1555 this.moved(0,position);
|
n@51
|
1556 }
|
n@49
|
1557 };
|
n@49
|
1558
|
n@49
|
1559 this.moved = function(time,position)
|
n@49
|
1560 {
|
n@454
|
1561 if (time > 0) {this.wasMoved = true;}
|
n@49
|
1562 this.movementTracker[this.movementTracker.length] = [time, position];
|
n@49
|
1563 };
|
n@49
|
1564
|
nicholas@132
|
1565 this.startListening = function(time)
|
n@49
|
1566 {
|
nicholas@110
|
1567 if (this.listenHold == false)
|
n@49
|
1568 {
|
n@49
|
1569 this.wasListenedTo = true;
|
n@49
|
1570 this.listenStart = time;
|
nicholas@110
|
1571 this.listenHold = true;
|
n@164
|
1572
|
n@164
|
1573 var evnt = document.createElement('event');
|
n@164
|
1574 var testTime = document.createElement('testTime');
|
n@164
|
1575 testTime.setAttribute('start',time);
|
n@164
|
1576 var bufferTime = document.createElement('bufferTime');
|
n@164
|
1577 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
|
n@164
|
1578 evnt.appendChild(testTime);
|
n@164
|
1579 evnt.appendChild(bufferTime);
|
n@164
|
1580 this.listenTracker.push(evnt);
|
n@164
|
1581
|
n@139
|
1582 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
n@139
|
1583 }
|
n@139
|
1584 };
|
nicholas@132
|
1585
|
n@203
|
1586 this.stopListening = function(time,bufferStopTime)
|
nicholas@132
|
1587 {
|
nicholas@132
|
1588 if (this.listenHold == true)
|
nicholas@132
|
1589 {
|
n@164
|
1590 var diff = time - this.listenStart;
|
n@164
|
1591 this.listenedTimer += (diff);
|
n@49
|
1592 this.listenStart = 0;
|
nicholas@110
|
1593 this.listenHold = false;
|
n@164
|
1594
|
n@164
|
1595 var evnt = this.listenTracker[this.listenTracker.length-1];
|
n@164
|
1596 var testTime = evnt.getElementsByTagName('testTime')[0];
|
n@164
|
1597 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
n@164
|
1598 testTime.setAttribute('stop',time);
|
n@203
|
1599 if (bufferStopTime == undefined) {
|
n@203
|
1600 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
|
n@203
|
1601 } else {
|
n@203
|
1602 bufferTime.setAttribute('stop',bufferStopTime);
|
n@203
|
1603 }
|
n@164
|
1604 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
n@49
|
1605 }
|
n@49
|
1606 };
|
n@177
|
1607
|
n@177
|
1608 this.exportXMLDOM = function() {
|
n@453
|
1609 var storeDOM = [];
|
n@177
|
1610 if (audioEngineContext.metric.enableElementTimer) {
|
n@453
|
1611 var mElementTimer = storage.document.createElement('metricresult');
|
n@177
|
1612 mElementTimer.setAttribute('name','enableElementTimer');
|
n@177
|
1613 mElementTimer.textContent = this.listenedTimer;
|
n@453
|
1614 storeDOM.push(mElementTimer);
|
n@177
|
1615 }
|
n@177
|
1616 if (audioEngineContext.metric.enableElementTracker) {
|
n@453
|
1617 var elementTrackerFull = storage.document.createElement('metricResult');
|
n@177
|
1618 elementTrackerFull.setAttribute('name','elementTrackerFull');
|
n@177
|
1619 for (var k=0; k<this.movementTracker.length; k++)
|
n@177
|
1620 {
|
n@575
|
1621 var timePos = storage.document.createElement('movement');
|
n@575
|
1622 timePos.setAttribute("time",this.movementTracker[k][0]);
|
n@575
|
1623 timePos.setAttribute("value",this.movementTracker[k][1]);
|
n@177
|
1624 elementTrackerFull.appendChild(timePos);
|
n@177
|
1625 }
|
n@453
|
1626 storeDOM.push(elementTrackerFull);
|
n@177
|
1627 }
|
n@177
|
1628 if (audioEngineContext.metric.enableElementListenTracker) {
|
n@453
|
1629 var elementListenTracker = storage.document.createElement('metricResult');
|
n@177
|
1630 elementListenTracker.setAttribute('name','elementListenTracker');
|
n@177
|
1631 for (var k=0; k<this.listenTracker.length; k++) {
|
n@177
|
1632 elementListenTracker.appendChild(this.listenTracker[k]);
|
n@177
|
1633 }
|
n@453
|
1634 storeDOM.push(elementListenTracker);
|
n@177
|
1635 }
|
n@177
|
1636 if (audioEngineContext.metric.enableElementInitialPosition) {
|
n@453
|
1637 var elementInitial = storage.document.createElement('metricResult');
|
n@177
|
1638 elementInitial.setAttribute('name','elementInitialPosition');
|
n@177
|
1639 elementInitial.textContent = this.initialPosition;
|
n@453
|
1640 storeDOM.push(elementInitial);
|
n@177
|
1641 }
|
n@177
|
1642 if (audioEngineContext.metric.enableFlagListenedTo) {
|
n@453
|
1643 var flagListenedTo = storage.document.createElement('metricResult');
|
n@177
|
1644 flagListenedTo.setAttribute('name','elementFlagListenedTo');
|
n@177
|
1645 flagListenedTo.textContent = this.wasListenedTo;
|
n@453
|
1646 storeDOM.push(flagListenedTo);
|
n@177
|
1647 }
|
n@177
|
1648 if (audioEngineContext.metric.enableFlagMoved) {
|
n@453
|
1649 var flagMoved = storage.document.createElement('metricResult');
|
n@177
|
1650 flagMoved.setAttribute('name','elementFlagMoved');
|
n@177
|
1651 flagMoved.textContent = this.wasMoved;
|
n@453
|
1652 storeDOM.push(flagMoved);
|
n@177
|
1653 }
|
n@177
|
1654 if (audioEngineContext.metric.enableFlagComments) {
|
n@453
|
1655 var flagComments = storage.document.createElement('metricResult');
|
n@177
|
1656 flagComments.setAttribute('name','elementFlagComments');
|
n@177
|
1657 if (this.parent.commentDOM == null)
|
n@177
|
1658 {flag.textContent = 'false';}
|
n@177
|
1659 else if (this.parent.commentDOM.textContent.length == 0)
|
n@177
|
1660 {flag.textContent = 'false';}
|
n@177
|
1661 else
|
n@177
|
1662 {flag.textContet = 'true';}
|
n@453
|
1663 storeDOM.push(flagComments);
|
n@177
|
1664 }
|
n@453
|
1665 return storeDOM;
|
n@177
|
1666 };
|
n@54
|
1667 }
|
n@54
|
1668
|
n@54
|
1669 function randomiseOrder(input)
|
n@54
|
1670 {
|
n@54
|
1671 // This takes an array of information and randomises the order
|
n@54
|
1672 var N = input.length;
|
b@207
|
1673
|
b@207
|
1674 var inputSequence = []; // For safety purposes: keep track of randomisation
|
b@207
|
1675 for (var counter = 0; counter < N; ++counter)
|
b@207
|
1676 inputSequence.push(counter) // Fill array
|
b@207
|
1677 var inputSequenceClone = inputSequence.slice(0);
|
b@207
|
1678
|
n@54
|
1679 var holdArr = [];
|
b@207
|
1680 var outputSequence = [];
|
n@54
|
1681 for (var n=0; n<N; n++)
|
n@54
|
1682 {
|
n@54
|
1683 // First pick a random number
|
n@54
|
1684 var r = Math.random();
|
n@54
|
1685 // Multiply and floor by the number of elements left
|
n@54
|
1686 r = Math.floor(r*input.length);
|
n@54
|
1687 // Pick out that element and delete from the array
|
n@54
|
1688 holdArr.push(input.splice(r,1)[0]);
|
b@207
|
1689 // Do the same with sequence
|
b@207
|
1690 outputSequence.push(inputSequence.splice(r,1)[0]);
|
n@54
|
1691 }
|
b@207
|
1692 console.log(inputSequenceClone.toString()); // print original array to console
|
b@207
|
1693 console.log(outputSequence.toString()); // print randomised array to console
|
n@54
|
1694 return holdArr;
|
n@125
|
1695 }
|
n@125
|
1696
|
n@125
|
1697 function returnDateNode()
|
n@125
|
1698 {
|
n@125
|
1699 // Create an XML Node for the Date and Time a test was conducted
|
n@125
|
1700 // Structure is
|
n@125
|
1701 // <datetime>
|
n@125
|
1702 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
n@125
|
1703 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
n@125
|
1704 // </datetime>
|
n@125
|
1705 var dateTime = new Date();
|
n@125
|
1706 var year = document.createAttribute('year');
|
n@125
|
1707 var month = document.createAttribute('month');
|
n@125
|
1708 var day = document.createAttribute('day');
|
n@125
|
1709 var hour = document.createAttribute('hour');
|
n@125
|
1710 var minute = document.createAttribute('minute');
|
n@125
|
1711 var secs = document.createAttribute('secs');
|
n@125
|
1712
|
n@125
|
1713 year.nodeValue = dateTime.getFullYear();
|
n@125
|
1714 month.nodeValue = dateTime.getMonth()+1;
|
n@125
|
1715 day.nodeValue = dateTime.getDate();
|
n@125
|
1716 hour.nodeValue = dateTime.getHours();
|
n@125
|
1717 minute.nodeValue = dateTime.getMinutes();
|
n@125
|
1718 secs.nodeValue = dateTime.getSeconds();
|
n@125
|
1719
|
n@125
|
1720 var hold = document.createElement("datetime");
|
n@125
|
1721 var date = document.createElement("date");
|
n@125
|
1722 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
|
n@125
|
1723 var time = document.createElement("time");
|
n@125
|
1724 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
|
n@125
|
1725
|
n@125
|
1726 date.setAttributeNode(year);
|
n@125
|
1727 date.setAttributeNode(month);
|
n@125
|
1728 date.setAttributeNode(day);
|
n@125
|
1729 time.setAttributeNode(hour);
|
n@125
|
1730 time.setAttributeNode(minute);
|
n@125
|
1731 time.setAttributeNode(secs);
|
n@125
|
1732
|
n@125
|
1733 hold.appendChild(date);
|
n@125
|
1734 hold.appendChild(time);
|
n@377
|
1735 return hold;
|
n@125
|
1736
|
nicholas@135
|
1737 }
|
nicholas@135
|
1738
|
n@180
|
1739 function Specification() {
|
n@180
|
1740 // Handles the decoding of the project specification XML into a simple JavaScript Object.
|
n@180
|
1741
|
n@453
|
1742 this.interface = null;
|
n@504
|
1743 this.projectReturn = "null";
|
n@453
|
1744 this.randomiseOrder = null;
|
n@453
|
1745 this.testPages = null;
|
n@453
|
1746 this.pages = [];
|
n@453
|
1747 this.metrics = null;
|
n@453
|
1748 this.interfaces = null;
|
n@453
|
1749 this.loudness = null;
|
n@453
|
1750 this.errors = [];
|
n@453
|
1751 this.schema = null;
|
n@380
|
1752
|
n@453
|
1753 this.processAttribute = function(attribute,schema)
|
n@453
|
1754 {
|
n@453
|
1755 // attribute is the string returned from getAttribute on the XML
|
n@453
|
1756 // schema is the <xs:attribute> node
|
n@453
|
1757 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
|
n@453
|
1758 {
|
n@477
|
1759 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
|
n@453
|
1760 }
|
n@453
|
1761 var defaultOpt = schema.getAttribute('default');
|
n@453
|
1762 if (attribute == null) {
|
n@453
|
1763 attribute = defaultOpt;
|
n@453
|
1764 }
|
n@453
|
1765 var dataType = schema.getAttribute('type');
|
n@453
|
1766 if (typeof dataType == "string") { dataType = dataType.substr(3);}
|
n@453
|
1767 else {dataType = "string";}
|
n@453
|
1768 if (attribute == null)
|
n@453
|
1769 {
|
n@453
|
1770 return attribute;
|
n@453
|
1771 }
|
n@453
|
1772 switch(dataType)
|
n@453
|
1773 {
|
n@453
|
1774 case "boolean":
|
n@453
|
1775 if (attribute == 'true'){attribute = true;}else{attribute=false;}
|
n@453
|
1776 break;
|
n@453
|
1777 case "negativeInteger":
|
n@453
|
1778 case "positiveInteger":
|
n@453
|
1779 case "nonNegativeInteger":
|
n@453
|
1780 case "nonPositiveInteger":
|
n@453
|
1781 case "integer":
|
n@453
|
1782 case "decimal":
|
n@453
|
1783 case "short":
|
n@453
|
1784 attribute = Number(attribute);
|
n@453
|
1785 break;
|
n@453
|
1786 case "string":
|
n@453
|
1787 default:
|
n@453
|
1788 attribute = String(attribute);
|
n@453
|
1789 break;
|
n@453
|
1790 }
|
n@453
|
1791 return attribute;
|
n@453
|
1792 };
|
n@180
|
1793
|
n@374
|
1794 this.decode = function(projectXML) {
|
n@453
|
1795 this.errors = [];
|
n@180
|
1796 // projectXML - DOM Parsed document
|
nicholas@240
|
1797 this.projectXML = projectXML.childNodes[0];
|
n@180
|
1798 var setupNode = projectXML.getElementsByTagName('setup')[0];
|
n@477
|
1799 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
|
n@453
|
1800 // First decode the attributes
|
n@477
|
1801 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
|
n@453
|
1802 for (var i in attributes)
|
n@297
|
1803 {
|
n@453
|
1804 if (isNaN(Number(i)) == true){break;}
|
n@453
|
1805 var attributeName = attributes[i].getAttribute('name');
|
n@453
|
1806 var projectAttr = setupNode.getAttribute(attributeName);
|
n@453
|
1807 projectAttr = this.processAttribute(projectAttr,attributes[i]);
|
n@453
|
1808 switch(typeof projectAttr)
|
n@410
|
1809 {
|
n@453
|
1810 case "number":
|
n@453
|
1811 case "boolean":
|
n@453
|
1812 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
1813 break;
|
n@453
|
1814 case "string":
|
n@453
|
1815 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
1816 break;
|
n@410
|
1817 }
|
n@453
|
1818
|
n@374
|
1819 }
|
n@374
|
1820
|
n@501
|
1821 this.metrics = new this.metricNode();
|
n@180
|
1822
|
n@453
|
1823 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
|
n@453
|
1824
|
n@453
|
1825 // Now process the survey node options
|
n@453
|
1826 var survey = setupNode.getElementsByTagName('survey');
|
n@453
|
1827 for (var i in survey) {
|
n@453
|
1828 if (isNaN(Number(i)) == true){break;}
|
n@453
|
1829 var location = survey[i].getAttribute('location');
|
n@453
|
1830 if (location == 'pre' || location == 'before')
|
n@453
|
1831 {
|
n@453
|
1832 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
|
n@453
|
1833 else {
|
n@453
|
1834 this.preTest = new this.surveyNode();
|
n@501
|
1835 this.preTest.decode(this,survey[i]);
|
n@453
|
1836 }
|
n@453
|
1837 } else if (location == 'post' || location == 'after') {
|
n@453
|
1838 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
|
n@453
|
1839 else {
|
n@453
|
1840 this.postTest = new this.surveyNode();
|
n@501
|
1841 this.postTest.decode(this,survey[i]);
|
n@453
|
1842 }
|
n@180
|
1843 }
|
n@180
|
1844 }
|
n@180
|
1845
|
n@453
|
1846 var interfaceNode = setupNode.getElementsByTagName('interface');
|
n@453
|
1847 if (interfaceNode.length > 1)
|
n@453
|
1848 {
|
n@453
|
1849 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
|
n@453
|
1850 }
|
n@453
|
1851 this.interfaces = new this.interfaceNode();
|
n@453
|
1852 if (interfaceNode.length != 0)
|
n@453
|
1853 {
|
n@453
|
1854 interfaceNode = interfaceNode[0];
|
n@477
|
1855 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
|
nicholas@213
|
1856 }
|
nicholas@213
|
1857
|
n@453
|
1858 // Page tags
|
n@453
|
1859 var pageTags = projectXML.getElementsByTagName('page');
|
n@477
|
1860 var pageSchema = this.schema.getAllElementsByName('page')[0];
|
n@453
|
1861 for (var i=0; i<pageTags.length; i++)
|
n@297
|
1862 {
|
n@453
|
1863 var node = new this.page();
|
n@453
|
1864 node.decode(this,pageTags[i],pageSchema);
|
n@453
|
1865 this.pages.push(node);
|
n@297
|
1866 }
|
n@180
|
1867 };
|
n@180
|
1868
|
n@374
|
1869 this.encode = function()
|
n@374
|
1870 {
|
n@503
|
1871 var RootDocument = document.implementation.createDocument(null,"waet");
|
n@503
|
1872 var root = RootDocument.children[0];
|
n@503
|
1873 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
|
n@503
|
1874 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
|
n@453
|
1875 // Build setup node
|
n@503
|
1876 var setup = RootDocument.createElement("setup");
|
n@503
|
1877 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
|
n@503
|
1878 // First decode the attributes
|
n@503
|
1879 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
|
n@503
|
1880 for (var i=0; i<attributes.length; i++)
|
n@503
|
1881 {
|
n@503
|
1882 var name = attributes[i].getAttribute("name");
|
n@503
|
1883 if (name == undefined) {
|
n@503
|
1884 name = attributes[i].getAttribute("ref");
|
n@503
|
1885 }
|
n@503
|
1886 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
1887 {
|
n@503
|
1888 eval("setup.setAttribute('"+name+"',this."+name+")");
|
n@503
|
1889 }
|
n@503
|
1890 }
|
n@503
|
1891 root.appendChild(setup);
|
n@503
|
1892 // Survey node
|
n@503
|
1893 setup.appendChild(this.preTest.encode(RootDocument));
|
n@503
|
1894 setup.appendChild(this.postTest.encode(RootDocument));
|
n@503
|
1895 setup.appendChild(this.metrics.encode(RootDocument));
|
n@503
|
1896 setup.appendChild(this.interfaces.encode(RootDocument));
|
n@503
|
1897 for (var page of this.pages)
|
n@503
|
1898 {
|
n@503
|
1899 root.appendChild(page.encode(RootDocument));
|
n@503
|
1900 }
|
n@503
|
1901 return RootDocument;
|
n@374
|
1902 };
|
n@374
|
1903
|
n@453
|
1904 this.surveyNode = function() {
|
n@453
|
1905 this.location = null;
|
n@180
|
1906 this.options = [];
|
n@501
|
1907 this.schema = specification.schema.getAllElementsByName('survey')[0];
|
n@180
|
1908
|
n@374
|
1909 this.OptionNode = function() {
|
n@374
|
1910 this.type = undefined;
|
n@501
|
1911 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
|
n@374
|
1912 this.id = undefined;
|
n@374
|
1913 this.mandatory = undefined;
|
n@374
|
1914 this.statement = undefined;
|
n@374
|
1915 this.boxsize = undefined;
|
n@374
|
1916 this.options = [];
|
n@374
|
1917 this.min = undefined;
|
n@374
|
1918 this.max = undefined;
|
n@374
|
1919 this.step = undefined;
|
n@374
|
1920
|
n@501
|
1921 this.decode = function(parent,child)
|
n@374
|
1922 {
|
n@501
|
1923 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
1924 for (var i in attributeMap){
|
n@453
|
1925 if(isNaN(Number(i)) == true){break;}
|
n@453
|
1926 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
1927 var projectAttr = child.getAttribute(attributeName);
|
n@453
|
1928 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
|
n@453
|
1929 switch(typeof projectAttr)
|
n@453
|
1930 {
|
n@453
|
1931 case "number":
|
n@453
|
1932 case "boolean":
|
n@453
|
1933 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
1934 break;
|
n@453
|
1935 case "string":
|
n@453
|
1936 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
1937 break;
|
n@374
|
1938 }
|
n@453
|
1939 }
|
n@453
|
1940 this.statement = child.getElementsByTagName('statement')[0].textContent;
|
n@453
|
1941 if (this.type == "checkbox" || this.type == "radio") {
|
n@453
|
1942 var children = child.getElementsByTagName('option');
|
n@453
|
1943 if (children.length == null) {
|
n@374
|
1944 console.log('Malformed' +child.nodeName+ 'entry');
|
n@374
|
1945 this.statement = 'Malformed' +child.nodeName+ 'entry';
|
n@374
|
1946 this.type = 'statement';
|
n@374
|
1947 } else {
|
n@374
|
1948 this.options = [];
|
n@453
|
1949 for (var i in children)
|
n@453
|
1950 {
|
n@453
|
1951 if (isNaN(Number(i))==true){break;}
|
n@453
|
1952 this.options.push({
|
n@453
|
1953 name: children[i].getAttribute('name'),
|
n@453
|
1954 text: children[i].textContent
|
n@453
|
1955 });
|
n@374
|
1956 }
|
n@374
|
1957 }
|
n@191
|
1958 }
|
n@374
|
1959 };
|
n@374
|
1960
|
n@503
|
1961 this.exportXML = function(doc)
|
n@374
|
1962 {
|
n@544
|
1963 var node = doc.createElement('surveyentry');
|
n@453
|
1964 node.setAttribute('type',this.type);
|
n@503
|
1965 var statement = doc.createElement('statement');
|
n@453
|
1966 statement.textContent = this.statement;
|
n@453
|
1967 node.appendChild(statement);
|
n@374
|
1968 switch(this.type)
|
n@374
|
1969 {
|
n@374
|
1970 case "statement":
|
n@374
|
1971 break;
|
n@374
|
1972 case "question":
|
n@544
|
1973 node.id = this.id;
|
n@544
|
1974 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
|
n@544
|
1975 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
|
n@544
|
1976 break;
|
n@544
|
1977 case "number":
|
n@544
|
1978 node.id = this.id;
|
n@544
|
1979 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
|
n@544
|
1980 if (this.min != undefined) {node.setAttribute("min", this.min);}
|
n@544
|
1981 if (this.max != undefined) {node.setAttribute("max", this.max);}
|
n@544
|
1982 break;
|
n@374
|
1983 case "checkbox":
|
n@374
|
1984 case "radio":
|
n@374
|
1985 node.id = this.id;
|
n@374
|
1986 for (var i=0; i<this.options.length; i++)
|
n@374
|
1987 {
|
n@374
|
1988 var option = this.options[i];
|
n@503
|
1989 var optionNode = doc.createElement("option");
|
n@374
|
1990 optionNode.setAttribute("name",option.name);
|
n@374
|
1991 optionNode.textContent = option.text;
|
n@374
|
1992 node.appendChild(optionNode);
|
n@374
|
1993 }
|
n@374
|
1994 break;
|
nicholas@188
|
1995 }
|
n@374
|
1996 return node;
|
n@374
|
1997 };
|
n@374
|
1998 };
|
n@501
|
1999 this.decode = function(parent,xml) {
|
n@453
|
2000 this.location = xml.getAttribute('location');
|
n@453
|
2001 if (this.location == 'before'){this.location = 'pre';}
|
n@453
|
2002 else if (this.location == 'after'){this.location = 'post';}
|
n@453
|
2003 for (var i in xml.children)
|
n@453
|
2004 {
|
n@453
|
2005 if(isNaN(Number(i))==true){break;}
|
n@374
|
2006 var node = new this.OptionNode();
|
n@501
|
2007 node.decode(parent,xml.children[i]);
|
n@374
|
2008 this.options.push(node);
|
n@453
|
2009 }
|
n@453
|
2010 };
|
n@503
|
2011 this.encode = function(doc) {
|
n@503
|
2012 var node = doc.createElement('survey');
|
n@453
|
2013 node.setAttribute('location',this.location);
|
n@453
|
2014 for (var i=0; i<this.options.length; i++)
|
n@453
|
2015 {
|
n@503
|
2016 node.appendChild(this.options[i].exportXML(doc));
|
n@453
|
2017 }
|
n@453
|
2018 return node;
|
n@453
|
2019 };
|
n@453
|
2020 };
|
n@453
|
2021
|
n@453
|
2022 this.interfaceNode = function()
|
n@453
|
2023 {
|
n@453
|
2024 this.title = null;
|
n@453
|
2025 this.name = null;
|
n@453
|
2026 this.options = [];
|
n@453
|
2027 this.scales = [];
|
n@501
|
2028 this.schema = specification.schema.getAllElementsByName('interface')[1];
|
n@453
|
2029
|
n@501
|
2030 this.decode = function(parent,xml) {
|
n@453
|
2031 this.name = xml.getAttribute('name');
|
n@453
|
2032 var titleNode = xml.getElementsByTagName('title');
|
n@453
|
2033 if (titleNode.length == 1)
|
n@453
|
2034 {
|
n@453
|
2035 this.title = titleNode[0].textContent;
|
n@453
|
2036 }
|
n@453
|
2037 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
|
n@453
|
2038 // Extract interfaceoption node schema
|
n@501
|
2039 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
|
n@477
|
2040 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2041 for (var i=0; i<interfaceOptionNodes.length; i++)
|
n@453
|
2042 {
|
n@453
|
2043 var ioNode = interfaceOptionNodes[i];
|
n@453
|
2044 var option = {};
|
n@453
|
2045 for (var j=0; j<attributeMap.length; j++)
|
n@453
|
2046 {
|
n@453
|
2047 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
|
n@453
|
2048 var projectAttr = ioNode.getAttribute(attributeName);
|
n@453
|
2049 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
|
n@453
|
2050 switch(typeof projectAttr)
|
n@453
|
2051 {
|
n@453
|
2052 case "number":
|
n@453
|
2053 case "boolean":
|
n@453
|
2054 eval('option.'+attributeName+' = '+projectAttr);
|
n@453
|
2055 break;
|
n@453
|
2056 case "string":
|
n@453
|
2057 eval('option.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2058 break;
|
n@453
|
2059 }
|
n@453
|
2060 }
|
n@453
|
2061 this.options.push(option);
|
n@453
|
2062 }
|
n@453
|
2063
|
n@453
|
2064 // Now the scales nodes
|
n@453
|
2065 var scaleParent = xml.getElementsByTagName('scales');
|
n@453
|
2066 if (scaleParent.length == 1) {
|
n@453
|
2067 scaleParent = scaleParent[0];
|
n@453
|
2068 for (var i=0; i<scaleParent.children.length; i++) {
|
n@453
|
2069 var child = scaleParent.children[i];
|
n@453
|
2070 this.scales.push({
|
n@453
|
2071 text: child.textContent,
|
n@453
|
2072 position: Number(child.getAttribute('position'))
|
n@453
|
2073 });
|
n@374
|
2074 }
|
n@180
|
2075 }
|
n@180
|
2076 };
|
n@453
|
2077
|
n@503
|
2078 this.encode = function(doc) {
|
n@503
|
2079 var node = doc.createElement("interface");
|
n@503
|
2080 if (typeof name == "string")
|
n@503
|
2081 node.setAttribute("name",this.name);
|
n@503
|
2082 for (var option of this.options)
|
n@503
|
2083 {
|
n@503
|
2084 var child = doc.createElement("interfaceoption");
|
n@503
|
2085 child.setAttribute("type",option.type);
|
n@503
|
2086 child.setAttribute("name",option.name);
|
n@503
|
2087 node.appendChild(child);
|
n@503
|
2088 }
|
n@503
|
2089 if (this.scales.length != 0) {
|
n@503
|
2090 var scales = doc.createElement("scales");
|
n@503
|
2091 for (var scale of this.scales)
|
n@503
|
2092 {
|
n@503
|
2093 var child = doc.createElement("scalelabel");
|
n@503
|
2094 child.setAttribute("position",scale.position);
|
n@503
|
2095 child.textContent = scale.text;
|
n@503
|
2096 scales.appendChild(child);
|
n@503
|
2097 }
|
n@503
|
2098 node.appendChild(scales);
|
n@503
|
2099 }
|
n@503
|
2100 return node;
|
n@453
|
2101 };
|
n@180
|
2102 };
|
n@180
|
2103
|
n@501
|
2104 this.metricNode = function() {
|
n@501
|
2105 this.enabled = [];
|
n@501
|
2106 this.decode = function(parent, xml) {
|
n@501
|
2107 var children = xml.getElementsByTagName('metricenable');
|
n@501
|
2108 for (var i in children) {
|
n@501
|
2109 if (isNaN(Number(i)) == true){break;}
|
n@501
|
2110 this.enabled.push(children[i].textContent);
|
n@501
|
2111 }
|
n@501
|
2112 }
|
n@503
|
2113 this.encode = function(doc) {
|
n@503
|
2114 var node = doc.createElement('metric');
|
n@501
|
2115 for (var i in this.enabled)
|
n@501
|
2116 {
|
n@501
|
2117 if (isNaN(Number(i)) == true){break;}
|
n@503
|
2118 var child = doc.createElement('metricenable');
|
n@501
|
2119 child.textContent = this.enabled[i];
|
n@501
|
2120 node.appendChild(child);
|
n@501
|
2121 }
|
n@501
|
2122 return node;
|
n@501
|
2123 }
|
n@501
|
2124 }
|
n@501
|
2125
|
n@453
|
2126 this.page = function() {
|
n@374
|
2127 this.presentedId = undefined;
|
n@374
|
2128 this.id = undefined;
|
n@374
|
2129 this.hostURL = undefined;
|
n@374
|
2130 this.randomiseOrder = undefined;
|
n@374
|
2131 this.loop = undefined;
|
n@453
|
2132 this.showElementComments = undefined;
|
n@374
|
2133 this.outsideReference = null;
|
n@410
|
2134 this.loudness = null;
|
n@453
|
2135 this.preTest = null;
|
n@453
|
2136 this.postTest = null;
|
n@374
|
2137 this.interfaces = [];
|
n@374
|
2138 this.commentBoxPrefix = "Comment on track";
|
n@374
|
2139 this.audioElements = [];
|
n@374
|
2140 this.commentQuestions = [];
|
n@501
|
2141 this.schema = specification.schema.getAllElementsByName("page")[0];
|
n@374
|
2142
|
n@501
|
2143 this.decode = function(parent,xml)
|
n@374
|
2144 {
|
n@477
|
2145 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2146 for (var i=0; i<attributeMap.length; i++)
|
n@410
|
2147 {
|
n@453
|
2148 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2149 var projectAttr = xml.getAttribute(attributeName);
|
n@453
|
2150 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
|
n@453
|
2151 switch(typeof projectAttr)
|
nicholas@417
|
2152 {
|
n@453
|
2153 case "number":
|
n@453
|
2154 case "boolean":
|
n@453
|
2155 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2156 break;
|
n@453
|
2157 case "string":
|
n@453
|
2158 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2159 break;
|
n@374
|
2160 }
|
n@374
|
2161 }
|
n@374
|
2162
|
n@453
|
2163 // Get the Comment Box Prefix
|
n@453
|
2164 var CBP = xml.getElementsByTagName('commentboxprefix');
|
n@453
|
2165 if (CBP.length != 0) {
|
n@453
|
2166 this.commentBoxPrefix = CBP[0].textContent;
|
n@427
|
2167 }
|
n@427
|
2168
|
n@453
|
2169 // Now decode the interfaces
|
n@453
|
2170 var interfaceNode = xml.getElementsByTagName('interface');
|
n@453
|
2171 for (var i=0; i<interfaceNode.length; i++)
|
n@453
|
2172 {
|
n@453
|
2173 var node = new parent.interfaceNode();
|
n@477
|
2174 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
|
n@453
|
2175 this.interfaces.push(node);
|
n@453
|
2176 }
|
n@380
|
2177
|
n@453
|
2178 // Now process the survey node options
|
n@453
|
2179 var survey = xml.getElementsByTagName('survey');
|
n@477
|
2180 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
|
n@453
|
2181 for (var i in survey) {
|
n@453
|
2182 if (isNaN(Number(i)) == true){break;}
|
n@453
|
2183 var location = survey[i].getAttribute('location');
|
n@453
|
2184 if (location == 'pre' || location == 'before')
|
n@453
|
2185 {
|
n@453
|
2186 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
|
n@453
|
2187 else {
|
n@453
|
2188 this.preTest = new parent.surveyNode();
|
n@453
|
2189 this.preTest.decode(parent,survey[i],surveySchema);
|
n@453
|
2190 }
|
n@453
|
2191 } else if (location == 'post' || location == 'after') {
|
n@453
|
2192 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
|
n@453
|
2193 else {
|
n@453
|
2194 this.postTest = new parent.surveyNode();
|
n@453
|
2195 this.postTest.decode(parent,survey[i],surveySchema);
|
n@453
|
2196 }
|
n@453
|
2197 }
|
n@453
|
2198 }
|
n@453
|
2199
|
n@453
|
2200 // Now process the audioelement tags
|
n@453
|
2201 var audioElements = xml.getElementsByTagName('audioelement');
|
n@453
|
2202 for (var i=0; i<audioElements.length; i++)
|
n@453
|
2203 {
|
n@453
|
2204 var node = new this.audioElementNode();
|
n@501
|
2205 node.decode(this,audioElements[i]);
|
n@453
|
2206 this.audioElements.push(node);
|
n@453
|
2207 }
|
n@453
|
2208
|
n@453
|
2209 // Now decode the commentquestions
|
n@453
|
2210 var commentQuestions = xml.getElementsByTagName('commentquestion');
|
n@453
|
2211 for (var i=0; i<commentQuestions.length; i++)
|
n@453
|
2212 {
|
n@374
|
2213 var node = new this.commentQuestionNode();
|
n@501
|
2214 node.decode(parent,commentQuestions[i]);
|
n@374
|
2215 this.commentQuestions.push(node);
|
n@180
|
2216 }
|
n@180
|
2217 };
|
n@180
|
2218
|
n@374
|
2219 this.encode = function(root)
|
n@374
|
2220 {
|
n@503
|
2221 var AHNode = root.createElement("page");
|
n@503
|
2222 // First decode the attributes
|
n@503
|
2223 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
|
n@503
|
2224 for (var i=0; i<attributes.length; i++)
|
n@503
|
2225 {
|
n@503
|
2226 var name = attributes[i].getAttribute("name");
|
n@503
|
2227 if (name == undefined) {
|
n@503
|
2228 name = attributes[i].getAttribute("ref");
|
n@503
|
2229 }
|
n@503
|
2230 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
2231 {
|
n@503
|
2232 eval("AHNode.setAttribute('"+name+"',this."+name+")");
|
n@503
|
2233 }
|
n@503
|
2234 }
|
n@410
|
2235 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
|
n@503
|
2236 // <commentboxprefix>
|
n@503
|
2237 var commentboxprefix = root.createElement("commentboxprefix");
|
n@503
|
2238 commentboxprefix.textContent = this.commentBoxPrefix;
|
n@503
|
2239 AHNode.appendChild(commentboxprefix);
|
n@503
|
2240
|
n@374
|
2241 for (var i=0; i<this.interfaces.length; i++)
|
n@324
|
2242 {
|
n@374
|
2243 AHNode.appendChild(this.interfaces[i].encode(root));
|
n@374
|
2244 }
|
n@374
|
2245
|
n@374
|
2246 for (var i=0; i<this.audioElements.length; i++) {
|
n@374
|
2247 AHNode.appendChild(this.audioElements[i].encode(root));
|
n@374
|
2248 }
|
n@374
|
2249 // Create <CommentQuestion>
|
n@374
|
2250 for (var i=0; i<this.commentQuestions.length; i++)
|
n@374
|
2251 {
|
n@503
|
2252 AHNode.appendChild(this.commentQuestions[i].encode(root));
|
n@374
|
2253 }
|
n@374
|
2254
|
n@503
|
2255 AHNode.appendChild(this.preTest.encode(root));
|
n@503
|
2256 AHNode.appendChild(this.postTest.encode(root));
|
n@374
|
2257 return AHNode;
|
n@374
|
2258 };
|
n@374
|
2259
|
n@453
|
2260 this.commentQuestionNode = function() {
|
n@453
|
2261 this.id = null;
|
n@453
|
2262 this.type = undefined;
|
n@374
|
2263 this.options = [];
|
n@453
|
2264 this.statement = undefined;
|
n@501
|
2265 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
|
n@501
|
2266 this.decode = function(parent,xml)
|
n@374
|
2267 {
|
n@453
|
2268 this.id = xml.id;
|
n@453
|
2269 this.type = xml.getAttribute('type');
|
n@453
|
2270 this.statement = xml.getElementsByTagName('statement')[0].textContent;
|
n@453
|
2271 var optNodes = xml.getElementsByTagName('option');
|
n@453
|
2272 for (var i=0; i<optNodes.length; i++)
|
n@453
|
2273 {
|
n@453
|
2274 var optNode = optNodes[i];
|
n@453
|
2275 this.options.push({
|
n@453
|
2276 name: optNode.getAttribute('name'),
|
n@453
|
2277 text: optNode.textContent
|
n@453
|
2278 });
|
n@374
|
2279 }
|
n@374
|
2280 };
|
n@453
|
2281
|
n@374
|
2282 this.encode = function(root)
|
n@374
|
2283 {
|
n@503
|
2284 var node = root.createElement("commentquestion");
|
n@503
|
2285 node.id = this.id;
|
n@503
|
2286 node.setAttribute("type",this.type);
|
n@503
|
2287 var statement = root.createElement("statement");
|
n@503
|
2288 statement.textContent = this.statement;
|
n@503
|
2289 node.appendChild(statement);
|
n@503
|
2290 for (var option of this.options)
|
n@503
|
2291 {
|
n@503
|
2292 var child = root.createElement("option");
|
n@503
|
2293 child.setAttribute("name",option.name);
|
n@503
|
2294 child.textContent = option.text;
|
n@503
|
2295 node.appendChild(child);
|
n@503
|
2296 }
|
n@503
|
2297 return node;
|
n@374
|
2298 };
|
n@374
|
2299 };
|
n@374
|
2300
|
n@374
|
2301 this.audioElementNode = function() {
|
n@374
|
2302 this.url = null;
|
n@374
|
2303 this.id = null;
|
n@374
|
2304 this.parent = null;
|
n@453
|
2305 this.type = null;
|
n@525
|
2306 this.marker = null;
|
n@374
|
2307 this.enforce = false;
|
n@564
|
2308 this.gain = 0.0;
|
n@501
|
2309 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
|
n@453
|
2310 this.parent = null;
|
n@501
|
2311 this.decode = function(parent,xml)
|
n@374
|
2312 {
|
n@374
|
2313 this.parent = parent;
|
n@477
|
2314 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2315 for (var i=0; i<attributeMap.length; i++)
|
n@400
|
2316 {
|
n@453
|
2317 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2318 var projectAttr = xml.getAttribute(attributeName);
|
n@453
|
2319 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
|
n@453
|
2320 switch(typeof projectAttr)
|
n@374
|
2321 {
|
n@453
|
2322 case "number":
|
n@453
|
2323 case "boolean":
|
n@453
|
2324 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2325 break;
|
n@453
|
2326 case "string":
|
n@453
|
2327 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2328 break;
|
n@324
|
2329 }
|
n@324
|
2330 }
|
n@453
|
2331
|
n@374
|
2332 };
|
n@374
|
2333 this.encode = function(root)
|
n@374
|
2334 {
|
n@503
|
2335 var AENode = root.createElement("audioelement");
|
n@503
|
2336 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
|
n@503
|
2337 for (var i=0; i<attributes.length; i++)
|
n@503
|
2338 {
|
n@503
|
2339 var name = attributes[i].getAttribute("name");
|
n@503
|
2340 if (name == undefined) {
|
n@503
|
2341 name = attributes[i].getAttribute("ref");
|
n@503
|
2342 }
|
n@503
|
2343 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
2344 {
|
n@503
|
2345 eval("AENode.setAttribute('"+name+"',this."+name+")");
|
n@503
|
2346 }
|
n@503
|
2347 }
|
n@374
|
2348 return AENode;
|
n@374
|
2349 };
|
n@180
|
2350 };
|
n@180
|
2351 };
|
n@180
|
2352 }
|
n@374
|
2353
|
n@182
|
2354 function Interface(specificationObject) {
|
n@180
|
2355 // This handles the bindings between the interface and the audioEngineContext;
|
n@182
|
2356 this.specification = specificationObject;
|
n@182
|
2357 this.insertPoint = document.getElementById("topLevelBody");
|
n@180
|
2358
|
n@453
|
2359 this.newPage = function(audioHolderObject,store)
|
n@375
|
2360 {
|
n@500
|
2361 audioEngineContext.newTestPage(audioHolderObject,store);
|
n@550
|
2362 interfaceContext.commentBoxes.deleteCommentBoxes();
|
n@375
|
2363 interfaceContext.deleteCommentQuestions();
|
n@453
|
2364 loadTest(audioHolderObject,store);
|
n@375
|
2365 };
|
n@375
|
2366
|
n@182
|
2367 // Bounded by interface!!
|
n@182
|
2368 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
n@182
|
2369 // For example, APE returns the slider position normalised in a <value> tag.
|
n@182
|
2370 this.interfaceObjects = [];
|
n@182
|
2371 this.interfaceObject = function(){};
|
n@182
|
2372
|
n@302
|
2373 this.resizeWindow = function(event)
|
n@302
|
2374 {
|
n@395
|
2375 popup.resize(event);
|
n@302
|
2376 for(var i=0; i<this.commentBoxes.length; i++)
|
n@302
|
2377 {this.commentBoxes[i].resize();}
|
n@302
|
2378 for(var i=0; i<this.commentQuestions.length; i++)
|
n@302
|
2379 {this.commentQuestions[i].resize();}
|
n@302
|
2380 try
|
n@302
|
2381 {
|
n@302
|
2382 resizeWindow(event);
|
n@302
|
2383 }
|
n@302
|
2384 catch(err)
|
n@302
|
2385 {
|
n@302
|
2386 console.log("Warning - Interface does not have Resize option");
|
n@302
|
2387 console.log(err);
|
n@302
|
2388 }
|
n@302
|
2389 };
|
n@302
|
2390
|
n@356
|
2391 this.returnNavigator = function()
|
n@356
|
2392 {
|
n@491
|
2393 var node = storage.document.createElement("navigator");
|
n@491
|
2394 var platform = storage.document.createElement("platform");
|
n@356
|
2395 platform.textContent = navigator.platform;
|
n@491
|
2396 var vendor = storage.document.createElement("vendor");
|
n@356
|
2397 vendor.textContent = navigator.vendor;
|
n@491
|
2398 var userAgent = storage.document.createElement("uagent");
|
n@356
|
2399 userAgent.textContent = navigator.userAgent;
|
n@491
|
2400 var screen = storage.document.createElement("window");
|
n@491
|
2401 screen.setAttribute('innerWidth',window.innerWidth);
|
n@491
|
2402 screen.setAttribute('innerHeight',window.innerHeight);
|
n@356
|
2403 node.appendChild(platform);
|
n@356
|
2404 node.appendChild(vendor);
|
n@356
|
2405 node.appendChild(userAgent);
|
n@491
|
2406 node.appendChild(screen);
|
n@356
|
2407 return node;
|
n@356
|
2408 };
|
n@356
|
2409
|
n@550
|
2410 this.commentBoxes = new function() {
|
n@550
|
2411 this.boxes = [];
|
n@550
|
2412 this.injectPoint = null;
|
n@550
|
2413 this.elementCommentBox = function(audioObject) {
|
n@550
|
2414 var element = audioObject.specification;
|
n@550
|
2415 this.audioObject = audioObject;
|
n@550
|
2416 this.id = audioObject.id;
|
n@550
|
2417 var audioHolderObject = audioObject.specification.parent;
|
n@550
|
2418 // Create document objects to hold the comment boxes
|
n@550
|
2419 this.trackComment = document.createElement('div');
|
n@550
|
2420 this.trackComment.className = 'comment-div';
|
n@550
|
2421 this.trackComment.id = 'comment-div-'+audioObject.id;
|
n@550
|
2422 // Create a string next to each comment asking for a comment
|
n@550
|
2423 this.trackString = document.createElement('span');
|
n@550
|
2424 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
|
n@550
|
2425 // Create the HTML5 comment box 'textarea'
|
n@550
|
2426 this.trackCommentBox = document.createElement('textarea');
|
n@550
|
2427 this.trackCommentBox.rows = '4';
|
n@550
|
2428 this.trackCommentBox.cols = '100';
|
n@550
|
2429 this.trackCommentBox.name = 'trackComment'+audioObject.id;
|
n@550
|
2430 this.trackCommentBox.className = 'trackComment';
|
n@550
|
2431 var br = document.createElement('br');
|
n@550
|
2432 // Add to the holder.
|
n@550
|
2433 this.trackComment.appendChild(this.trackString);
|
n@550
|
2434 this.trackComment.appendChild(br);
|
n@550
|
2435 this.trackComment.appendChild(this.trackCommentBox);
|
n@550
|
2436
|
n@550
|
2437 this.exportXMLDOM = function() {
|
n@550
|
2438 var root = document.createElement('comment');
|
n@550
|
2439 var question = document.createElement('question');
|
n@550
|
2440 question.textContent = this.trackString.textContent;
|
n@550
|
2441 var response = document.createElement('response');
|
n@550
|
2442 response.textContent = this.trackCommentBox.value;
|
n@550
|
2443 console.log("Comment frag-"+this.id+": "+response.textContent);
|
n@550
|
2444 root.appendChild(question);
|
n@550
|
2445 root.appendChild(response);
|
n@550
|
2446 return root;
|
n@550
|
2447 };
|
n@550
|
2448 this.resize = function()
|
n@550
|
2449 {
|
n@550
|
2450 var boxwidth = (window.innerWidth-100)/2;
|
n@550
|
2451 if (boxwidth >= 600)
|
n@550
|
2452 {
|
n@550
|
2453 boxwidth = 600;
|
n@550
|
2454 }
|
n@550
|
2455 else if (boxwidth < 400)
|
n@550
|
2456 {
|
n@550
|
2457 boxwidth = 400;
|
n@550
|
2458 }
|
n@550
|
2459 this.trackComment.style.width = boxwidth+"px";
|
n@550
|
2460 this.trackCommentBox.style.width = boxwidth-6+"px";
|
n@550
|
2461 };
|
n@550
|
2462 this.resize();
|
n@550
|
2463 };
|
n@550
|
2464 this.createCommentBox = function(audioObject) {
|
n@550
|
2465 var node = new this.elementCommentBox(audioObject);
|
n@550
|
2466 this.boxes.push(node);
|
n@550
|
2467 audioObject.commentDOM = node;
|
n@550
|
2468 return node;
|
n@550
|
2469 };
|
n@550
|
2470 this.sortCommentBoxes = function() {
|
n@550
|
2471 this.boxes.sort(function(a,b){return a.id - b.id;});
|
n@550
|
2472 };
|
n@550
|
2473
|
n@550
|
2474 this.showCommentBoxes = function(inject, sort) {
|
n@550
|
2475 this.injectPoint = inject;
|
n@550
|
2476 if (sort) {this.sortCommentBoxes();}
|
n@550
|
2477 for (var box of this.boxes) {
|
n@550
|
2478 inject.appendChild(box.trackComment);
|
n@550
|
2479 }
|
n@550
|
2480 };
|
n@550
|
2481
|
n@550
|
2482 this.deleteCommentBoxes = function() {
|
n@550
|
2483 if (this.injectPoint != null) {
|
n@550
|
2484 for (var box of this.boxes) {
|
n@550
|
2485 this.injectPoint.removeChild(box.trackComment);
|
n@550
|
2486 }
|
n@550
|
2487 this.injectPoint = null;
|
n@550
|
2488 }
|
n@550
|
2489 this.boxes = [];
|
n@550
|
2490 };
|
n@550
|
2491 }
|
n@182
|
2492
|
n@193
|
2493 this.commentQuestions = [];
|
n@193
|
2494
|
n@193
|
2495 this.commentBox = function(commentQuestion) {
|
n@193
|
2496 this.specification = commentQuestion;
|
n@193
|
2497 // Create document objects to hold the comment boxes
|
n@193
|
2498 this.holder = document.createElement('div');
|
n@193
|
2499 this.holder.className = 'comment-div';
|
n@193
|
2500 // Create a string next to each comment asking for a comment
|
n@193
|
2501 this.string = document.createElement('span');
|
n@453
|
2502 this.string.innerHTML = commentQuestion.statement;
|
n@193
|
2503 // Create the HTML5 comment box 'textarea'
|
n@193
|
2504 this.textArea = document.createElement('textarea');
|
n@193
|
2505 this.textArea.rows = '4';
|
n@193
|
2506 this.textArea.cols = '100';
|
n@193
|
2507 this.textArea.className = 'trackComment';
|
n@193
|
2508 var br = document.createElement('br');
|
n@193
|
2509 // Add to the holder.
|
n@193
|
2510 this.holder.appendChild(this.string);
|
n@193
|
2511 this.holder.appendChild(br);
|
n@193
|
2512 this.holder.appendChild(this.textArea);
|
n@193
|
2513
|
n@520
|
2514 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2515 var root = storePoint.parent.document.createElement('comment');
|
n@193
|
2516 root.id = this.specification.id;
|
n@193
|
2517 root.setAttribute('type',this.specification.type);
|
b@254
|
2518 console.log("Question: "+this.string.textContent);
|
b@254
|
2519 console.log("Response: "+root.textContent);
|
n@520
|
2520 var question = storePoint.parent.document.createElement('question');
|
n@520
|
2521 question.textContent = this.string.textContent;
|
n@520
|
2522 var response = storePoint.parent.document.createElement('response');
|
n@520
|
2523 response.textContent = this.textArea.value;
|
n@520
|
2524 root.appendChild(question);
|
n@520
|
2525 root.appendChild(response);
|
n@520
|
2526 storePoint.XMLDOM.appendChild(root);
|
n@193
|
2527 return root;
|
n@193
|
2528 };
|
n@302
|
2529 this.resize = function()
|
n@302
|
2530 {
|
n@302
|
2531 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2532 if (boxwidth >= 600)
|
n@302
|
2533 {
|
n@302
|
2534 boxwidth = 600;
|
n@302
|
2535 }
|
n@302
|
2536 else if (boxwidth < 400)
|
n@302
|
2537 {
|
n@302
|
2538 boxwidth = 400;
|
n@302
|
2539 }
|
n@302
|
2540 this.holder.style.width = boxwidth+"px";
|
n@302
|
2541 this.textArea.style.width = boxwidth-6+"px";
|
n@302
|
2542 };
|
n@302
|
2543 this.resize();
|
n@193
|
2544 };
|
n@193
|
2545
|
n@193
|
2546 this.radioBox = function(commentQuestion) {
|
n@193
|
2547 this.specification = commentQuestion;
|
n@193
|
2548 // Create document objects to hold the comment boxes
|
n@193
|
2549 this.holder = document.createElement('div');
|
n@193
|
2550 this.holder.className = 'comment-div';
|
n@193
|
2551 // Create a string next to each comment asking for a comment
|
n@193
|
2552 this.string = document.createElement('span');
|
n@193
|
2553 this.string.innerHTML = commentQuestion.statement;
|
n@193
|
2554 var br = document.createElement('br');
|
n@193
|
2555 // Add to the holder.
|
n@193
|
2556 this.holder.appendChild(this.string);
|
n@193
|
2557 this.holder.appendChild(br);
|
n@193
|
2558 this.options = [];
|
n@193
|
2559 this.inputs = document.createElement('div');
|
n@193
|
2560 this.span = document.createElement('div');
|
n@193
|
2561 this.inputs.align = 'center';
|
n@193
|
2562 this.inputs.style.marginLeft = '12px';
|
n@193
|
2563 this.span.style.marginLeft = '12px';
|
n@193
|
2564 this.span.align = 'center';
|
n@193
|
2565 this.span.style.marginTop = '15px';
|
n@193
|
2566
|
n@193
|
2567 var optCount = commentQuestion.options.length;
|
n@453
|
2568 for (var optNode of commentQuestion.options)
|
n@193
|
2569 {
|
n@193
|
2570 var div = document.createElement('div');
|
n@301
|
2571 div.style.width = '80px';
|
n@193
|
2572 div.style.float = 'left';
|
n@193
|
2573 var input = document.createElement('input');
|
n@193
|
2574 input.type = 'radio';
|
n@193
|
2575 input.name = commentQuestion.id;
|
n@453
|
2576 input.setAttribute('setvalue',optNode.name);
|
n@193
|
2577 input.className = 'comment-radio';
|
n@193
|
2578 div.appendChild(input);
|
n@193
|
2579 this.inputs.appendChild(div);
|
n@193
|
2580
|
n@193
|
2581
|
n@193
|
2582 div = document.createElement('div');
|
n@301
|
2583 div.style.width = '80px';
|
n@193
|
2584 div.style.float = 'left';
|
n@193
|
2585 div.align = 'center';
|
n@193
|
2586 var span = document.createElement('span');
|
n@453
|
2587 span.textContent = optNode.text;
|
n@193
|
2588 span.className = 'comment-radio-span';
|
n@193
|
2589 div.appendChild(span);
|
n@193
|
2590 this.span.appendChild(div);
|
n@193
|
2591 this.options.push(input);
|
n@193
|
2592 }
|
n@193
|
2593 this.holder.appendChild(this.span);
|
n@193
|
2594 this.holder.appendChild(this.inputs);
|
n@193
|
2595
|
n@520
|
2596 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2597 var root = storePoint.parent.document.createElement('comment');
|
n@193
|
2598 root.id = this.specification.id;
|
n@193
|
2599 root.setAttribute('type',this.specification.type);
|
n@193
|
2600 var question = document.createElement('question');
|
n@193
|
2601 question.textContent = this.string.textContent;
|
n@193
|
2602 var response = document.createElement('response');
|
n@193
|
2603 var i=0;
|
n@193
|
2604 while(this.options[i].checked == false) {
|
n@193
|
2605 i++;
|
n@193
|
2606 if (i >= this.options.length) {
|
n@193
|
2607 break;
|
n@193
|
2608 }
|
n@193
|
2609 }
|
n@193
|
2610 if (i >= this.options.length) {
|
n@193
|
2611 response.textContent = 'null';
|
n@193
|
2612 } else {
|
n@193
|
2613 response.textContent = this.options[i].getAttribute('setvalue');
|
n@193
|
2614 response.setAttribute('number',i);
|
n@193
|
2615 }
|
n@195
|
2616 console.log('Comment: '+question.textContent);
|
n@195
|
2617 console.log('Response: '+response.textContent);
|
n@193
|
2618 root.appendChild(question);
|
n@193
|
2619 root.appendChild(response);
|
n@520
|
2620 storePoint.XMLDOM.appendChild(root);
|
n@193
|
2621 return root;
|
n@193
|
2622 };
|
n@302
|
2623 this.resize = function()
|
n@302
|
2624 {
|
n@302
|
2625 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2626 if (boxwidth >= 600)
|
n@302
|
2627 {
|
n@302
|
2628 boxwidth = 600;
|
n@302
|
2629 }
|
n@302
|
2630 else if (boxwidth < 400)
|
n@302
|
2631 {
|
n@302
|
2632 boxwidth = 400;
|
n@302
|
2633 }
|
n@302
|
2634 this.holder.style.width = boxwidth+"px";
|
n@302
|
2635 var text = this.holder.children[2];
|
n@302
|
2636 var options = this.holder.children[3];
|
n@302
|
2637 var optCount = options.children.length;
|
n@302
|
2638 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
n@302
|
2639 var options = options.firstChild;
|
n@302
|
2640 var text = text.firstChild;
|
n@302
|
2641 options.style.marginRight = spanMargin;
|
n@302
|
2642 options.style.marginLeft = spanMargin;
|
n@302
|
2643 text.style.marginRight = spanMargin;
|
n@302
|
2644 text.style.marginLeft = spanMargin;
|
n@302
|
2645 while(options.nextSibling != undefined)
|
n@302
|
2646 {
|
n@302
|
2647 options = options.nextSibling;
|
n@302
|
2648 text = text.nextSibling;
|
n@302
|
2649 options.style.marginRight = spanMargin;
|
n@302
|
2650 options.style.marginLeft = spanMargin;
|
n@302
|
2651 text.style.marginRight = spanMargin;
|
n@302
|
2652 text.style.marginLeft = spanMargin;
|
n@302
|
2653 }
|
n@302
|
2654 };
|
n@302
|
2655 this.resize();
|
n@193
|
2656 };
|
n@193
|
2657
|
n@195
|
2658 this.checkboxBox = function(commentQuestion) {
|
n@195
|
2659 this.specification = commentQuestion;
|
n@195
|
2660 // Create document objects to hold the comment boxes
|
n@195
|
2661 this.holder = document.createElement('div');
|
n@195
|
2662 this.holder.className = 'comment-div';
|
n@195
|
2663 // Create a string next to each comment asking for a comment
|
n@195
|
2664 this.string = document.createElement('span');
|
n@195
|
2665 this.string.innerHTML = commentQuestion.statement;
|
n@195
|
2666 var br = document.createElement('br');
|
n@195
|
2667 // Add to the holder.
|
n@195
|
2668 this.holder.appendChild(this.string);
|
n@195
|
2669 this.holder.appendChild(br);
|
n@195
|
2670 this.options = [];
|
n@195
|
2671 this.inputs = document.createElement('div');
|
n@195
|
2672 this.span = document.createElement('div');
|
n@195
|
2673 this.inputs.align = 'center';
|
n@195
|
2674 this.inputs.style.marginLeft = '12px';
|
n@195
|
2675 this.span.style.marginLeft = '12px';
|
n@195
|
2676 this.span.align = 'center';
|
n@195
|
2677 this.span.style.marginTop = '15px';
|
n@195
|
2678
|
n@195
|
2679 var optCount = commentQuestion.options.length;
|
n@195
|
2680 for (var i=0; i<optCount; i++)
|
n@195
|
2681 {
|
n@195
|
2682 var div = document.createElement('div');
|
n@301
|
2683 div.style.width = '80px';
|
n@195
|
2684 div.style.float = 'left';
|
n@195
|
2685 var input = document.createElement('input');
|
n@195
|
2686 input.type = 'checkbox';
|
n@195
|
2687 input.name = commentQuestion.id;
|
n@195
|
2688 input.setAttribute('setvalue',commentQuestion.options[i].name);
|
n@195
|
2689 input.className = 'comment-radio';
|
n@195
|
2690 div.appendChild(input);
|
n@195
|
2691 this.inputs.appendChild(div);
|
n@195
|
2692
|
n@195
|
2693
|
n@195
|
2694 div = document.createElement('div');
|
n@301
|
2695 div.style.width = '80px';
|
n@195
|
2696 div.style.float = 'left';
|
n@195
|
2697 div.align = 'center';
|
n@195
|
2698 var span = document.createElement('span');
|
n@195
|
2699 span.textContent = commentQuestion.options[i].text;
|
n@195
|
2700 span.className = 'comment-radio-span';
|
n@195
|
2701 div.appendChild(span);
|
n@195
|
2702 this.span.appendChild(div);
|
n@195
|
2703 this.options.push(input);
|
n@195
|
2704 }
|
n@195
|
2705 this.holder.appendChild(this.span);
|
n@195
|
2706 this.holder.appendChild(this.inputs);
|
n@195
|
2707
|
n@520
|
2708 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2709 var root = storePoint.parent.document.createElement('comment');
|
n@195
|
2710 root.id = this.specification.id;
|
n@195
|
2711 root.setAttribute('type',this.specification.type);
|
n@195
|
2712 var question = document.createElement('question');
|
n@195
|
2713 question.textContent = this.string.textContent;
|
n@195
|
2714 root.appendChild(question);
|
n@195
|
2715 console.log('Comment: '+question.textContent);
|
n@195
|
2716 for (var i=0; i<this.options.length; i++) {
|
n@195
|
2717 var response = document.createElement('response');
|
n@195
|
2718 response.textContent = this.options[i].checked;
|
n@195
|
2719 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
|
n@195
|
2720 root.appendChild(response);
|
n@195
|
2721 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
|
n@195
|
2722 }
|
n@520
|
2723 storePoint.XMLDOM.appendChild(root);
|
n@195
|
2724 return root;
|
n@195
|
2725 };
|
n@302
|
2726 this.resize = function()
|
n@302
|
2727 {
|
n@302
|
2728 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2729 if (boxwidth >= 600)
|
n@302
|
2730 {
|
n@302
|
2731 boxwidth = 600;
|
n@302
|
2732 }
|
n@302
|
2733 else if (boxwidth < 400)
|
n@302
|
2734 {
|
n@302
|
2735 boxwidth = 400;
|
n@302
|
2736 }
|
n@302
|
2737 this.holder.style.width = boxwidth+"px";
|
n@302
|
2738 var text = this.holder.children[2];
|
n@302
|
2739 var options = this.holder.children[3];
|
n@302
|
2740 var optCount = options.children.length;
|
n@302
|
2741 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
n@302
|
2742 var options = options.firstChild;
|
n@302
|
2743 var text = text.firstChild;
|
n@302
|
2744 options.style.marginRight = spanMargin;
|
n@302
|
2745 options.style.marginLeft = spanMargin;
|
n@302
|
2746 text.style.marginRight = spanMargin;
|
n@302
|
2747 text.style.marginLeft = spanMargin;
|
n@302
|
2748 while(options.nextSibling != undefined)
|
n@302
|
2749 {
|
n@302
|
2750 options = options.nextSibling;
|
n@302
|
2751 text = text.nextSibling;
|
n@302
|
2752 options.style.marginRight = spanMargin;
|
n@302
|
2753 options.style.marginLeft = spanMargin;
|
n@302
|
2754 text.style.marginRight = spanMargin;
|
n@302
|
2755 text.style.marginLeft = spanMargin;
|
n@302
|
2756 }
|
n@302
|
2757 };
|
n@302
|
2758 this.resize();
|
n@195
|
2759 };
|
nicholas@211
|
2760
|
n@193
|
2761 this.createCommentQuestion = function(element) {
|
n@193
|
2762 var node;
|
n@453
|
2763 if (element.type == 'question') {
|
n@193
|
2764 node = new this.commentBox(element);
|
n@193
|
2765 } else if (element.type == 'radio') {
|
n@193
|
2766 node = new this.radioBox(element);
|
n@195
|
2767 } else if (element.type == 'checkbox') {
|
n@195
|
2768 node = new this.checkboxBox(element);
|
n@193
|
2769 }
|
n@193
|
2770 this.commentQuestions.push(node);
|
n@193
|
2771 return node;
|
n@193
|
2772 };
|
n@201
|
2773
|
nicholas@237
|
2774 this.deleteCommentQuestions = function()
|
nicholas@237
|
2775 {
|
nicholas@237
|
2776 this.commentQuestions = [];
|
nicholas@237
|
2777 };
|
nicholas@237
|
2778
|
n@201
|
2779 this.playhead = new function()
|
n@201
|
2780 {
|
n@201
|
2781 this.object = document.createElement('div');
|
n@201
|
2782 this.object.className = 'playhead';
|
n@201
|
2783 this.object.align = 'left';
|
n@201
|
2784 var curTime = document.createElement('div');
|
n@201
|
2785 curTime.style.width = '50px';
|
n@201
|
2786 this.curTimeSpan = document.createElement('span');
|
n@201
|
2787 this.curTimeSpan.textContent = '00:00';
|
n@201
|
2788 curTime.appendChild(this.curTimeSpan);
|
n@201
|
2789 this.object.appendChild(curTime);
|
n@201
|
2790 this.scrubberTrack = document.createElement('div');
|
n@201
|
2791 this.scrubberTrack.className = 'playhead-scrub-track';
|
n@201
|
2792
|
n@201
|
2793 this.scrubberHead = document.createElement('div');
|
n@201
|
2794 this.scrubberHead.id = 'playhead-scrubber';
|
n@201
|
2795 this.scrubberTrack.appendChild(this.scrubberHead);
|
n@201
|
2796 this.object.appendChild(this.scrubberTrack);
|
n@201
|
2797
|
n@201
|
2798 this.timePerPixel = 0;
|
n@201
|
2799 this.maxTime = 0;
|
n@201
|
2800
|
n@204
|
2801 this.playbackObject;
|
n@204
|
2802
|
n@204
|
2803 this.setTimePerPixel = function(audioObject) {
|
n@201
|
2804 //maxTime must be in seconds
|
n@204
|
2805 this.playbackObject = audioObject;
|
n@379
|
2806 this.maxTime = audioObject.buffer.buffer.duration;
|
n@201
|
2807 var width = 490; //500 - 10, 5 each side of the tracker head
|
n@204
|
2808 this.timePerPixel = this.maxTime/490;
|
n@204
|
2809 if (this.maxTime < 60) {
|
n@201
|
2810 this.curTimeSpan.textContent = '0.00';
|
n@201
|
2811 } else {
|
n@201
|
2812 this.curTimeSpan.textContent = '00:00';
|
n@201
|
2813 }
|
n@201
|
2814 };
|
n@201
|
2815
|
n@204
|
2816 this.update = function() {
|
n@201
|
2817 // Update the playhead position, startPlay must be called
|
n@201
|
2818 if (this.timePerPixel > 0) {
|
n@204
|
2819 var time = this.playbackObject.getCurrentPosition();
|
n@498
|
2820 if (time > 0 && time < this.maxTime) {
|
nicholas@267
|
2821 var width = 490;
|
nicholas@267
|
2822 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@267
|
2823 this.scrubberHead.style.left = pix+'px';
|
nicholas@267
|
2824 if (this.maxTime > 60.0) {
|
nicholas@267
|
2825 var secs = time%60;
|
nicholas@267
|
2826 var mins = Math.floor((time-secs)/60);
|
nicholas@267
|
2827 secs = secs.toString();
|
nicholas@267
|
2828 secs = secs.substr(0,2);
|
nicholas@267
|
2829 mins = mins.toString();
|
nicholas@267
|
2830 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@267
|
2831 } else {
|
nicholas@267
|
2832 time = time.toString();
|
nicholas@267
|
2833 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@267
|
2834 }
|
n@201
|
2835 } else {
|
nicholas@267
|
2836 this.scrubberHead.style.left = '0px';
|
nicholas@267
|
2837 if (this.maxTime < 60) {
|
nicholas@267
|
2838 this.curTimeSpan.textContent = '0.00';
|
nicholas@267
|
2839 } else {
|
nicholas@267
|
2840 this.curTimeSpan.textContent = '00:00';
|
nicholas@267
|
2841 }
|
n@201
|
2842 }
|
n@201
|
2843 }
|
n@201
|
2844 };
|
n@204
|
2845
|
n@204
|
2846 this.interval = undefined;
|
n@204
|
2847
|
n@204
|
2848 this.start = function() {
|
n@204
|
2849 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@267
|
2850 if (this.maxTime < 60) {
|
nicholas@267
|
2851 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@267
|
2852 } else {
|
nicholas@267
|
2853 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@267
|
2854 }
|
n@204
|
2855 }
|
n@204
|
2856 };
|
n@204
|
2857 this.stop = function() {
|
n@204
|
2858 clearInterval(this.interval);
|
n@204
|
2859 this.interval = undefined;
|
n@527
|
2860 this.scrubberHead.style.left = '0px';
|
nicholas@267
|
2861 if (this.maxTime < 60) {
|
nicholas@267
|
2862 this.curTimeSpan.textContent = '0.00';
|
nicholas@267
|
2863 } else {
|
nicholas@267
|
2864 this.curTimeSpan.textContent = '00:00';
|
nicholas@267
|
2865 }
|
n@204
|
2866 };
|
n@201
|
2867 };
|
n@483
|
2868
|
n@483
|
2869 this.volume = new function()
|
n@483
|
2870 {
|
n@483
|
2871 // An in-built volume module which can be viewed on page
|
n@483
|
2872 // Includes trackers on page-by-page data
|
n@483
|
2873 // Volume does NOT reset to 0dB on each page load
|
n@483
|
2874 this.valueLin = 1.0;
|
n@483
|
2875 this.valueDB = 0.0;
|
n@483
|
2876 this.object = document.createElement('div');
|
n@483
|
2877 this.object.id = 'master-volume-holder';
|
n@483
|
2878 this.slider = document.createElement('input');
|
n@483
|
2879 this.slider.id = 'master-volume-control';
|
n@483
|
2880 this.slider.type = 'range';
|
n@483
|
2881 this.valueText = document.createElement('span');
|
n@483
|
2882 this.valueText.id = 'master-volume-feedback';
|
n@483
|
2883 this.valueText.textContent = '0dB';
|
n@483
|
2884
|
n@483
|
2885 this.slider.min = -60;
|
n@483
|
2886 this.slider.max = 12;
|
n@483
|
2887 this.slider.value = 0;
|
n@483
|
2888 this.slider.step = 1;
|
n@483
|
2889 this.slider.onmousemove = function(event)
|
n@483
|
2890 {
|
n@483
|
2891 interfaceContext.volume.valueDB = event.currentTarget.value;
|
n@483
|
2892 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
n@483
|
2893 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
n@483
|
2894 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
n@483
|
2895 }
|
n@483
|
2896 this.slider.onmouseup = function(event)
|
n@483
|
2897 {
|
n@526
|
2898 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
n@483
|
2899 if (storePoint.length == 0)
|
n@483
|
2900 {
|
n@483
|
2901 storePoint = storage.document.createElement('metricresult');
|
n@483
|
2902 storePoint.setAttribute('name','volumeTracker');
|
n@526
|
2903 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
n@483
|
2904 }
|
n@483
|
2905 else {
|
n@483
|
2906 storePoint = storePoint[0];
|
n@483
|
2907 }
|
n@483
|
2908 var node = storage.document.createElement('movement');
|
n@483
|
2909 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
n@483
|
2910 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
n@483
|
2911 node.setAttribute('format','dBFS');
|
n@483
|
2912 storePoint.appendChild(node);
|
n@483
|
2913 }
|
n@483
|
2914
|
n@484
|
2915 var title = document.createElement('div');
|
n@484
|
2916 title.innerHTML = '<span>Master Volume Control</span>';
|
n@484
|
2917 title.style.fontSize = '0.75em';
|
n@484
|
2918 title.style.width = "100%";
|
n@484
|
2919 title.align = 'center';
|
n@484
|
2920 this.object.appendChild(title);
|
n@484
|
2921
|
n@483
|
2922 this.object.appendChild(this.slider);
|
n@483
|
2923 this.object.appendChild(this.valueText);
|
n@483
|
2924 }
|
nicholas@235
|
2925 // Global Checkers
|
nicholas@235
|
2926 // These functions will help enforce the checkers
|
nicholas@235
|
2927 this.checkHiddenAnchor = function()
|
nicholas@235
|
2928 {
|
n@453
|
2929 for (var ao of audioEngineContext.audioObjects)
|
nicholas@235
|
2930 {
|
n@453
|
2931 if (ao.specification.type == "anchor")
|
nicholas@235
|
2932 {
|
n@454
|
2933 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
n@453
|
2934 // Anchor is not set below
|
n@453
|
2935 console.log('Anchor node not below marker value');
|
n@453
|
2936 alert('Please keep listening');
|
n@498
|
2937 this.storeErrorNode('Anchor node not below marker value');
|
n@453
|
2938 return false;
|
n@453
|
2939 }
|
nicholas@235
|
2940 }
|
nicholas@235
|
2941 }
|
nicholas@235
|
2942 return true;
|
nicholas@235
|
2943 };
|
nicholas@235
|
2944
|
nicholas@235
|
2945 this.checkHiddenReference = function()
|
nicholas@235
|
2946 {
|
n@453
|
2947 for (var ao of audioEngineContext.audioObjects)
|
nicholas@235
|
2948 {
|
n@453
|
2949 if (ao.specification.type == "reference")
|
nicholas@235
|
2950 {
|
n@454
|
2951 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
n@453
|
2952 // Anchor is not set below
|
n@498
|
2953 console.log('Reference node not above marker value');
|
n@498
|
2954 this.storeErrorNode('Reference node not above marker value');
|
n@453
|
2955 alert('Please keep listening');
|
n@453
|
2956 return false;
|
n@453
|
2957 }
|
nicholas@235
|
2958 }
|
nicholas@235
|
2959 }
|
nicholas@235
|
2960 return true;
|
nicholas@235
|
2961 };
|
n@366
|
2962
|
n@366
|
2963 this.checkFragmentsFullyPlayed = function ()
|
n@366
|
2964 {
|
n@366
|
2965 // Checks the entire file has been played back
|
n@366
|
2966 // NOTE ! This will return true IF playback is Looped!!!
|
n@366
|
2967 if (audioEngineContext.loopPlayback)
|
n@366
|
2968 {
|
n@366
|
2969 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
n@366
|
2970 return true;
|
n@366
|
2971 }
|
n@366
|
2972 var check_pass = true;
|
n@366
|
2973 var error_obj = [];
|
n@366
|
2974 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
n@366
|
2975 {
|
n@366
|
2976 var object = audioEngineContext.audioObjects[i];
|
nicholas@415
|
2977 var time = object.buffer.buffer.duration;
|
n@366
|
2978 var metric = object.metric;
|
n@366
|
2979 var passed = false;
|
n@366
|
2980 for (var j=0; j<metric.listenTracker.length; j++)
|
n@366
|
2981 {
|
n@366
|
2982 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
n@366
|
2983 var start_time = Number(bt[0].getAttribute('start'));
|
n@366
|
2984 var stop_time = Number(bt[0].getAttribute('stop'));
|
n@366
|
2985 var delta = stop_time - start_time;
|
n@366
|
2986 if (delta >= time)
|
n@366
|
2987 {
|
n@366
|
2988 passed = true;
|
n@366
|
2989 break;
|
n@366
|
2990 }
|
n@366
|
2991 }
|
n@366
|
2992 if (passed == false)
|
n@366
|
2993 {
|
n@366
|
2994 check_pass = false;
|
n@469
|
2995 console.log("Continue listening to track-"+audioEngineContext.audioObjects.interfaceDOM.getPresentedId());
|
n@469
|
2996 error_obj.push(audioEngineContext.audioObjects.interfaceDOM.getPresentedId());
|
n@366
|
2997 }
|
n@366
|
2998 }
|
n@366
|
2999 if (check_pass == false)
|
n@366
|
3000 {
|
nicholas@415
|
3001 var str_start = "You have not completely listened to fragments ";
|
n@366
|
3002 for (var i=0; i<error_obj.length; i++)
|
n@366
|
3003 {
|
n@366
|
3004 str_start += error_obj[i];
|
n@366
|
3005 if (i != error_obj.length-1)
|
n@366
|
3006 {
|
n@366
|
3007 str_start += ', ';
|
n@366
|
3008 }
|
n@366
|
3009 }
|
n@366
|
3010 str_start += ". Please keep listening";
|
n@366
|
3011 console.log("[ALERT]: "+str_start);
|
n@498
|
3012 this.storeErrorNode("[ALERT]: "+str_start);
|
n@366
|
3013 alert(str_start);
|
n@366
|
3014 }
|
n@366
|
3015 };
|
nicholas@421
|
3016 this.checkAllMoved = function()
|
nicholas@421
|
3017 {
|
nicholas@421
|
3018 var str = "You have not moved ";
|
nicholas@421
|
3019 var failed = [];
|
n@469
|
3020 for (var ao of audioEngineContext.audioObjects)
|
nicholas@421
|
3021 {
|
n@469
|
3022 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@421
|
3023 {
|
n@469
|
3024 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@421
|
3025 }
|
nicholas@421
|
3026 }
|
nicholas@421
|
3027 if (failed.length == 0)
|
nicholas@421
|
3028 {
|
nicholas@421
|
3029 return true;
|
nicholas@421
|
3030 } else if (failed.length == 1)
|
nicholas@421
|
3031 {
|
nicholas@421
|
3032 str += 'track '+failed[0];
|
nicholas@421
|
3033 } else {
|
nicholas@421
|
3034 str += 'tracks ';
|
nicholas@421
|
3035 for (var i=0; i<failed.length-1; i++)
|
nicholas@421
|
3036 {
|
nicholas@421
|
3037 str += failed[i]+', ';
|
nicholas@421
|
3038 }
|
nicholas@421
|
3039 str += 'and '+failed[i];
|
nicholas@421
|
3040 }
|
nicholas@421
|
3041 str +='.';
|
nicholas@421
|
3042 alert(str);
|
nicholas@421
|
3043 console.log(str);
|
n@498
|
3044 this.storeErrorNode(str);
|
nicholas@421
|
3045 return false;
|
nicholas@421
|
3046 };
|
nicholas@421
|
3047 this.checkAllPlayed = function()
|
nicholas@421
|
3048 {
|
nicholas@421
|
3049 var str = "You have not played ";
|
nicholas@421
|
3050 var failed = [];
|
n@469
|
3051 for (var ao of audioEngineContext.audioObjects)
|
nicholas@421
|
3052 {
|
n@469
|
3053 if(ao.metric.wasListenedTo == false)
|
nicholas@421
|
3054 {
|
n@469
|
3055 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@421
|
3056 }
|
nicholas@421
|
3057 }
|
nicholas@421
|
3058 if (failed.length == 0)
|
nicholas@421
|
3059 {
|
nicholas@421
|
3060 return true;
|
nicholas@421
|
3061 } else if (failed.length == 1)
|
nicholas@421
|
3062 {
|
nicholas@421
|
3063 str += 'track '+failed[0];
|
nicholas@421
|
3064 } else {
|
nicholas@421
|
3065 str += 'tracks ';
|
nicholas@421
|
3066 for (var i=0; i<failed.length-1; i++)
|
nicholas@421
|
3067 {
|
nicholas@421
|
3068 str += failed[i]+', ';
|
nicholas@421
|
3069 }
|
nicholas@421
|
3070 str += 'and '+failed[i];
|
nicholas@421
|
3071 }
|
nicholas@421
|
3072 str +='.';
|
nicholas@421
|
3073 alert(str);
|
nicholas@421
|
3074 console.log(str);
|
n@498
|
3075 this.storeErrorNode(str);
|
nicholas@421
|
3076 return false;
|
nicholas@421
|
3077 };
|
n@498
|
3078
|
n@498
|
3079 this.storeErrorNode = function(errorMessage)
|
n@498
|
3080 {
|
n@498
|
3081 var time = audioEngineContext.timer.getTestTime();
|
n@498
|
3082 var node = storage.document.createElement('error');
|
n@498
|
3083 node.setAttribute('time',time);
|
n@498
|
3084 node.textContent = errorMessage;
|
n@498
|
3085 testState.currentStore.XMLDOM.appendChild(node);
|
n@498
|
3086 };
|
n@453
|
3087 }
|
n@453
|
3088
|
n@453
|
3089 function Storage()
|
n@453
|
3090 {
|
n@453
|
3091 // Holds results in XML format until ready for collection
|
n@453
|
3092 this.globalPreTest = null;
|
n@453
|
3093 this.globalPostTest = null;
|
n@453
|
3094 this.testPages = [];
|
n@453
|
3095 this.document = document.implementation.createDocument(null,"waetresult");
|
n@551
|
3096 this.root = this.document.childNodes[0];
|
n@453
|
3097 this.state = 0;
|
n@453
|
3098
|
n@453
|
3099 this.initialise = function()
|
n@453
|
3100 {
|
n@471
|
3101 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
n@471
|
3102 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
n@453
|
3103 };
|
n@453
|
3104
|
n@453
|
3105 this.createTestPageStore = function(specification)
|
n@453
|
3106 {
|
n@453
|
3107 var store = new this.pageNode(this,specification);
|
n@453
|
3108 this.testPages.push(store);
|
n@453
|
3109 return this.testPages[this.testPages.length-1];
|
n@453
|
3110 };
|
n@453
|
3111
|
n@453
|
3112 this.surveyNode = function(parent,root,specification)
|
n@453
|
3113 {
|
n@453
|
3114 this.specification = specification;
|
n@453
|
3115 this.parent = parent;
|
n@453
|
3116 this.XMLDOM = this.parent.document.createElement('survey');
|
n@453
|
3117 this.XMLDOM.setAttribute('location',this.specification.location);
|
n@453
|
3118 for (var optNode of this.specification.options)
|
n@453
|
3119 {
|
n@453
|
3120 if (optNode.type != 'statement')
|
n@453
|
3121 {
|
n@453
|
3122 var node = this.parent.document.createElement('surveyresult');
|
n@453
|
3123 node.id = optNode.id;
|
n@453
|
3124 node.setAttribute('type',optNode.type);
|
n@453
|
3125 this.XMLDOM.appendChild(node);
|
n@453
|
3126 }
|
n@453
|
3127 }
|
n@453
|
3128 root.appendChild(this.XMLDOM);
|
n@453
|
3129
|
n@453
|
3130 this.postResult = function(node)
|
n@453
|
3131 {
|
n@453
|
3132 // From popup: node is the popupOption node containing both spec. and results
|
n@453
|
3133 // ID is the position
|
n@453
|
3134 if (node.specification.type == 'statement'){return;}
|
n@453
|
3135 var surveyresult = this.parent.document.getElementById(node.specification.id);
|
n@453
|
3136 switch(node.specification.type)
|
n@453
|
3137 {
|
n@453
|
3138 case "number":
|
n@453
|
3139 case "question":
|
n@453
|
3140 var child = this.parent.document.createElement('response');
|
n@453
|
3141 child.textContent = node.response;
|
n@453
|
3142 surveyresult.appendChild(child);
|
n@453
|
3143 break;
|
n@453
|
3144 case "radio":
|
n@453
|
3145 var child = this.parent.document.createElement('response');
|
n@453
|
3146 child.setAttribute('name',node.response.name);
|
n@453
|
3147 child.textContent = node.response.text;
|
n@453
|
3148 surveyresult.appendChild(child);
|
n@453
|
3149 break;
|
n@453
|
3150 case "checkbox":
|
n@453
|
3151 for (var i=0; i<node.response.length; i++)
|
n@453
|
3152 {
|
n@453
|
3153 var checkNode = this.parent.document.createElement('response');
|
n@476
|
3154 checkNode.setAttribute('name',node.response[i].name);
|
n@476
|
3155 checkNode.setAttribute('checked',node.response[i].checked);
|
n@455
|
3156 surveyresult.appendChild(checkNode);
|
n@453
|
3157 }
|
n@453
|
3158 break;
|
n@453
|
3159 }
|
n@453
|
3160 };
|
n@453
|
3161 };
|
n@453
|
3162
|
n@453
|
3163 this.pageNode = function(parent,specification)
|
n@453
|
3164 {
|
n@453
|
3165 // Create one store per test page
|
n@453
|
3166 this.specification = specification;
|
n@453
|
3167 this.parent = parent;
|
n@453
|
3168 this.XMLDOM = this.parent.document.createElement('page');
|
n@453
|
3169 this.XMLDOM.setAttribute('id',specification.id);
|
n@453
|
3170 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
n@474
|
3171 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
n@474
|
3172 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
n@453
|
3173
|
n@453
|
3174 // Add any page metrics
|
n@453
|
3175 var page_metric = this.parent.document.createElement('metric');
|
n@453
|
3176 this.XMLDOM.appendChild(page_metric);
|
n@453
|
3177
|
n@453
|
3178 // Add the audioelement
|
n@453
|
3179 for (var element of this.specification.audioElements)
|
n@453
|
3180 {
|
n@453
|
3181 var aeNode = this.parent.document.createElement('audioelement');
|
n@453
|
3182 aeNode.id = element.id;
|
n@453
|
3183 aeNode.setAttribute('type',element.type);
|
n@453
|
3184 aeNode.setAttribute('url', element.url);
|
n@453
|
3185 aeNode.setAttribute('gain', element.gain);
|
n@453
|
3186 if (element.type == 'anchor' || element.type == 'reference')
|
n@453
|
3187 {
|
n@453
|
3188 if (element.marker > 0)
|
n@453
|
3189 {
|
n@453
|
3190 aeNode.setAttribute('marker',element.marker);
|
n@453
|
3191 }
|
n@453
|
3192 }
|
n@453
|
3193 var ae_metric = this.parent.document.createElement('metric');
|
n@453
|
3194 aeNode.appendChild(ae_metric);
|
n@453
|
3195 this.XMLDOM.appendChild(aeNode);
|
n@453
|
3196 }
|
n@453
|
3197
|
n@453
|
3198 this.parent.root.appendChild(this.XMLDOM);
|
n@453
|
3199 };
|
n@453
|
3200 this.finish = function()
|
n@453
|
3201 {
|
n@453
|
3202 if (this.state == 0)
|
n@453
|
3203 {
|
n@453
|
3204 var projectDocument = specification.projectXML;
|
n@453
|
3205 projectDocument.setAttribute('file-name',url);
|
n@453
|
3206 this.root.appendChild(projectDocument);
|
n@453
|
3207 this.root.appendChild(returnDateNode());
|
n@453
|
3208 this.root.appendChild(interfaceContext.returnNavigator());
|
n@453
|
3209 }
|
n@453
|
3210 this.state = 1;
|
n@453
|
3211 return this.root;
|
n@453
|
3212 };
|
n@453
|
3213 }
|