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