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