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