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