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