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