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 }
|
n@496
|
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');
|
nicholas@387
|
1478 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
|
nicholas@387
|
1479 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
|
nicholas@387
|
1480 file.setAttribute('sampleCount',this.buffer.buffer.length);
|
nicholas@387
|
1481 file.setAttribute('duration',this.buffer.buffer.duration);
|
n@453
|
1482 this.storeDOM.appendChild(file);
|
n@453
|
1483 if (this.specification.type != 'outside-reference') {
|
n@383
|
1484 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
|
n@469
|
1485 if (interfaceXML != null)
|
n@469
|
1486 {
|
n@469
|
1487 if (interfaceXML.length == undefined) {
|
n@469
|
1488 this.storeDOM.appendChild(interfaceXML);
|
n@469
|
1489 } else {
|
n@469
|
1490 for (var i=0; i<interfaceXML.length; i++)
|
n@469
|
1491 {
|
n@469
|
1492 this.storeDOM.appendChild(interfaceXML[i]);
|
n@469
|
1493 }
|
n@383
|
1494 }
|
n@383
|
1495 }
|
n@459
|
1496 if (this.commentDOM != null) {
|
n@459
|
1497 this.storeDOM.appendChild(this.commentDOM.exportXMLDOM(this));
|
n@459
|
1498 }
|
nicholas@236
|
1499 }
|
n@453
|
1500 var nodes = this.metric.exportXMLDOM();
|
n@453
|
1501 var mroot = this.storeDOM.getElementsByTagName('metric')[0];
|
n@453
|
1502 for (var i=0; i<nodes.length; i++)
|
n@453
|
1503 {
|
n@453
|
1504 mroot.appendChild(nodes[i]);
|
n@453
|
1505 }
|
n@183
|
1506 };
|
n@49
|
1507 }
|
n@49
|
1508
|
n@49
|
1509 function timer()
|
n@49
|
1510 {
|
n@49
|
1511 /* Timer object used in audioEngine to keep track of session timings
|
n@49
|
1512 * Uses the timer of the web audio API, so sample resolution
|
n@49
|
1513 */
|
n@49
|
1514 this.testStarted = false;
|
n@49
|
1515 this.testStartTime = 0;
|
n@49
|
1516 this.testDuration = 0;
|
n@49
|
1517 this.minimumTestTime = 0; // No minimum test time
|
n@49
|
1518 this.startTest = function()
|
n@49
|
1519 {
|
n@49
|
1520 if (this.testStarted == false)
|
n@49
|
1521 {
|
n@49
|
1522 this.testStartTime = audioContext.currentTime;
|
n@49
|
1523 this.testStarted = true;
|
n@49
|
1524 this.updateTestTime();
|
n@52
|
1525 audioEngineContext.metric.initialiseTest();
|
n@49
|
1526 }
|
n@49
|
1527 };
|
n@49
|
1528 this.stopTest = function()
|
n@49
|
1529 {
|
n@49
|
1530 if (this.testStarted)
|
n@49
|
1531 {
|
n@49
|
1532 this.testDuration = this.getTestTime();
|
n@49
|
1533 this.testStarted = false;
|
n@49
|
1534 } else {
|
n@49
|
1535 console.log('ERR: Test tried to end before beginning');
|
n@49
|
1536 }
|
n@49
|
1537 };
|
n@49
|
1538 this.updateTestTime = function()
|
n@49
|
1539 {
|
n@49
|
1540 if (this.testStarted)
|
n@49
|
1541 {
|
n@49
|
1542 this.testDuration = audioContext.currentTime - this.testStartTime;
|
n@49
|
1543 }
|
n@49
|
1544 };
|
n@49
|
1545 this.getTestTime = function()
|
n@49
|
1546 {
|
n@49
|
1547 this.updateTestTime();
|
n@49
|
1548 return this.testDuration;
|
n@49
|
1549 };
|
n@49
|
1550 }
|
n@49
|
1551
|
n@377
|
1552 function sessionMetrics(engine,specification)
|
n@49
|
1553 {
|
n@49
|
1554 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
|
n@49
|
1555 */
|
n@49
|
1556 this.engine = engine;
|
n@49
|
1557 this.lastClicked = -1;
|
n@49
|
1558 this.data = -1;
|
n@113
|
1559 this.reset = function() {
|
n@113
|
1560 this.lastClicked = -1;
|
n@113
|
1561 this.data = -1;
|
n@113
|
1562 };
|
n@377
|
1563
|
n@377
|
1564 this.enableElementInitialPosition = false;
|
n@377
|
1565 this.enableElementListenTracker = false;
|
n@377
|
1566 this.enableElementTimer = false;
|
n@377
|
1567 this.enableElementTracker = false;
|
n@377
|
1568 this.enableFlagListenedTo = false;
|
n@377
|
1569 this.enableFlagMoved = false;
|
n@377
|
1570 this.enableTestTimer = false;
|
n@377
|
1571 // Obtain the metrics enabled
|
n@453
|
1572 for (var i=0; i<specification.metrics.enabled.length; i++)
|
n@377
|
1573 {
|
n@453
|
1574 var node = specification.metrics.enabled[i];
|
n@453
|
1575 switch(node)
|
n@377
|
1576 {
|
n@377
|
1577 case 'testTimer':
|
n@377
|
1578 this.enableTestTimer = true;
|
n@377
|
1579 break;
|
n@377
|
1580 case 'elementTimer':
|
n@377
|
1581 this.enableElementTimer = true;
|
n@377
|
1582 break;
|
n@377
|
1583 case 'elementTracker':
|
n@377
|
1584 this.enableElementTracker = true;
|
n@377
|
1585 break;
|
n@377
|
1586 case 'elementListenTracker':
|
n@377
|
1587 this.enableElementListenTracker = true;
|
n@377
|
1588 break;
|
n@377
|
1589 case 'elementInitialPosition':
|
n@377
|
1590 this.enableElementInitialPosition = true;
|
n@377
|
1591 break;
|
n@377
|
1592 case 'elementFlagListenedTo':
|
n@377
|
1593 this.enableFlagListenedTo = true;
|
n@377
|
1594 break;
|
n@377
|
1595 case 'elementFlagMoved':
|
n@377
|
1596 this.enableFlagMoved = true;
|
n@377
|
1597 break;
|
n@377
|
1598 case 'elementFlagComments':
|
n@377
|
1599 this.enableFlagComments = true;
|
n@377
|
1600 break;
|
n@377
|
1601 }
|
n@377
|
1602 }
|
n@52
|
1603 this.initialiseTest = function(){};
|
n@49
|
1604 }
|
n@49
|
1605
|
n@139
|
1606 function metricTracker(caller)
|
n@49
|
1607 {
|
n@49
|
1608 /* Custom object to track and collect metric data
|
n@49
|
1609 * Used only inside the audioObjects object.
|
n@49
|
1610 */
|
n@49
|
1611
|
n@49
|
1612 this.listenedTimer = 0;
|
n@49
|
1613 this.listenStart = 0;
|
nicholas@110
|
1614 this.listenHold = false;
|
n@51
|
1615 this.initialPosition = -1;
|
n@49
|
1616 this.movementTracker = [];
|
n@164
|
1617 this.listenTracker =[];
|
n@49
|
1618 this.wasListenedTo = false;
|
n@49
|
1619 this.wasMoved = false;
|
n@49
|
1620 this.hasComments = false;
|
n@139
|
1621 this.parent = caller;
|
n@49
|
1622
|
n@453
|
1623 this.initialise = function(position)
|
n@49
|
1624 {
|
n@51
|
1625 if (this.initialPosition == -1) {
|
n@51
|
1626 this.initialPosition = position;
|
n@454
|
1627 this.moved(0,position);
|
n@51
|
1628 }
|
n@49
|
1629 };
|
n@49
|
1630
|
n@49
|
1631 this.moved = function(time,position)
|
n@49
|
1632 {
|
n@454
|
1633 if (time > 0) {this.wasMoved = true;}
|
n@49
|
1634 this.movementTracker[this.movementTracker.length] = [time, position];
|
n@49
|
1635 };
|
n@49
|
1636
|
nicholas@132
|
1637 this.startListening = function(time)
|
n@49
|
1638 {
|
nicholas@110
|
1639 if (this.listenHold == false)
|
n@49
|
1640 {
|
n@49
|
1641 this.wasListenedTo = true;
|
n@49
|
1642 this.listenStart = time;
|
nicholas@110
|
1643 this.listenHold = true;
|
n@164
|
1644
|
n@164
|
1645 var evnt = document.createElement('event');
|
n@164
|
1646 var testTime = document.createElement('testTime');
|
n@164
|
1647 testTime.setAttribute('start',time);
|
n@164
|
1648 var bufferTime = document.createElement('bufferTime');
|
n@164
|
1649 bufferTime.setAttribute('start',this.parent.getCurrentPosition());
|
n@164
|
1650 evnt.appendChild(testTime);
|
n@164
|
1651 evnt.appendChild(bufferTime);
|
n@164
|
1652 this.listenTracker.push(evnt);
|
n@164
|
1653
|
n@139
|
1654 console.log('slider ' + this.parent.id + ' played (' + time + ')'); // DEBUG/SAFETY: show played slider id
|
n@139
|
1655 }
|
n@139
|
1656 };
|
nicholas@132
|
1657
|
n@203
|
1658 this.stopListening = function(time,bufferStopTime)
|
nicholas@132
|
1659 {
|
nicholas@132
|
1660 if (this.listenHold == true)
|
nicholas@132
|
1661 {
|
n@164
|
1662 var diff = time - this.listenStart;
|
n@164
|
1663 this.listenedTimer += (diff);
|
n@49
|
1664 this.listenStart = 0;
|
nicholas@110
|
1665 this.listenHold = false;
|
n@164
|
1666
|
n@164
|
1667 var evnt = this.listenTracker[this.listenTracker.length-1];
|
n@164
|
1668 var testTime = evnt.getElementsByTagName('testTime')[0];
|
n@164
|
1669 var bufferTime = evnt.getElementsByTagName('bufferTime')[0];
|
n@164
|
1670 testTime.setAttribute('stop',time);
|
n@203
|
1671 if (bufferStopTime == undefined) {
|
n@203
|
1672 bufferTime.setAttribute('stop',this.parent.getCurrentPosition());
|
n@203
|
1673 } else {
|
n@203
|
1674 bufferTime.setAttribute('stop',bufferStopTime);
|
n@203
|
1675 }
|
n@164
|
1676 console.log('slider ' + this.parent.id + ' played for (' + diff + ')'); // DEBUG/SAFETY: show played slider id
|
n@49
|
1677 }
|
n@49
|
1678 };
|
n@177
|
1679
|
n@177
|
1680 this.exportXMLDOM = function() {
|
n@453
|
1681 var storeDOM = [];
|
n@177
|
1682 if (audioEngineContext.metric.enableElementTimer) {
|
n@453
|
1683 var mElementTimer = storage.document.createElement('metricresult');
|
n@177
|
1684 mElementTimer.setAttribute('name','enableElementTimer');
|
n@177
|
1685 mElementTimer.textContent = this.listenedTimer;
|
n@453
|
1686 storeDOM.push(mElementTimer);
|
n@177
|
1687 }
|
n@177
|
1688 if (audioEngineContext.metric.enableElementTracker) {
|
n@453
|
1689 var elementTrackerFull = storage.document.createElement('metricResult');
|
n@177
|
1690 elementTrackerFull.setAttribute('name','elementTrackerFull');
|
n@177
|
1691 for (var k=0; k<this.movementTracker.length; k++)
|
n@177
|
1692 {
|
n@575
|
1693 var timePos = storage.document.createElement('movement');
|
n@575
|
1694 timePos.setAttribute("time",this.movementTracker[k][0]);
|
n@575
|
1695 timePos.setAttribute("value",this.movementTracker[k][1]);
|
n@177
|
1696 elementTrackerFull.appendChild(timePos);
|
n@177
|
1697 }
|
n@453
|
1698 storeDOM.push(elementTrackerFull);
|
n@177
|
1699 }
|
n@177
|
1700 if (audioEngineContext.metric.enableElementListenTracker) {
|
n@453
|
1701 var elementListenTracker = storage.document.createElement('metricResult');
|
n@177
|
1702 elementListenTracker.setAttribute('name','elementListenTracker');
|
n@177
|
1703 for (var k=0; k<this.listenTracker.length; k++) {
|
n@177
|
1704 elementListenTracker.appendChild(this.listenTracker[k]);
|
n@177
|
1705 }
|
n@453
|
1706 storeDOM.push(elementListenTracker);
|
n@177
|
1707 }
|
n@177
|
1708 if (audioEngineContext.metric.enableElementInitialPosition) {
|
n@453
|
1709 var elementInitial = storage.document.createElement('metricResult');
|
n@177
|
1710 elementInitial.setAttribute('name','elementInitialPosition');
|
n@177
|
1711 elementInitial.textContent = this.initialPosition;
|
n@453
|
1712 storeDOM.push(elementInitial);
|
n@177
|
1713 }
|
n@177
|
1714 if (audioEngineContext.metric.enableFlagListenedTo) {
|
n@453
|
1715 var flagListenedTo = storage.document.createElement('metricResult');
|
n@177
|
1716 flagListenedTo.setAttribute('name','elementFlagListenedTo');
|
n@177
|
1717 flagListenedTo.textContent = this.wasListenedTo;
|
n@453
|
1718 storeDOM.push(flagListenedTo);
|
n@177
|
1719 }
|
n@177
|
1720 if (audioEngineContext.metric.enableFlagMoved) {
|
n@453
|
1721 var flagMoved = storage.document.createElement('metricResult');
|
n@177
|
1722 flagMoved.setAttribute('name','elementFlagMoved');
|
n@177
|
1723 flagMoved.textContent = this.wasMoved;
|
n@453
|
1724 storeDOM.push(flagMoved);
|
n@177
|
1725 }
|
n@177
|
1726 if (audioEngineContext.metric.enableFlagComments) {
|
n@453
|
1727 var flagComments = storage.document.createElement('metricResult');
|
n@177
|
1728 flagComments.setAttribute('name','elementFlagComments');
|
n@177
|
1729 if (this.parent.commentDOM == null)
|
n@177
|
1730 {flag.textContent = 'false';}
|
n@177
|
1731 else if (this.parent.commentDOM.textContent.length == 0)
|
n@177
|
1732 {flag.textContent = 'false';}
|
n@177
|
1733 else
|
n@177
|
1734 {flag.textContet = 'true';}
|
n@453
|
1735 storeDOM.push(flagComments);
|
n@177
|
1736 }
|
n@453
|
1737 return storeDOM;
|
n@177
|
1738 };
|
n@54
|
1739 }
|
n@54
|
1740
|
n@54
|
1741 function randomiseOrder(input)
|
n@54
|
1742 {
|
n@54
|
1743 // This takes an array of information and randomises the order
|
n@54
|
1744 var N = input.length;
|
b@207
|
1745
|
b@207
|
1746 var inputSequence = []; // For safety purposes: keep track of randomisation
|
b@207
|
1747 for (var counter = 0; counter < N; ++counter)
|
b@207
|
1748 inputSequence.push(counter) // Fill array
|
b@207
|
1749 var inputSequenceClone = inputSequence.slice(0);
|
b@207
|
1750
|
n@54
|
1751 var holdArr = [];
|
b@207
|
1752 var outputSequence = [];
|
n@54
|
1753 for (var n=0; n<N; n++)
|
n@54
|
1754 {
|
n@54
|
1755 // First pick a random number
|
n@54
|
1756 var r = Math.random();
|
n@54
|
1757 // Multiply and floor by the number of elements left
|
n@54
|
1758 r = Math.floor(r*input.length);
|
n@54
|
1759 // Pick out that element and delete from the array
|
n@54
|
1760 holdArr.push(input.splice(r,1)[0]);
|
b@207
|
1761 // Do the same with sequence
|
b@207
|
1762 outputSequence.push(inputSequence.splice(r,1)[0]);
|
n@54
|
1763 }
|
b@207
|
1764 console.log(inputSequenceClone.toString()); // print original array to console
|
b@207
|
1765 console.log(outputSequence.toString()); // print randomised array to console
|
n@54
|
1766 return holdArr;
|
n@125
|
1767 }
|
n@125
|
1768
|
n@125
|
1769 function returnDateNode()
|
n@125
|
1770 {
|
n@125
|
1771 // Create an XML Node for the Date and Time a test was conducted
|
n@125
|
1772 // Structure is
|
n@125
|
1773 // <datetime>
|
n@125
|
1774 // <date year="##" month="##" day="##">DD/MM/YY</date>
|
n@125
|
1775 // <time hour="##" minute="##" sec="##">HH:MM:SS</time>
|
n@125
|
1776 // </datetime>
|
n@125
|
1777 var dateTime = new Date();
|
n@125
|
1778 var year = document.createAttribute('year');
|
n@125
|
1779 var month = document.createAttribute('month');
|
n@125
|
1780 var day = document.createAttribute('day');
|
n@125
|
1781 var hour = document.createAttribute('hour');
|
n@125
|
1782 var minute = document.createAttribute('minute');
|
n@125
|
1783 var secs = document.createAttribute('secs');
|
n@125
|
1784
|
n@125
|
1785 year.nodeValue = dateTime.getFullYear();
|
n@125
|
1786 month.nodeValue = dateTime.getMonth()+1;
|
n@125
|
1787 day.nodeValue = dateTime.getDate();
|
n@125
|
1788 hour.nodeValue = dateTime.getHours();
|
n@125
|
1789 minute.nodeValue = dateTime.getMinutes();
|
n@125
|
1790 secs.nodeValue = dateTime.getSeconds();
|
n@125
|
1791
|
n@125
|
1792 var hold = document.createElement("datetime");
|
n@125
|
1793 var date = document.createElement("date");
|
n@125
|
1794 date.textContent = year.nodeValue+'/'+month.nodeValue+'/'+day.nodeValue;
|
n@125
|
1795 var time = document.createElement("time");
|
n@125
|
1796 time.textContent = hour.nodeValue+':'+minute.nodeValue+':'+secs.nodeValue;
|
n@125
|
1797
|
n@125
|
1798 date.setAttributeNode(year);
|
n@125
|
1799 date.setAttributeNode(month);
|
n@125
|
1800 date.setAttributeNode(day);
|
n@125
|
1801 time.setAttributeNode(hour);
|
n@125
|
1802 time.setAttributeNode(minute);
|
n@125
|
1803 time.setAttributeNode(secs);
|
n@125
|
1804
|
n@125
|
1805 hold.appendChild(date);
|
n@125
|
1806 hold.appendChild(time);
|
n@377
|
1807 return hold;
|
n@125
|
1808
|
nicholas@135
|
1809 }
|
nicholas@135
|
1810
|
n@180
|
1811 function Specification() {
|
n@180
|
1812 // Handles the decoding of the project specification XML into a simple JavaScript Object.
|
n@180
|
1813
|
n@453
|
1814 this.interface = null;
|
n@504
|
1815 this.projectReturn = "null";
|
n@453
|
1816 this.randomiseOrder = null;
|
n@453
|
1817 this.testPages = null;
|
n@453
|
1818 this.pages = [];
|
n@453
|
1819 this.metrics = null;
|
n@453
|
1820 this.interfaces = null;
|
n@453
|
1821 this.loudness = null;
|
n@453
|
1822 this.errors = [];
|
n@453
|
1823 this.schema = null;
|
n@380
|
1824
|
n@453
|
1825 this.processAttribute = function(attribute,schema)
|
n@453
|
1826 {
|
n@453
|
1827 // attribute is the string returned from getAttribute on the XML
|
n@453
|
1828 // schema is the <xs:attribute> node
|
n@453
|
1829 if (schema.getAttribute('name') == undefined && schema.getAttribute('ref') != undefined)
|
n@453
|
1830 {
|
n@477
|
1831 schema = this.schema.getAllElementsByName(schema.getAttribute('ref'))[0];
|
n@453
|
1832 }
|
n@453
|
1833 var defaultOpt = schema.getAttribute('default');
|
n@453
|
1834 if (attribute == null) {
|
n@453
|
1835 attribute = defaultOpt;
|
n@453
|
1836 }
|
n@453
|
1837 var dataType = schema.getAttribute('type');
|
n@453
|
1838 if (typeof dataType == "string") { dataType = dataType.substr(3);}
|
n@453
|
1839 else {dataType = "string";}
|
n@453
|
1840 if (attribute == null)
|
n@453
|
1841 {
|
n@453
|
1842 return attribute;
|
n@453
|
1843 }
|
n@453
|
1844 switch(dataType)
|
n@453
|
1845 {
|
n@453
|
1846 case "boolean":
|
n@453
|
1847 if (attribute == 'true'){attribute = true;}else{attribute=false;}
|
n@453
|
1848 break;
|
n@453
|
1849 case "negativeInteger":
|
n@453
|
1850 case "positiveInteger":
|
n@453
|
1851 case "nonNegativeInteger":
|
n@453
|
1852 case "nonPositiveInteger":
|
n@453
|
1853 case "integer":
|
n@453
|
1854 case "decimal":
|
n@453
|
1855 case "short":
|
n@453
|
1856 attribute = Number(attribute);
|
n@453
|
1857 break;
|
n@453
|
1858 case "string":
|
n@453
|
1859 default:
|
n@453
|
1860 attribute = String(attribute);
|
n@453
|
1861 break;
|
n@453
|
1862 }
|
n@453
|
1863 return attribute;
|
n@453
|
1864 };
|
n@180
|
1865
|
n@374
|
1866 this.decode = function(projectXML) {
|
n@453
|
1867 this.errors = [];
|
n@180
|
1868 // projectXML - DOM Parsed document
|
nicholas@240
|
1869 this.projectXML = projectXML.childNodes[0];
|
n@180
|
1870 var setupNode = projectXML.getElementsByTagName('setup')[0];
|
n@477
|
1871 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
|
n@453
|
1872 // First decode the attributes
|
n@477
|
1873 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
|
n@453
|
1874 for (var i in attributes)
|
n@297
|
1875 {
|
n@453
|
1876 if (isNaN(Number(i)) == true){break;}
|
n@453
|
1877 var attributeName = attributes[i].getAttribute('name');
|
n@453
|
1878 var projectAttr = setupNode.getAttribute(attributeName);
|
n@453
|
1879 projectAttr = this.processAttribute(projectAttr,attributes[i]);
|
n@453
|
1880 switch(typeof projectAttr)
|
n@410
|
1881 {
|
n@453
|
1882 case "number":
|
n@453
|
1883 case "boolean":
|
n@453
|
1884 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
1885 break;
|
n@453
|
1886 case "string":
|
n@453
|
1887 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
1888 break;
|
n@410
|
1889 }
|
n@453
|
1890
|
n@374
|
1891 }
|
n@374
|
1892
|
n@501
|
1893 this.metrics = new this.metricNode();
|
n@180
|
1894
|
n@453
|
1895 this.metrics.decode(this,setupNode.getElementsByTagName('metric')[0]);
|
n@453
|
1896
|
n@453
|
1897 // Now process the survey node options
|
n@453
|
1898 var survey = setupNode.getElementsByTagName('survey');
|
n@453
|
1899 for (var i in survey) {
|
n@453
|
1900 if (isNaN(Number(i)) == true){break;}
|
n@453
|
1901 var location = survey[i].getAttribute('location');
|
n@453
|
1902 if (location == 'pre' || location == 'before')
|
n@453
|
1903 {
|
n@453
|
1904 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
|
n@453
|
1905 else {
|
n@453
|
1906 this.preTest = new this.surveyNode();
|
n@501
|
1907 this.preTest.decode(this,survey[i]);
|
n@453
|
1908 }
|
n@453
|
1909 } else if (location == 'post' || location == 'after') {
|
n@453
|
1910 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
|
n@453
|
1911 else {
|
n@453
|
1912 this.postTest = new this.surveyNode();
|
n@501
|
1913 this.postTest.decode(this,survey[i]);
|
n@453
|
1914 }
|
n@180
|
1915 }
|
n@180
|
1916 }
|
n@180
|
1917
|
n@453
|
1918 var interfaceNode = setupNode.getElementsByTagName('interface');
|
n@453
|
1919 if (interfaceNode.length > 1)
|
n@453
|
1920 {
|
n@453
|
1921 this.errors.push("Only one <interface> node in the <setup> node allowed! Others except first ingnored!");
|
n@453
|
1922 }
|
n@453
|
1923 this.interfaces = new this.interfaceNode();
|
n@453
|
1924 if (interfaceNode.length != 0)
|
n@453
|
1925 {
|
n@453
|
1926 interfaceNode = interfaceNode[0];
|
n@477
|
1927 this.interfaces.decode(this,interfaceNode,this.schema.getAllElementsByName('interface')[1]);
|
nicholas@213
|
1928 }
|
nicholas@213
|
1929
|
n@453
|
1930 // Page tags
|
n@453
|
1931 var pageTags = projectXML.getElementsByTagName('page');
|
n@477
|
1932 var pageSchema = this.schema.getAllElementsByName('page')[0];
|
n@453
|
1933 for (var i=0; i<pageTags.length; i++)
|
n@297
|
1934 {
|
n@453
|
1935 var node = new this.page();
|
n@453
|
1936 node.decode(this,pageTags[i],pageSchema);
|
n@453
|
1937 this.pages.push(node);
|
n@297
|
1938 }
|
n@180
|
1939 };
|
n@180
|
1940
|
n@374
|
1941 this.encode = function()
|
n@374
|
1942 {
|
n@503
|
1943 var RootDocument = document.implementation.createDocument(null,"waet");
|
n@503
|
1944 var root = RootDocument.children[0];
|
n@503
|
1945 root.setAttribute("xmlns:xsi","http://www.w3.org/2001/XMLSchema-instance");
|
n@503
|
1946 root.setAttribute("xsi:noNamespaceSchemaLocation","test-schema.xsd");
|
n@453
|
1947 // Build setup node
|
n@503
|
1948 var setup = RootDocument.createElement("setup");
|
n@503
|
1949 var schemaSetup = this.schema.getAllElementsByName('setup')[0];
|
n@503
|
1950 // First decode the attributes
|
n@503
|
1951 var attributes = schemaSetup.getAllElementsByTagName('xs:attribute');
|
n@503
|
1952 for (var i=0; i<attributes.length; i++)
|
n@503
|
1953 {
|
n@503
|
1954 var name = attributes[i].getAttribute("name");
|
n@503
|
1955 if (name == undefined) {
|
n@503
|
1956 name = attributes[i].getAttribute("ref");
|
n@503
|
1957 }
|
n@503
|
1958 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
1959 {
|
n@503
|
1960 eval("setup.setAttribute('"+name+"',this."+name+")");
|
n@503
|
1961 }
|
n@503
|
1962 }
|
n@503
|
1963 root.appendChild(setup);
|
n@503
|
1964 // Survey node
|
n@503
|
1965 setup.appendChild(this.preTest.encode(RootDocument));
|
n@503
|
1966 setup.appendChild(this.postTest.encode(RootDocument));
|
n@503
|
1967 setup.appendChild(this.metrics.encode(RootDocument));
|
n@503
|
1968 setup.appendChild(this.interfaces.encode(RootDocument));
|
n@503
|
1969 for (var page of this.pages)
|
n@503
|
1970 {
|
n@503
|
1971 root.appendChild(page.encode(RootDocument));
|
n@503
|
1972 }
|
n@503
|
1973 return RootDocument;
|
n@374
|
1974 };
|
n@374
|
1975
|
n@453
|
1976 this.surveyNode = function() {
|
n@453
|
1977 this.location = null;
|
n@180
|
1978 this.options = [];
|
n@501
|
1979 this.schema = specification.schema.getAllElementsByName('survey')[0];
|
n@180
|
1980
|
n@374
|
1981 this.OptionNode = function() {
|
n@374
|
1982 this.type = undefined;
|
n@501
|
1983 this.schema = specification.schema.getAllElementsByName('surveyentry')[0];
|
n@374
|
1984 this.id = undefined;
|
n@597
|
1985 this.name = undefined;
|
n@374
|
1986 this.mandatory = undefined;
|
n@374
|
1987 this.statement = undefined;
|
n@374
|
1988 this.boxsize = undefined;
|
n@374
|
1989 this.options = [];
|
n@374
|
1990 this.min = undefined;
|
n@374
|
1991 this.max = undefined;
|
n@374
|
1992 this.step = undefined;
|
n@374
|
1993
|
n@501
|
1994 this.decode = function(parent,child)
|
n@374
|
1995 {
|
n@501
|
1996 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
1997 for (var i in attributeMap){
|
n@453
|
1998 if(isNaN(Number(i)) == true){break;}
|
n@453
|
1999 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2000 var projectAttr = child.getAttribute(attributeName);
|
n@453
|
2001 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
|
n@453
|
2002 switch(typeof projectAttr)
|
n@453
|
2003 {
|
n@453
|
2004 case "number":
|
n@453
|
2005 case "boolean":
|
n@453
|
2006 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2007 break;
|
n@453
|
2008 case "string":
|
n@453
|
2009 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2010 break;
|
n@374
|
2011 }
|
n@453
|
2012 }
|
n@453
|
2013 this.statement = child.getElementsByTagName('statement')[0].textContent;
|
n@453
|
2014 if (this.type == "checkbox" || this.type == "radio") {
|
n@453
|
2015 var children = child.getElementsByTagName('option');
|
n@453
|
2016 if (children.length == null) {
|
n@374
|
2017 console.log('Malformed' +child.nodeName+ 'entry');
|
n@374
|
2018 this.statement = 'Malformed' +child.nodeName+ 'entry';
|
n@374
|
2019 this.type = 'statement';
|
n@374
|
2020 } else {
|
n@374
|
2021 this.options = [];
|
n@453
|
2022 for (var i in children)
|
n@453
|
2023 {
|
n@453
|
2024 if (isNaN(Number(i))==true){break;}
|
n@453
|
2025 this.options.push({
|
n@453
|
2026 name: children[i].getAttribute('name'),
|
n@453
|
2027 text: children[i].textContent
|
n@453
|
2028 });
|
n@374
|
2029 }
|
n@374
|
2030 }
|
n@191
|
2031 }
|
n@374
|
2032 };
|
n@374
|
2033
|
n@503
|
2034 this.exportXML = function(doc)
|
n@374
|
2035 {
|
n@544
|
2036 var node = doc.createElement('surveyentry');
|
n@453
|
2037 node.setAttribute('type',this.type);
|
n@503
|
2038 var statement = doc.createElement('statement');
|
n@453
|
2039 statement.textContent = this.statement;
|
n@453
|
2040 node.appendChild(statement);
|
n@597
|
2041 if (this.type != "statement") {
|
n@544
|
2042 node.id = this.id;
|
n@597
|
2043 if (this.name != undefined) { node.setAttribute("name",this.name);}
|
n@544
|
2044 if (this.mandatory != undefined) { node.setAttribute("mandatory",this.mandatory);}
|
n@597
|
2045 switch(this.type)
|
n@597
|
2046 {
|
n@597
|
2047 case "question":
|
n@597
|
2048 if (this.boxsize != undefined) {node.setAttribute("boxsize",this.boxsize);}
|
n@597
|
2049 break;
|
n@597
|
2050 case "number":
|
n@597
|
2051 if (this.min != undefined) {node.setAttribute("min", this.min);}
|
n@597
|
2052 if (this.max != undefined) {node.setAttribute("max", this.max);}
|
n@597
|
2053 break;
|
n@597
|
2054 case "checkbox":
|
n@597
|
2055 case "radio":
|
n@597
|
2056 for (var i=0; i<this.options.length; i++)
|
n@597
|
2057 {
|
n@597
|
2058 var option = this.options[i];
|
n@597
|
2059 var optionNode = doc.createElement("option");
|
n@597
|
2060 optionNode.setAttribute("name",option.name);
|
n@597
|
2061 optionNode.textContent = option.text;
|
n@597
|
2062 node.appendChild(optionNode);
|
n@597
|
2063 }
|
n@597
|
2064 break;
|
n@597
|
2065 }
|
n@597
|
2066 }
|
n@374
|
2067 return node;
|
n@374
|
2068 };
|
n@374
|
2069 };
|
n@501
|
2070 this.decode = function(parent,xml) {
|
n@453
|
2071 this.location = xml.getAttribute('location');
|
n@453
|
2072 if (this.location == 'before'){this.location = 'pre';}
|
n@453
|
2073 else if (this.location == 'after'){this.location = 'post';}
|
n@453
|
2074 for (var i in xml.children)
|
n@453
|
2075 {
|
n@453
|
2076 if(isNaN(Number(i))==true){break;}
|
n@374
|
2077 var node = new this.OptionNode();
|
n@501
|
2078 node.decode(parent,xml.children[i]);
|
n@374
|
2079 this.options.push(node);
|
n@453
|
2080 }
|
n@453
|
2081 };
|
n@503
|
2082 this.encode = function(doc) {
|
n@503
|
2083 var node = doc.createElement('survey');
|
n@453
|
2084 node.setAttribute('location',this.location);
|
n@453
|
2085 for (var i=0; i<this.options.length; i++)
|
n@453
|
2086 {
|
n@503
|
2087 node.appendChild(this.options[i].exportXML(doc));
|
n@453
|
2088 }
|
n@453
|
2089 return node;
|
n@453
|
2090 };
|
n@453
|
2091 };
|
n@453
|
2092
|
n@453
|
2093 this.interfaceNode = function()
|
n@453
|
2094 {
|
n@453
|
2095 this.title = null;
|
n@453
|
2096 this.name = null;
|
n@453
|
2097 this.options = [];
|
n@453
|
2098 this.scales = [];
|
n@501
|
2099 this.schema = specification.schema.getAllElementsByName('interface')[1];
|
n@453
|
2100
|
n@501
|
2101 this.decode = function(parent,xml) {
|
n@453
|
2102 this.name = xml.getAttribute('name');
|
n@453
|
2103 var titleNode = xml.getElementsByTagName('title');
|
n@453
|
2104 if (titleNode.length == 1)
|
n@453
|
2105 {
|
n@453
|
2106 this.title = titleNode[0].textContent;
|
n@453
|
2107 }
|
n@453
|
2108 var interfaceOptionNodes = xml.getElementsByTagName('interfaceoption');
|
n@453
|
2109 // Extract interfaceoption node schema
|
n@501
|
2110 var interfaceOptionNodeSchema = this.schema.getAllElementsByName('interfaceoption')[0];
|
n@477
|
2111 var attributeMap = interfaceOptionNodeSchema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2112 for (var i=0; i<interfaceOptionNodes.length; i++)
|
n@453
|
2113 {
|
n@453
|
2114 var ioNode = interfaceOptionNodes[i];
|
n@453
|
2115 var option = {};
|
n@453
|
2116 for (var j=0; j<attributeMap.length; j++)
|
n@453
|
2117 {
|
n@453
|
2118 var attributeName = attributeMap[j].getAttribute('name') || attributeMap[j].getAttribute('ref');
|
n@453
|
2119 var projectAttr = ioNode.getAttribute(attributeName);
|
n@453
|
2120 projectAttr = parent.processAttribute(projectAttr,attributeMap[j]);
|
n@453
|
2121 switch(typeof projectAttr)
|
n@453
|
2122 {
|
n@453
|
2123 case "number":
|
n@453
|
2124 case "boolean":
|
n@453
|
2125 eval('option.'+attributeName+' = '+projectAttr);
|
n@453
|
2126 break;
|
n@453
|
2127 case "string":
|
n@453
|
2128 eval('option.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2129 break;
|
n@453
|
2130 }
|
n@453
|
2131 }
|
n@453
|
2132 this.options.push(option);
|
n@453
|
2133 }
|
n@453
|
2134
|
n@453
|
2135 // Now the scales nodes
|
n@453
|
2136 var scaleParent = xml.getElementsByTagName('scales');
|
n@453
|
2137 if (scaleParent.length == 1) {
|
n@453
|
2138 scaleParent = scaleParent[0];
|
n@453
|
2139 for (var i=0; i<scaleParent.children.length; i++) {
|
n@453
|
2140 var child = scaleParent.children[i];
|
n@453
|
2141 this.scales.push({
|
n@453
|
2142 text: child.textContent,
|
n@453
|
2143 position: Number(child.getAttribute('position'))
|
n@453
|
2144 });
|
n@374
|
2145 }
|
n@180
|
2146 }
|
n@180
|
2147 };
|
n@453
|
2148
|
n@503
|
2149 this.encode = function(doc) {
|
n@503
|
2150 var node = doc.createElement("interface");
|
n@503
|
2151 if (typeof name == "string")
|
n@503
|
2152 node.setAttribute("name",this.name);
|
n@503
|
2153 for (var option of this.options)
|
n@503
|
2154 {
|
n@503
|
2155 var child = doc.createElement("interfaceoption");
|
n@503
|
2156 child.setAttribute("type",option.type);
|
n@503
|
2157 child.setAttribute("name",option.name);
|
n@503
|
2158 node.appendChild(child);
|
n@503
|
2159 }
|
n@503
|
2160 if (this.scales.length != 0) {
|
n@503
|
2161 var scales = doc.createElement("scales");
|
n@503
|
2162 for (var scale of this.scales)
|
n@503
|
2163 {
|
n@503
|
2164 var child = doc.createElement("scalelabel");
|
n@503
|
2165 child.setAttribute("position",scale.position);
|
n@503
|
2166 child.textContent = scale.text;
|
n@503
|
2167 scales.appendChild(child);
|
n@503
|
2168 }
|
n@503
|
2169 node.appendChild(scales);
|
n@503
|
2170 }
|
n@503
|
2171 return node;
|
n@453
|
2172 };
|
n@180
|
2173 };
|
n@180
|
2174
|
n@501
|
2175 this.metricNode = function() {
|
n@501
|
2176 this.enabled = [];
|
n@501
|
2177 this.decode = function(parent, xml) {
|
n@501
|
2178 var children = xml.getElementsByTagName('metricenable');
|
n@501
|
2179 for (var i in children) {
|
n@501
|
2180 if (isNaN(Number(i)) == true){break;}
|
n@501
|
2181 this.enabled.push(children[i].textContent);
|
n@501
|
2182 }
|
n@501
|
2183 }
|
n@503
|
2184 this.encode = function(doc) {
|
n@503
|
2185 var node = doc.createElement('metric');
|
n@501
|
2186 for (var i in this.enabled)
|
n@501
|
2187 {
|
n@501
|
2188 if (isNaN(Number(i)) == true){break;}
|
n@503
|
2189 var child = doc.createElement('metricenable');
|
n@501
|
2190 child.textContent = this.enabled[i];
|
n@501
|
2191 node.appendChild(child);
|
n@501
|
2192 }
|
n@501
|
2193 return node;
|
n@501
|
2194 }
|
n@501
|
2195 }
|
n@501
|
2196
|
n@453
|
2197 this.page = function() {
|
n@374
|
2198 this.presentedId = undefined;
|
n@374
|
2199 this.id = undefined;
|
n@374
|
2200 this.hostURL = undefined;
|
n@374
|
2201 this.randomiseOrder = undefined;
|
n@374
|
2202 this.loop = undefined;
|
n@453
|
2203 this.showElementComments = undefined;
|
n@374
|
2204 this.outsideReference = null;
|
n@410
|
2205 this.loudness = null;
|
n@603
|
2206 this.label = null;
|
n@453
|
2207 this.preTest = null;
|
n@453
|
2208 this.postTest = null;
|
n@374
|
2209 this.interfaces = [];
|
n@374
|
2210 this.commentBoxPrefix = "Comment on track";
|
n@374
|
2211 this.audioElements = [];
|
n@374
|
2212 this.commentQuestions = [];
|
n@501
|
2213 this.schema = specification.schema.getAllElementsByName("page")[0];
|
n@374
|
2214
|
n@501
|
2215 this.decode = function(parent,xml)
|
n@374
|
2216 {
|
n@477
|
2217 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2218 for (var i=0; i<attributeMap.length; i++)
|
n@410
|
2219 {
|
n@453
|
2220 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2221 var projectAttr = xml.getAttribute(attributeName);
|
n@453
|
2222 projectAttr = parent.processAttribute(projectAttr,attributeMap[i]);
|
n@453
|
2223 switch(typeof projectAttr)
|
nicholas@417
|
2224 {
|
n@453
|
2225 case "number":
|
n@453
|
2226 case "boolean":
|
n@453
|
2227 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2228 break;
|
n@453
|
2229 case "string":
|
n@453
|
2230 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2231 break;
|
n@374
|
2232 }
|
n@374
|
2233 }
|
n@374
|
2234
|
n@453
|
2235 // Get the Comment Box Prefix
|
n@453
|
2236 var CBP = xml.getElementsByTagName('commentboxprefix');
|
n@453
|
2237 if (CBP.length != 0) {
|
n@453
|
2238 this.commentBoxPrefix = CBP[0].textContent;
|
n@427
|
2239 }
|
n@427
|
2240
|
n@453
|
2241 // Now decode the interfaces
|
n@453
|
2242 var interfaceNode = xml.getElementsByTagName('interface');
|
n@453
|
2243 for (var i=0; i<interfaceNode.length; i++)
|
n@453
|
2244 {
|
n@453
|
2245 var node = new parent.interfaceNode();
|
n@477
|
2246 node.decode(this,interfaceNode[i],parent.schema.getAllElementsByName('interface')[1]);
|
n@453
|
2247 this.interfaces.push(node);
|
n@453
|
2248 }
|
n@380
|
2249
|
n@453
|
2250 // Now process the survey node options
|
n@453
|
2251 var survey = xml.getElementsByTagName('survey');
|
n@477
|
2252 var surveySchema = parent.schema.getAllElementsByName('survey')[0];
|
n@453
|
2253 for (var i in survey) {
|
n@453
|
2254 if (isNaN(Number(i)) == true){break;}
|
n@453
|
2255 var location = survey[i].getAttribute('location');
|
n@453
|
2256 if (location == 'pre' || location == 'before')
|
n@453
|
2257 {
|
n@453
|
2258 if (this.preTest != null){this.errors.push("Already a pre/before test survey defined! Ignoring second!!");}
|
n@453
|
2259 else {
|
n@453
|
2260 this.preTest = new parent.surveyNode();
|
n@453
|
2261 this.preTest.decode(parent,survey[i],surveySchema);
|
n@453
|
2262 }
|
n@453
|
2263 } else if (location == 'post' || location == 'after') {
|
n@453
|
2264 if (this.postTest != null){this.errors.push("Already a post/after test survey defined! Ignoring second!!");}
|
n@453
|
2265 else {
|
n@453
|
2266 this.postTest = new parent.surveyNode();
|
n@453
|
2267 this.postTest.decode(parent,survey[i],surveySchema);
|
n@453
|
2268 }
|
n@453
|
2269 }
|
n@453
|
2270 }
|
n@453
|
2271
|
n@453
|
2272 // Now process the audioelement tags
|
n@453
|
2273 var audioElements = xml.getElementsByTagName('audioelement');
|
n@453
|
2274 for (var i=0; i<audioElements.length; i++)
|
n@453
|
2275 {
|
n@453
|
2276 var node = new this.audioElementNode();
|
n@501
|
2277 node.decode(this,audioElements[i]);
|
n@453
|
2278 this.audioElements.push(node);
|
n@453
|
2279 }
|
n@453
|
2280
|
n@453
|
2281 // Now decode the commentquestions
|
n@453
|
2282 var commentQuestions = xml.getElementsByTagName('commentquestion');
|
n@453
|
2283 for (var i=0; i<commentQuestions.length; i++)
|
n@453
|
2284 {
|
n@374
|
2285 var node = new this.commentQuestionNode();
|
n@501
|
2286 node.decode(parent,commentQuestions[i]);
|
n@374
|
2287 this.commentQuestions.push(node);
|
n@180
|
2288 }
|
n@180
|
2289 };
|
n@180
|
2290
|
n@374
|
2291 this.encode = function(root)
|
n@374
|
2292 {
|
n@503
|
2293 var AHNode = root.createElement("page");
|
n@503
|
2294 // First decode the attributes
|
n@503
|
2295 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
|
n@503
|
2296 for (var i=0; i<attributes.length; i++)
|
n@503
|
2297 {
|
n@503
|
2298 var name = attributes[i].getAttribute("name");
|
n@503
|
2299 if (name == undefined) {
|
n@503
|
2300 name = attributes[i].getAttribute("ref");
|
n@503
|
2301 }
|
n@503
|
2302 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
2303 {
|
n@503
|
2304 eval("AHNode.setAttribute('"+name+"',this."+name+")");
|
n@503
|
2305 }
|
n@503
|
2306 }
|
n@410
|
2307 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
|
n@503
|
2308 // <commentboxprefix>
|
n@503
|
2309 var commentboxprefix = root.createElement("commentboxprefix");
|
n@503
|
2310 commentboxprefix.textContent = this.commentBoxPrefix;
|
n@503
|
2311 AHNode.appendChild(commentboxprefix);
|
n@503
|
2312
|
n@374
|
2313 for (var i=0; i<this.interfaces.length; i++)
|
n@324
|
2314 {
|
n@374
|
2315 AHNode.appendChild(this.interfaces[i].encode(root));
|
n@374
|
2316 }
|
n@374
|
2317
|
n@374
|
2318 for (var i=0; i<this.audioElements.length; i++) {
|
n@374
|
2319 AHNode.appendChild(this.audioElements[i].encode(root));
|
n@374
|
2320 }
|
n@374
|
2321 // Create <CommentQuestion>
|
n@374
|
2322 for (var i=0; i<this.commentQuestions.length; i++)
|
n@374
|
2323 {
|
n@503
|
2324 AHNode.appendChild(this.commentQuestions[i].encode(root));
|
n@374
|
2325 }
|
n@374
|
2326
|
n@503
|
2327 AHNode.appendChild(this.preTest.encode(root));
|
n@503
|
2328 AHNode.appendChild(this.postTest.encode(root));
|
n@374
|
2329 return AHNode;
|
n@374
|
2330 };
|
n@374
|
2331
|
n@453
|
2332 this.commentQuestionNode = function() {
|
n@453
|
2333 this.id = null;
|
n@597
|
2334 this.name = undefined;
|
n@453
|
2335 this.type = undefined;
|
n@374
|
2336 this.options = [];
|
n@453
|
2337 this.statement = undefined;
|
n@501
|
2338 this.schema = specification.schema.getAllElementsByName('commentquestion')[0];
|
n@501
|
2339 this.decode = function(parent,xml)
|
n@374
|
2340 {
|
n@453
|
2341 this.id = xml.id;
|
n@597
|
2342 this.name = xml.getAttribute('name');
|
n@453
|
2343 this.type = xml.getAttribute('type');
|
n@453
|
2344 this.statement = xml.getElementsByTagName('statement')[0].textContent;
|
n@453
|
2345 var optNodes = xml.getElementsByTagName('option');
|
n@453
|
2346 for (var i=0; i<optNodes.length; i++)
|
n@453
|
2347 {
|
n@453
|
2348 var optNode = optNodes[i];
|
n@453
|
2349 this.options.push({
|
n@453
|
2350 name: optNode.getAttribute('name'),
|
n@453
|
2351 text: optNode.textContent
|
n@453
|
2352 });
|
n@374
|
2353 }
|
n@374
|
2354 };
|
n@453
|
2355
|
n@374
|
2356 this.encode = function(root)
|
n@374
|
2357 {
|
n@503
|
2358 var node = root.createElement("commentquestion");
|
n@503
|
2359 node.id = this.id;
|
n@503
|
2360 node.setAttribute("type",this.type);
|
n@597
|
2361 if (this.name != undefined){node.setAttribute("name",this.name);}
|
n@503
|
2362 var statement = root.createElement("statement");
|
n@503
|
2363 statement.textContent = this.statement;
|
n@503
|
2364 node.appendChild(statement);
|
n@503
|
2365 for (var option of this.options)
|
n@503
|
2366 {
|
n@503
|
2367 var child = root.createElement("option");
|
n@503
|
2368 child.setAttribute("name",option.name);
|
n@503
|
2369 child.textContent = option.text;
|
n@503
|
2370 node.appendChild(child);
|
n@503
|
2371 }
|
n@503
|
2372 return node;
|
n@374
|
2373 };
|
n@374
|
2374 };
|
n@374
|
2375
|
n@374
|
2376 this.audioElementNode = function() {
|
n@374
|
2377 this.url = null;
|
n@374
|
2378 this.id = null;
|
n@597
|
2379 this.name = null;
|
n@374
|
2380 this.parent = null;
|
n@453
|
2381 this.type = null;
|
n@525
|
2382 this.marker = null;
|
n@374
|
2383 this.enforce = false;
|
n@564
|
2384 this.gain = 0.0;
|
n@501
|
2385 this.schema = specification.schema.getAllElementsByName('audioelement')[0];;
|
n@453
|
2386 this.parent = null;
|
n@501
|
2387 this.decode = function(parent,xml)
|
n@374
|
2388 {
|
n@374
|
2389 this.parent = parent;
|
n@477
|
2390 var attributeMap = this.schema.getAllElementsByTagName('xs:attribute');
|
n@453
|
2391 for (var i=0; i<attributeMap.length; i++)
|
n@400
|
2392 {
|
n@453
|
2393 var attributeName = attributeMap[i].getAttribute('name') || attributeMap[i].getAttribute('ref');
|
n@453
|
2394 var projectAttr = xml.getAttribute(attributeName);
|
n@453
|
2395 projectAttr = specification.processAttribute(projectAttr,attributeMap[i]);
|
n@453
|
2396 switch(typeof projectAttr)
|
n@374
|
2397 {
|
n@453
|
2398 case "number":
|
n@453
|
2399 case "boolean":
|
n@453
|
2400 eval('this.'+attributeName+' = '+projectAttr);
|
n@453
|
2401 break;
|
n@453
|
2402 case "string":
|
n@453
|
2403 eval('this.'+attributeName+' = "'+projectAttr+'"');
|
n@453
|
2404 break;
|
n@324
|
2405 }
|
n@324
|
2406 }
|
n@453
|
2407
|
n@374
|
2408 };
|
n@374
|
2409 this.encode = function(root)
|
n@374
|
2410 {
|
n@503
|
2411 var AENode = root.createElement("audioelement");
|
n@503
|
2412 var attributes = this.schema.getAllElementsByTagName('xs:attribute');
|
n@503
|
2413 for (var i=0; i<attributes.length; i++)
|
n@503
|
2414 {
|
n@503
|
2415 var name = attributes[i].getAttribute("name");
|
n@503
|
2416 if (name == undefined) {
|
n@503
|
2417 name = attributes[i].getAttribute("ref");
|
n@503
|
2418 }
|
n@503
|
2419 if(eval("this."+name+" != undefined") || attributes[i].getAttribute("use") == "required")
|
n@503
|
2420 {
|
n@503
|
2421 eval("AENode.setAttribute('"+name+"',this."+name+")");
|
n@503
|
2422 }
|
n@503
|
2423 }
|
n@374
|
2424 return AENode;
|
n@374
|
2425 };
|
n@180
|
2426 };
|
n@180
|
2427 };
|
n@180
|
2428 }
|
n@374
|
2429
|
n@182
|
2430 function Interface(specificationObject) {
|
n@180
|
2431 // This handles the bindings between the interface and the audioEngineContext;
|
n@182
|
2432 this.specification = specificationObject;
|
n@182
|
2433 this.insertPoint = document.getElementById("topLevelBody");
|
n@180
|
2434
|
n@453
|
2435 this.newPage = function(audioHolderObject,store)
|
n@375
|
2436 {
|
n@500
|
2437 audioEngineContext.newTestPage(audioHolderObject,store);
|
n@550
|
2438 interfaceContext.commentBoxes.deleteCommentBoxes();
|
n@375
|
2439 interfaceContext.deleteCommentQuestions();
|
n@453
|
2440 loadTest(audioHolderObject,store);
|
n@375
|
2441 };
|
n@375
|
2442
|
n@182
|
2443 // Bounded by interface!!
|
n@182
|
2444 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
|
n@182
|
2445 // For example, APE returns the slider position normalised in a <value> tag.
|
n@182
|
2446 this.interfaceObjects = [];
|
n@182
|
2447 this.interfaceObject = function(){};
|
n@182
|
2448
|
n@302
|
2449 this.resizeWindow = function(event)
|
n@302
|
2450 {
|
n@395
|
2451 popup.resize(event);
|
n@302
|
2452 for(var i=0; i<this.commentBoxes.length; i++)
|
n@302
|
2453 {this.commentBoxes[i].resize();}
|
n@302
|
2454 for(var i=0; i<this.commentQuestions.length; i++)
|
n@302
|
2455 {this.commentQuestions[i].resize();}
|
n@302
|
2456 try
|
n@302
|
2457 {
|
n@302
|
2458 resizeWindow(event);
|
n@302
|
2459 }
|
n@302
|
2460 catch(err)
|
n@302
|
2461 {
|
n@302
|
2462 console.log("Warning - Interface does not have Resize option");
|
n@302
|
2463 console.log(err);
|
n@302
|
2464 }
|
n@302
|
2465 };
|
n@302
|
2466
|
n@356
|
2467 this.returnNavigator = function()
|
n@356
|
2468 {
|
n@491
|
2469 var node = storage.document.createElement("navigator");
|
n@491
|
2470 var platform = storage.document.createElement("platform");
|
n@356
|
2471 platform.textContent = navigator.platform;
|
n@491
|
2472 var vendor = storage.document.createElement("vendor");
|
n@356
|
2473 vendor.textContent = navigator.vendor;
|
n@491
|
2474 var userAgent = storage.document.createElement("uagent");
|
n@356
|
2475 userAgent.textContent = navigator.userAgent;
|
n@491
|
2476 var screen = storage.document.createElement("window");
|
n@491
|
2477 screen.setAttribute('innerWidth',window.innerWidth);
|
n@491
|
2478 screen.setAttribute('innerHeight',window.innerHeight);
|
n@356
|
2479 node.appendChild(platform);
|
n@356
|
2480 node.appendChild(vendor);
|
n@356
|
2481 node.appendChild(userAgent);
|
n@491
|
2482 node.appendChild(screen);
|
n@356
|
2483 return node;
|
n@356
|
2484 };
|
n@356
|
2485
|
n@550
|
2486 this.commentBoxes = new function() {
|
n@550
|
2487 this.boxes = [];
|
n@550
|
2488 this.injectPoint = null;
|
n@550
|
2489 this.elementCommentBox = function(audioObject) {
|
n@550
|
2490 var element = audioObject.specification;
|
n@550
|
2491 this.audioObject = audioObject;
|
n@550
|
2492 this.id = audioObject.id;
|
n@550
|
2493 var audioHolderObject = audioObject.specification.parent;
|
n@550
|
2494 // Create document objects to hold the comment boxes
|
n@550
|
2495 this.trackComment = document.createElement('div');
|
n@550
|
2496 this.trackComment.className = 'comment-div';
|
n@550
|
2497 this.trackComment.id = 'comment-div-'+audioObject.id;
|
n@550
|
2498 // Create a string next to each comment asking for a comment
|
n@550
|
2499 this.trackString = document.createElement('span');
|
n@550
|
2500 this.trackString.innerHTML = audioHolderObject.commentBoxPrefix+' '+audioObject.interfaceDOM.getPresentedId();
|
n@550
|
2501 // Create the HTML5 comment box 'textarea'
|
n@550
|
2502 this.trackCommentBox = document.createElement('textarea');
|
n@550
|
2503 this.trackCommentBox.rows = '4';
|
n@550
|
2504 this.trackCommentBox.cols = '100';
|
n@550
|
2505 this.trackCommentBox.name = 'trackComment'+audioObject.id;
|
n@550
|
2506 this.trackCommentBox.className = 'trackComment';
|
n@550
|
2507 var br = document.createElement('br');
|
n@550
|
2508 // Add to the holder.
|
n@550
|
2509 this.trackComment.appendChild(this.trackString);
|
n@550
|
2510 this.trackComment.appendChild(br);
|
n@550
|
2511 this.trackComment.appendChild(this.trackCommentBox);
|
n@550
|
2512
|
n@550
|
2513 this.exportXMLDOM = function() {
|
n@550
|
2514 var root = document.createElement('comment');
|
n@550
|
2515 var question = document.createElement('question');
|
n@550
|
2516 question.textContent = this.trackString.textContent;
|
n@550
|
2517 var response = document.createElement('response');
|
n@550
|
2518 response.textContent = this.trackCommentBox.value;
|
n@550
|
2519 console.log("Comment frag-"+this.id+": "+response.textContent);
|
n@550
|
2520 root.appendChild(question);
|
n@550
|
2521 root.appendChild(response);
|
n@550
|
2522 return root;
|
n@550
|
2523 };
|
n@550
|
2524 this.resize = function()
|
n@550
|
2525 {
|
n@550
|
2526 var boxwidth = (window.innerWidth-100)/2;
|
n@550
|
2527 if (boxwidth >= 600)
|
n@550
|
2528 {
|
n@550
|
2529 boxwidth = 600;
|
n@550
|
2530 }
|
n@550
|
2531 else if (boxwidth < 400)
|
n@550
|
2532 {
|
n@550
|
2533 boxwidth = 400;
|
n@550
|
2534 }
|
n@550
|
2535 this.trackComment.style.width = boxwidth+"px";
|
n@550
|
2536 this.trackCommentBox.style.width = boxwidth-6+"px";
|
n@550
|
2537 };
|
n@550
|
2538 this.resize();
|
n@550
|
2539 };
|
n@550
|
2540 this.createCommentBox = function(audioObject) {
|
n@550
|
2541 var node = new this.elementCommentBox(audioObject);
|
n@550
|
2542 this.boxes.push(node);
|
n@550
|
2543 audioObject.commentDOM = node;
|
n@550
|
2544 return node;
|
n@550
|
2545 };
|
n@550
|
2546 this.sortCommentBoxes = function() {
|
n@550
|
2547 this.boxes.sort(function(a,b){return a.id - b.id;});
|
n@550
|
2548 };
|
n@550
|
2549
|
n@550
|
2550 this.showCommentBoxes = function(inject, sort) {
|
n@550
|
2551 this.injectPoint = inject;
|
n@550
|
2552 if (sort) {this.sortCommentBoxes();}
|
n@550
|
2553 for (var box of this.boxes) {
|
n@550
|
2554 inject.appendChild(box.trackComment);
|
n@550
|
2555 }
|
n@550
|
2556 };
|
n@550
|
2557
|
n@550
|
2558 this.deleteCommentBoxes = function() {
|
n@550
|
2559 if (this.injectPoint != null) {
|
n@550
|
2560 for (var box of this.boxes) {
|
n@550
|
2561 this.injectPoint.removeChild(box.trackComment);
|
n@550
|
2562 }
|
n@550
|
2563 this.injectPoint = null;
|
n@550
|
2564 }
|
n@550
|
2565 this.boxes = [];
|
n@550
|
2566 };
|
n@550
|
2567 }
|
n@182
|
2568
|
n@193
|
2569 this.commentQuestions = [];
|
n@193
|
2570
|
n@193
|
2571 this.commentBox = function(commentQuestion) {
|
n@193
|
2572 this.specification = commentQuestion;
|
n@193
|
2573 // Create document objects to hold the comment boxes
|
n@193
|
2574 this.holder = document.createElement('div');
|
n@193
|
2575 this.holder.className = 'comment-div';
|
n@193
|
2576 // Create a string next to each comment asking for a comment
|
n@193
|
2577 this.string = document.createElement('span');
|
n@453
|
2578 this.string.innerHTML = commentQuestion.statement;
|
n@193
|
2579 // Create the HTML5 comment box 'textarea'
|
n@193
|
2580 this.textArea = document.createElement('textarea');
|
n@193
|
2581 this.textArea.rows = '4';
|
n@193
|
2582 this.textArea.cols = '100';
|
n@193
|
2583 this.textArea.className = 'trackComment';
|
n@193
|
2584 var br = document.createElement('br');
|
n@193
|
2585 // Add to the holder.
|
n@193
|
2586 this.holder.appendChild(this.string);
|
n@193
|
2587 this.holder.appendChild(br);
|
n@193
|
2588 this.holder.appendChild(this.textArea);
|
n@193
|
2589
|
n@520
|
2590 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2591 var root = storePoint.parent.document.createElement('comment');
|
n@193
|
2592 root.id = this.specification.id;
|
n@193
|
2593 root.setAttribute('type',this.specification.type);
|
b@254
|
2594 console.log("Question: "+this.string.textContent);
|
b@254
|
2595 console.log("Response: "+root.textContent);
|
n@520
|
2596 var question = storePoint.parent.document.createElement('question');
|
n@520
|
2597 question.textContent = this.string.textContent;
|
n@520
|
2598 var response = storePoint.parent.document.createElement('response');
|
n@520
|
2599 response.textContent = this.textArea.value;
|
n@520
|
2600 root.appendChild(question);
|
n@520
|
2601 root.appendChild(response);
|
n@520
|
2602 storePoint.XMLDOM.appendChild(root);
|
n@193
|
2603 return root;
|
n@193
|
2604 };
|
n@302
|
2605 this.resize = function()
|
n@302
|
2606 {
|
n@302
|
2607 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2608 if (boxwidth >= 600)
|
n@302
|
2609 {
|
n@302
|
2610 boxwidth = 600;
|
n@302
|
2611 }
|
n@302
|
2612 else if (boxwidth < 400)
|
n@302
|
2613 {
|
n@302
|
2614 boxwidth = 400;
|
n@302
|
2615 }
|
n@302
|
2616 this.holder.style.width = boxwidth+"px";
|
n@302
|
2617 this.textArea.style.width = boxwidth-6+"px";
|
n@302
|
2618 };
|
n@302
|
2619 this.resize();
|
n@193
|
2620 };
|
n@193
|
2621
|
n@193
|
2622 this.radioBox = function(commentQuestion) {
|
n@193
|
2623 this.specification = commentQuestion;
|
n@193
|
2624 // Create document objects to hold the comment boxes
|
n@193
|
2625 this.holder = document.createElement('div');
|
n@193
|
2626 this.holder.className = 'comment-div';
|
n@193
|
2627 // Create a string next to each comment asking for a comment
|
n@193
|
2628 this.string = document.createElement('span');
|
n@193
|
2629 this.string.innerHTML = commentQuestion.statement;
|
n@193
|
2630 var br = document.createElement('br');
|
n@193
|
2631 // Add to the holder.
|
n@193
|
2632 this.holder.appendChild(this.string);
|
n@193
|
2633 this.holder.appendChild(br);
|
n@193
|
2634 this.options = [];
|
n@193
|
2635 this.inputs = document.createElement('div');
|
n@193
|
2636 this.span = document.createElement('div');
|
n@193
|
2637 this.inputs.align = 'center';
|
n@193
|
2638 this.inputs.style.marginLeft = '12px';
|
n@193
|
2639 this.span.style.marginLeft = '12px';
|
n@193
|
2640 this.span.align = 'center';
|
n@193
|
2641 this.span.style.marginTop = '15px';
|
n@193
|
2642
|
n@193
|
2643 var optCount = commentQuestion.options.length;
|
n@453
|
2644 for (var optNode of commentQuestion.options)
|
n@193
|
2645 {
|
n@193
|
2646 var div = document.createElement('div');
|
n@301
|
2647 div.style.width = '80px';
|
n@193
|
2648 div.style.float = 'left';
|
n@193
|
2649 var input = document.createElement('input');
|
n@193
|
2650 input.type = 'radio';
|
n@193
|
2651 input.name = commentQuestion.id;
|
n@453
|
2652 input.setAttribute('setvalue',optNode.name);
|
n@193
|
2653 input.className = 'comment-radio';
|
n@193
|
2654 div.appendChild(input);
|
n@193
|
2655 this.inputs.appendChild(div);
|
n@193
|
2656
|
n@193
|
2657
|
n@193
|
2658 div = document.createElement('div');
|
n@301
|
2659 div.style.width = '80px';
|
n@193
|
2660 div.style.float = 'left';
|
n@193
|
2661 div.align = 'center';
|
n@193
|
2662 var span = document.createElement('span');
|
n@453
|
2663 span.textContent = optNode.text;
|
n@193
|
2664 span.className = 'comment-radio-span';
|
n@193
|
2665 div.appendChild(span);
|
n@193
|
2666 this.span.appendChild(div);
|
n@193
|
2667 this.options.push(input);
|
n@193
|
2668 }
|
n@193
|
2669 this.holder.appendChild(this.span);
|
n@193
|
2670 this.holder.appendChild(this.inputs);
|
n@193
|
2671
|
n@520
|
2672 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2673 var root = storePoint.parent.document.createElement('comment');
|
n@193
|
2674 root.id = this.specification.id;
|
n@193
|
2675 root.setAttribute('type',this.specification.type);
|
n@193
|
2676 var question = document.createElement('question');
|
n@193
|
2677 question.textContent = this.string.textContent;
|
n@193
|
2678 var response = document.createElement('response');
|
n@193
|
2679 var i=0;
|
n@193
|
2680 while(this.options[i].checked == false) {
|
n@193
|
2681 i++;
|
n@193
|
2682 if (i >= this.options.length) {
|
n@193
|
2683 break;
|
n@193
|
2684 }
|
n@193
|
2685 }
|
n@193
|
2686 if (i >= this.options.length) {
|
n@193
|
2687 response.textContent = 'null';
|
n@193
|
2688 } else {
|
n@193
|
2689 response.textContent = this.options[i].getAttribute('setvalue');
|
n@193
|
2690 response.setAttribute('number',i);
|
n@193
|
2691 }
|
n@195
|
2692 console.log('Comment: '+question.textContent);
|
n@195
|
2693 console.log('Response: '+response.textContent);
|
n@193
|
2694 root.appendChild(question);
|
n@193
|
2695 root.appendChild(response);
|
n@520
|
2696 storePoint.XMLDOM.appendChild(root);
|
n@193
|
2697 return root;
|
n@193
|
2698 };
|
n@302
|
2699 this.resize = function()
|
n@302
|
2700 {
|
n@302
|
2701 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2702 if (boxwidth >= 600)
|
n@302
|
2703 {
|
n@302
|
2704 boxwidth = 600;
|
n@302
|
2705 }
|
n@302
|
2706 else if (boxwidth < 400)
|
n@302
|
2707 {
|
n@302
|
2708 boxwidth = 400;
|
n@302
|
2709 }
|
n@302
|
2710 this.holder.style.width = boxwidth+"px";
|
n@302
|
2711 var text = this.holder.children[2];
|
n@302
|
2712 var options = this.holder.children[3];
|
n@302
|
2713 var optCount = options.children.length;
|
n@302
|
2714 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
n@302
|
2715 var options = options.firstChild;
|
n@302
|
2716 var text = text.firstChild;
|
n@302
|
2717 options.style.marginRight = spanMargin;
|
n@302
|
2718 options.style.marginLeft = spanMargin;
|
n@302
|
2719 text.style.marginRight = spanMargin;
|
n@302
|
2720 text.style.marginLeft = spanMargin;
|
n@302
|
2721 while(options.nextSibling != undefined)
|
n@302
|
2722 {
|
n@302
|
2723 options = options.nextSibling;
|
n@302
|
2724 text = text.nextSibling;
|
n@302
|
2725 options.style.marginRight = spanMargin;
|
n@302
|
2726 options.style.marginLeft = spanMargin;
|
n@302
|
2727 text.style.marginRight = spanMargin;
|
n@302
|
2728 text.style.marginLeft = spanMargin;
|
n@302
|
2729 }
|
n@302
|
2730 };
|
n@302
|
2731 this.resize();
|
n@193
|
2732 };
|
n@193
|
2733
|
n@195
|
2734 this.checkboxBox = function(commentQuestion) {
|
n@195
|
2735 this.specification = commentQuestion;
|
n@195
|
2736 // Create document objects to hold the comment boxes
|
n@195
|
2737 this.holder = document.createElement('div');
|
n@195
|
2738 this.holder.className = 'comment-div';
|
n@195
|
2739 // Create a string next to each comment asking for a comment
|
n@195
|
2740 this.string = document.createElement('span');
|
n@195
|
2741 this.string.innerHTML = commentQuestion.statement;
|
n@195
|
2742 var br = document.createElement('br');
|
n@195
|
2743 // Add to the holder.
|
n@195
|
2744 this.holder.appendChild(this.string);
|
n@195
|
2745 this.holder.appendChild(br);
|
n@195
|
2746 this.options = [];
|
n@195
|
2747 this.inputs = document.createElement('div');
|
n@195
|
2748 this.span = document.createElement('div');
|
n@195
|
2749 this.inputs.align = 'center';
|
n@195
|
2750 this.inputs.style.marginLeft = '12px';
|
n@195
|
2751 this.span.style.marginLeft = '12px';
|
n@195
|
2752 this.span.align = 'center';
|
n@195
|
2753 this.span.style.marginTop = '15px';
|
n@195
|
2754
|
n@195
|
2755 var optCount = commentQuestion.options.length;
|
n@195
|
2756 for (var i=0; i<optCount; i++)
|
n@195
|
2757 {
|
n@195
|
2758 var div = document.createElement('div');
|
n@301
|
2759 div.style.width = '80px';
|
n@195
|
2760 div.style.float = 'left';
|
n@195
|
2761 var input = document.createElement('input');
|
n@195
|
2762 input.type = 'checkbox';
|
n@195
|
2763 input.name = commentQuestion.id;
|
n@195
|
2764 input.setAttribute('setvalue',commentQuestion.options[i].name);
|
n@195
|
2765 input.className = 'comment-radio';
|
n@195
|
2766 div.appendChild(input);
|
n@195
|
2767 this.inputs.appendChild(div);
|
n@195
|
2768
|
n@195
|
2769
|
n@195
|
2770 div = document.createElement('div');
|
n@301
|
2771 div.style.width = '80px';
|
n@195
|
2772 div.style.float = 'left';
|
n@195
|
2773 div.align = 'center';
|
n@195
|
2774 var span = document.createElement('span');
|
n@195
|
2775 span.textContent = commentQuestion.options[i].text;
|
n@195
|
2776 span.className = 'comment-radio-span';
|
n@195
|
2777 div.appendChild(span);
|
n@195
|
2778 this.span.appendChild(div);
|
n@195
|
2779 this.options.push(input);
|
n@195
|
2780 }
|
n@195
|
2781 this.holder.appendChild(this.span);
|
n@195
|
2782 this.holder.appendChild(this.inputs);
|
n@195
|
2783
|
n@520
|
2784 this.exportXMLDOM = function(storePoint) {
|
n@520
|
2785 var root = storePoint.parent.document.createElement('comment');
|
n@195
|
2786 root.id = this.specification.id;
|
n@195
|
2787 root.setAttribute('type',this.specification.type);
|
n@195
|
2788 var question = document.createElement('question');
|
n@195
|
2789 question.textContent = this.string.textContent;
|
n@195
|
2790 root.appendChild(question);
|
n@195
|
2791 console.log('Comment: '+question.textContent);
|
n@195
|
2792 for (var i=0; i<this.options.length; i++) {
|
n@195
|
2793 var response = document.createElement('response');
|
n@195
|
2794 response.textContent = this.options[i].checked;
|
n@195
|
2795 response.setAttribute('name',this.options[i].getAttribute('setvalue'));
|
n@195
|
2796 root.appendChild(response);
|
n@195
|
2797 console.log('Response '+response.getAttribute('name') +': '+response.textContent);
|
n@195
|
2798 }
|
n@520
|
2799 storePoint.XMLDOM.appendChild(root);
|
n@195
|
2800 return root;
|
n@195
|
2801 };
|
n@302
|
2802 this.resize = function()
|
n@302
|
2803 {
|
n@302
|
2804 var boxwidth = (window.innerWidth-100)/2;
|
n@302
|
2805 if (boxwidth >= 600)
|
n@302
|
2806 {
|
n@302
|
2807 boxwidth = 600;
|
n@302
|
2808 }
|
n@302
|
2809 else if (boxwidth < 400)
|
n@302
|
2810 {
|
n@302
|
2811 boxwidth = 400;
|
n@302
|
2812 }
|
n@302
|
2813 this.holder.style.width = boxwidth+"px";
|
n@302
|
2814 var text = this.holder.children[2];
|
n@302
|
2815 var options = this.holder.children[3];
|
n@302
|
2816 var optCount = options.children.length;
|
n@302
|
2817 var spanMargin = Math.floor(((boxwidth-20-(optCount*80))/(optCount))/2)+'px';
|
n@302
|
2818 var options = options.firstChild;
|
n@302
|
2819 var text = text.firstChild;
|
n@302
|
2820 options.style.marginRight = spanMargin;
|
n@302
|
2821 options.style.marginLeft = spanMargin;
|
n@302
|
2822 text.style.marginRight = spanMargin;
|
n@302
|
2823 text.style.marginLeft = spanMargin;
|
n@302
|
2824 while(options.nextSibling != undefined)
|
n@302
|
2825 {
|
n@302
|
2826 options = options.nextSibling;
|
n@302
|
2827 text = text.nextSibling;
|
n@302
|
2828 options.style.marginRight = spanMargin;
|
n@302
|
2829 options.style.marginLeft = spanMargin;
|
n@302
|
2830 text.style.marginRight = spanMargin;
|
n@302
|
2831 text.style.marginLeft = spanMargin;
|
n@302
|
2832 }
|
n@302
|
2833 };
|
n@302
|
2834 this.resize();
|
n@195
|
2835 };
|
nicholas@211
|
2836
|
n@193
|
2837 this.createCommentQuestion = function(element) {
|
n@193
|
2838 var node;
|
n@453
|
2839 if (element.type == 'question') {
|
n@193
|
2840 node = new this.commentBox(element);
|
n@193
|
2841 } else if (element.type == 'radio') {
|
n@193
|
2842 node = new this.radioBox(element);
|
n@195
|
2843 } else if (element.type == 'checkbox') {
|
n@195
|
2844 node = new this.checkboxBox(element);
|
n@193
|
2845 }
|
n@193
|
2846 this.commentQuestions.push(node);
|
n@193
|
2847 return node;
|
n@193
|
2848 };
|
n@201
|
2849
|
nicholas@237
|
2850 this.deleteCommentQuestions = function()
|
nicholas@237
|
2851 {
|
nicholas@237
|
2852 this.commentQuestions = [];
|
nicholas@237
|
2853 };
|
nicholas@237
|
2854
|
n@201
|
2855 this.playhead = new function()
|
n@201
|
2856 {
|
n@201
|
2857 this.object = document.createElement('div');
|
n@201
|
2858 this.object.className = 'playhead';
|
n@201
|
2859 this.object.align = 'left';
|
n@201
|
2860 var curTime = document.createElement('div');
|
n@201
|
2861 curTime.style.width = '50px';
|
n@201
|
2862 this.curTimeSpan = document.createElement('span');
|
n@201
|
2863 this.curTimeSpan.textContent = '00:00';
|
n@201
|
2864 curTime.appendChild(this.curTimeSpan);
|
n@201
|
2865 this.object.appendChild(curTime);
|
n@201
|
2866 this.scrubberTrack = document.createElement('div');
|
n@201
|
2867 this.scrubberTrack.className = 'playhead-scrub-track';
|
n@201
|
2868
|
n@201
|
2869 this.scrubberHead = document.createElement('div');
|
n@201
|
2870 this.scrubberHead.id = 'playhead-scrubber';
|
n@201
|
2871 this.scrubberTrack.appendChild(this.scrubberHead);
|
n@201
|
2872 this.object.appendChild(this.scrubberTrack);
|
n@201
|
2873
|
n@201
|
2874 this.timePerPixel = 0;
|
n@201
|
2875 this.maxTime = 0;
|
n@201
|
2876
|
n@204
|
2877 this.playbackObject;
|
n@204
|
2878
|
n@204
|
2879 this.setTimePerPixel = function(audioObject) {
|
n@201
|
2880 //maxTime must be in seconds
|
n@204
|
2881 this.playbackObject = audioObject;
|
n@379
|
2882 this.maxTime = audioObject.buffer.buffer.duration;
|
n@201
|
2883 var width = 490; //500 - 10, 5 each side of the tracker head
|
n@204
|
2884 this.timePerPixel = this.maxTime/490;
|
n@204
|
2885 if (this.maxTime < 60) {
|
n@201
|
2886 this.curTimeSpan.textContent = '0.00';
|
n@201
|
2887 } else {
|
n@201
|
2888 this.curTimeSpan.textContent = '00:00';
|
n@201
|
2889 }
|
n@201
|
2890 };
|
n@201
|
2891
|
n@204
|
2892 this.update = function() {
|
n@201
|
2893 // Update the playhead position, startPlay must be called
|
n@201
|
2894 if (this.timePerPixel > 0) {
|
n@204
|
2895 var time = this.playbackObject.getCurrentPosition();
|
n@498
|
2896 if (time > 0 && time < this.maxTime) {
|
nicholas@267
|
2897 var width = 490;
|
nicholas@267
|
2898 var pix = Math.floor(time/this.timePerPixel);
|
nicholas@267
|
2899 this.scrubberHead.style.left = pix+'px';
|
nicholas@267
|
2900 if (this.maxTime > 60.0) {
|
nicholas@267
|
2901 var secs = time%60;
|
nicholas@267
|
2902 var mins = Math.floor((time-secs)/60);
|
nicholas@267
|
2903 secs = secs.toString();
|
nicholas@267
|
2904 secs = secs.substr(0,2);
|
nicholas@267
|
2905 mins = mins.toString();
|
nicholas@267
|
2906 this.curTimeSpan.textContent = mins+':'+secs;
|
nicholas@267
|
2907 } else {
|
nicholas@267
|
2908 time = time.toString();
|
nicholas@267
|
2909 this.curTimeSpan.textContent = time.substr(0,4);
|
nicholas@267
|
2910 }
|
n@201
|
2911 } else {
|
nicholas@267
|
2912 this.scrubberHead.style.left = '0px';
|
nicholas@267
|
2913 if (this.maxTime < 60) {
|
nicholas@267
|
2914 this.curTimeSpan.textContent = '0.00';
|
nicholas@267
|
2915 } else {
|
nicholas@267
|
2916 this.curTimeSpan.textContent = '00:00';
|
nicholas@267
|
2917 }
|
n@201
|
2918 }
|
n@201
|
2919 }
|
n@201
|
2920 };
|
n@204
|
2921
|
n@204
|
2922 this.interval = undefined;
|
n@204
|
2923
|
n@204
|
2924 this.start = function() {
|
n@204
|
2925 if (this.playbackObject != undefined && this.interval == undefined) {
|
nicholas@267
|
2926 if (this.maxTime < 60) {
|
nicholas@267
|
2927 this.interval = setInterval(function(){interfaceContext.playhead.update();},10);
|
nicholas@267
|
2928 } else {
|
nicholas@267
|
2929 this.interval = setInterval(function(){interfaceContext.playhead.update();},100);
|
nicholas@267
|
2930 }
|
n@204
|
2931 }
|
n@204
|
2932 };
|
n@204
|
2933 this.stop = function() {
|
n@204
|
2934 clearInterval(this.interval);
|
n@204
|
2935 this.interval = undefined;
|
n@527
|
2936 this.scrubberHead.style.left = '0px';
|
nicholas@267
|
2937 if (this.maxTime < 60) {
|
nicholas@267
|
2938 this.curTimeSpan.textContent = '0.00';
|
nicholas@267
|
2939 } else {
|
nicholas@267
|
2940 this.curTimeSpan.textContent = '00:00';
|
nicholas@267
|
2941 }
|
n@204
|
2942 };
|
n@201
|
2943 };
|
n@483
|
2944
|
n@483
|
2945 this.volume = new function()
|
n@483
|
2946 {
|
n@483
|
2947 // An in-built volume module which can be viewed on page
|
n@483
|
2948 // Includes trackers on page-by-page data
|
n@483
|
2949 // Volume does NOT reset to 0dB on each page load
|
n@483
|
2950 this.valueLin = 1.0;
|
n@483
|
2951 this.valueDB = 0.0;
|
n@483
|
2952 this.object = document.createElement('div');
|
n@483
|
2953 this.object.id = 'master-volume-holder';
|
n@483
|
2954 this.slider = document.createElement('input');
|
n@483
|
2955 this.slider.id = 'master-volume-control';
|
n@483
|
2956 this.slider.type = 'range';
|
n@483
|
2957 this.valueText = document.createElement('span');
|
n@483
|
2958 this.valueText.id = 'master-volume-feedback';
|
n@483
|
2959 this.valueText.textContent = '0dB';
|
n@483
|
2960
|
n@483
|
2961 this.slider.min = -60;
|
n@483
|
2962 this.slider.max = 12;
|
n@483
|
2963 this.slider.value = 0;
|
n@483
|
2964 this.slider.step = 1;
|
n@483
|
2965 this.slider.onmousemove = function(event)
|
n@483
|
2966 {
|
n@483
|
2967 interfaceContext.volume.valueDB = event.currentTarget.value;
|
n@483
|
2968 interfaceContext.volume.valueLin = decibelToLinear(interfaceContext.volume.valueDB);
|
n@483
|
2969 interfaceContext.volume.valueText.textContent = interfaceContext.volume.valueDB+'dB';
|
n@483
|
2970 audioEngineContext.outputGain.gain.value = interfaceContext.volume.valueLin;
|
n@483
|
2971 }
|
n@483
|
2972 this.slider.onmouseup = function(event)
|
n@483
|
2973 {
|
n@526
|
2974 var storePoint = testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].getAllElementsByName('volumeTracker');
|
n@483
|
2975 if (storePoint.length == 0)
|
n@483
|
2976 {
|
n@483
|
2977 storePoint = storage.document.createElement('metricresult');
|
n@483
|
2978 storePoint.setAttribute('name','volumeTracker');
|
n@526
|
2979 testState.currentStore.XMLDOM.getElementsByTagName('metric')[0].appendChild(storePoint);
|
n@483
|
2980 }
|
n@483
|
2981 else {
|
n@483
|
2982 storePoint = storePoint[0];
|
n@483
|
2983 }
|
n@483
|
2984 var node = storage.document.createElement('movement');
|
n@483
|
2985 node.setAttribute('test-time',audioEngineContext.timer.getTestTime());
|
n@483
|
2986 node.setAttribute('volume',interfaceContext.volume.valueDB);
|
n@483
|
2987 node.setAttribute('format','dBFS');
|
n@483
|
2988 storePoint.appendChild(node);
|
n@483
|
2989 }
|
n@483
|
2990
|
n@484
|
2991 var title = document.createElement('div');
|
n@484
|
2992 title.innerHTML = '<span>Master Volume Control</span>';
|
n@484
|
2993 title.style.fontSize = '0.75em';
|
n@484
|
2994 title.style.width = "100%";
|
n@484
|
2995 title.align = 'center';
|
n@484
|
2996 this.object.appendChild(title);
|
n@484
|
2997
|
n@483
|
2998 this.object.appendChild(this.slider);
|
n@483
|
2999 this.object.appendChild(this.valueText);
|
n@483
|
3000 }
|
nicholas@235
|
3001 // Global Checkers
|
nicholas@235
|
3002 // These functions will help enforce the checkers
|
nicholas@235
|
3003 this.checkHiddenAnchor = function()
|
nicholas@235
|
3004 {
|
n@453
|
3005 for (var ao of audioEngineContext.audioObjects)
|
nicholas@235
|
3006 {
|
n@453
|
3007 if (ao.specification.type == "anchor")
|
nicholas@235
|
3008 {
|
n@454
|
3009 if (ao.interfaceDOM.getValue() > (ao.specification.marker/100) && ao.specification.marker > 0) {
|
n@453
|
3010 // Anchor is not set below
|
n@453
|
3011 console.log('Anchor node not below marker value');
|
n@453
|
3012 alert('Please keep listening');
|
n@498
|
3013 this.storeErrorNode('Anchor node not below marker value');
|
n@453
|
3014 return false;
|
n@453
|
3015 }
|
nicholas@235
|
3016 }
|
nicholas@235
|
3017 }
|
nicholas@235
|
3018 return true;
|
nicholas@235
|
3019 };
|
nicholas@235
|
3020
|
nicholas@235
|
3021 this.checkHiddenReference = function()
|
nicholas@235
|
3022 {
|
n@453
|
3023 for (var ao of audioEngineContext.audioObjects)
|
nicholas@235
|
3024 {
|
n@453
|
3025 if (ao.specification.type == "reference")
|
nicholas@235
|
3026 {
|
n@454
|
3027 if (ao.interfaceDOM.getValue() < (ao.specification.marker/100) && ao.specification.marker > 0) {
|
n@453
|
3028 // Anchor is not set below
|
n@498
|
3029 console.log('Reference node not above marker value');
|
n@498
|
3030 this.storeErrorNode('Reference node not above marker value');
|
n@453
|
3031 alert('Please keep listening');
|
n@453
|
3032 return false;
|
n@453
|
3033 }
|
nicholas@235
|
3034 }
|
nicholas@235
|
3035 }
|
nicholas@235
|
3036 return true;
|
nicholas@235
|
3037 };
|
n@366
|
3038
|
n@366
|
3039 this.checkFragmentsFullyPlayed = function ()
|
n@366
|
3040 {
|
n@366
|
3041 // Checks the entire file has been played back
|
n@366
|
3042 // NOTE ! This will return true IF playback is Looped!!!
|
n@366
|
3043 if (audioEngineContext.loopPlayback)
|
n@366
|
3044 {
|
n@366
|
3045 console.log("WARNING - Looped source: Cannot check fragments are fully played");
|
n@366
|
3046 return true;
|
n@366
|
3047 }
|
n@366
|
3048 var check_pass = true;
|
n@366
|
3049 var error_obj = [];
|
n@366
|
3050 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
|
n@366
|
3051 {
|
n@366
|
3052 var object = audioEngineContext.audioObjects[i];
|
nicholas@415
|
3053 var time = object.buffer.buffer.duration;
|
n@366
|
3054 var metric = object.metric;
|
n@366
|
3055 var passed = false;
|
n@366
|
3056 for (var j=0; j<metric.listenTracker.length; j++)
|
n@366
|
3057 {
|
n@366
|
3058 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
|
n@366
|
3059 var start_time = Number(bt[0].getAttribute('start'));
|
n@366
|
3060 var stop_time = Number(bt[0].getAttribute('stop'));
|
n@366
|
3061 var delta = stop_time - start_time;
|
n@366
|
3062 if (delta >= time)
|
n@366
|
3063 {
|
n@366
|
3064 passed = true;
|
n@366
|
3065 break;
|
n@366
|
3066 }
|
n@366
|
3067 }
|
n@366
|
3068 if (passed == false)
|
n@366
|
3069 {
|
n@366
|
3070 check_pass = false;
|
n@598
|
3071 console.log("Continue listening to track-"+object.interfaceDOM.getPresentedId());
|
n@598
|
3072 error_obj.push(object.interfaceDOM.getPresentedId());
|
n@366
|
3073 }
|
n@366
|
3074 }
|
n@366
|
3075 if (check_pass == false)
|
n@366
|
3076 {
|
nicholas@415
|
3077 var str_start = "You have not completely listened to fragments ";
|
n@366
|
3078 for (var i=0; i<error_obj.length; i++)
|
n@366
|
3079 {
|
n@366
|
3080 str_start += error_obj[i];
|
n@366
|
3081 if (i != error_obj.length-1)
|
n@366
|
3082 {
|
n@366
|
3083 str_start += ', ';
|
n@366
|
3084 }
|
n@366
|
3085 }
|
n@366
|
3086 str_start += ". Please keep listening";
|
n@366
|
3087 console.log("[ALERT]: "+str_start);
|
n@498
|
3088 this.storeErrorNode("[ALERT]: "+str_start);
|
n@366
|
3089 alert(str_start);
|
n@366
|
3090 }
|
n@366
|
3091 };
|
nicholas@421
|
3092 this.checkAllMoved = function()
|
nicholas@421
|
3093 {
|
nicholas@421
|
3094 var str = "You have not moved ";
|
nicholas@421
|
3095 var failed = [];
|
n@469
|
3096 for (var ao of audioEngineContext.audioObjects)
|
nicholas@421
|
3097 {
|
n@469
|
3098 if(ao.metric.wasMoved == false && ao.interfaceDOM.canMove() == true)
|
nicholas@421
|
3099 {
|
n@469
|
3100 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@421
|
3101 }
|
nicholas@421
|
3102 }
|
nicholas@421
|
3103 if (failed.length == 0)
|
nicholas@421
|
3104 {
|
nicholas@421
|
3105 return true;
|
nicholas@421
|
3106 } else if (failed.length == 1)
|
nicholas@421
|
3107 {
|
nicholas@421
|
3108 str += 'track '+failed[0];
|
nicholas@421
|
3109 } else {
|
nicholas@421
|
3110 str += 'tracks ';
|
nicholas@421
|
3111 for (var i=0; i<failed.length-1; i++)
|
nicholas@421
|
3112 {
|
nicholas@421
|
3113 str += failed[i]+', ';
|
nicholas@421
|
3114 }
|
nicholas@421
|
3115 str += 'and '+failed[i];
|
nicholas@421
|
3116 }
|
nicholas@421
|
3117 str +='.';
|
nicholas@421
|
3118 alert(str);
|
nicholas@421
|
3119 console.log(str);
|
n@498
|
3120 this.storeErrorNode(str);
|
nicholas@421
|
3121 return false;
|
nicholas@421
|
3122 };
|
nicholas@421
|
3123 this.checkAllPlayed = function()
|
nicholas@421
|
3124 {
|
nicholas@421
|
3125 var str = "You have not played ";
|
nicholas@421
|
3126 var failed = [];
|
n@469
|
3127 for (var ao of audioEngineContext.audioObjects)
|
nicholas@421
|
3128 {
|
n@469
|
3129 if(ao.metric.wasListenedTo == false)
|
nicholas@421
|
3130 {
|
n@469
|
3131 failed.push(ao.interfaceDOM.getPresentedId());
|
nicholas@421
|
3132 }
|
nicholas@421
|
3133 }
|
nicholas@421
|
3134 if (failed.length == 0)
|
nicholas@421
|
3135 {
|
nicholas@421
|
3136 return true;
|
nicholas@421
|
3137 } else if (failed.length == 1)
|
nicholas@421
|
3138 {
|
nicholas@421
|
3139 str += 'track '+failed[0];
|
nicholas@421
|
3140 } else {
|
nicholas@421
|
3141 str += 'tracks ';
|
nicholas@421
|
3142 for (var i=0; i<failed.length-1; i++)
|
nicholas@421
|
3143 {
|
nicholas@421
|
3144 str += failed[i]+', ';
|
nicholas@421
|
3145 }
|
nicholas@421
|
3146 str += 'and '+failed[i];
|
nicholas@421
|
3147 }
|
nicholas@421
|
3148 str +='.';
|
nicholas@421
|
3149 alert(str);
|
nicholas@421
|
3150 console.log(str);
|
n@498
|
3151 this.storeErrorNode(str);
|
nicholas@421
|
3152 return false;
|
nicholas@421
|
3153 };
|
n@498
|
3154
|
n@498
|
3155 this.storeErrorNode = function(errorMessage)
|
n@498
|
3156 {
|
n@498
|
3157 var time = audioEngineContext.timer.getTestTime();
|
n@498
|
3158 var node = storage.document.createElement('error');
|
n@498
|
3159 node.setAttribute('time',time);
|
n@498
|
3160 node.textContent = errorMessage;
|
n@498
|
3161 testState.currentStore.XMLDOM.appendChild(node);
|
n@498
|
3162 };
|
n@453
|
3163 }
|
n@453
|
3164
|
n@453
|
3165 function Storage()
|
n@453
|
3166 {
|
n@453
|
3167 // Holds results in XML format until ready for collection
|
n@453
|
3168 this.globalPreTest = null;
|
n@453
|
3169 this.globalPostTest = null;
|
n@453
|
3170 this.testPages = [];
|
n@602
|
3171 this.document = null;
|
n@602
|
3172 this.root = null;
|
n@453
|
3173 this.state = 0;
|
n@453
|
3174
|
n@602
|
3175 this.initialise = function(existingStore)
|
n@453
|
3176 {
|
n@602
|
3177 if (existingStore == undefined) {
|
n@584
|
3178 // We need to get the sessionKey
|
n@584
|
3179 this.SessionKey.generateKey();
|
n@602
|
3180 this.document = document.implementation.createDocument(null,"waetresult");
|
n@602
|
3181 this.root = this.document.childNodes[0];
|
n@589
|
3182 var projectDocument = specification.projectXML;
|
n@589
|
3183 projectDocument.setAttribute('file-name',url);
|
n@589
|
3184 this.root.appendChild(projectDocument);
|
n@589
|
3185 this.root.appendChild(returnDateNode());
|
n@589
|
3186 this.root.appendChild(interfaceContext.returnNavigator());
|
n@584
|
3187 } else {
|
n@602
|
3188 this.document = existingStore;
|
n@602
|
3189 this.root = existingStore.children[0];
|
n@602
|
3190 this.SessionKey.key = this.root.getAttribute("key");
|
n@584
|
3191 }
|
n@589
|
3192 if (specification.preTest != undefined){this.globalPreTest = new this.surveyNode(this,this.root,specification.preTest);}
|
n@602
|
3193 if (specification.postTest != undefined){this.globalPostTest = new this.surveyNode(this,this.root,specification.postTest);}
|
n@453
|
3194 };
|
n@584
|
3195
|
n@584
|
3196 this.SessionKey = {
|
n@584
|
3197 key: null,
|
n@584
|
3198 request: new XMLHttpRequest(),
|
n@584
|
3199 parent: this,
|
n@584
|
3200 handleEvent: function() {
|
n@584
|
3201 var parse = new DOMParser();
|
n@584
|
3202 var xml = parse.parseFromString(this.request.response,"text/xml");
|
n@584
|
3203 if (xml.getAllElementsByTagName("state")[0].textContent == "OK") {
|
n@584
|
3204 this.key = xml.getAllElementsByTagName("key")[0].textContent;
|
n@589
|
3205 this.parent.root.setAttribute("key",this.key);
|
n@589
|
3206 this.parent.root.setAttribute("state","empty");
|
n@584
|
3207 } else {
|
n@584
|
3208 this.generateKey();
|
n@584
|
3209 }
|
n@584
|
3210 },
|
n@584
|
3211 generateKey: function() {
|
n@584
|
3212 var temp_key = randomString(32);
|
n@584
|
3213 this.request.open("GET","keygen.php?key="+temp_key,true);
|
n@584
|
3214 this.request.addEventListener("load",this);
|
n@584
|
3215 this.request.send();
|
n@587
|
3216 },
|
n@589
|
3217 update: function() {
|
n@589
|
3218 this.parent.root.setAttribute("state","update");
|
n@589
|
3219 var xmlhttp = new XMLHttpRequest();
|
n@589
|
3220 xmlhttp.open("POST",specification.projectReturn+"?key="+this.key);
|
n@589
|
3221 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
|
n@589
|
3222 xmlhttp.onerror = function(){
|
n@589
|
3223 console.log('Error updating file to server!');
|
n@589
|
3224 };
|
n@589
|
3225 var hold = document.createElement("div");
|
n@589
|
3226 var clone = this.parent.root.cloneNode(true);
|
n@589
|
3227 hold.appendChild(clone);
|
n@589
|
3228 xmlhttp.onload = function() {
|
n@589
|
3229 if (this.status >= 300) {
|
n@589
|
3230 console.log("WARNING - Could not update at this time");
|
n@589
|
3231 } else {
|
n@589
|
3232 var parser = new DOMParser();
|
n@589
|
3233 var xmlDoc = parser.parseFromString(xmlhttp.responseText, "application/xml");
|
n@589
|
3234 var response = xmlDoc.getElementsByTagName('response')[0];
|
n@589
|
3235 if (response.getAttribute("state") == "OK") {
|
n@589
|
3236 var file = response.getElementsByTagName("file")[0];
|
n@589
|
3237 console.log("Intermediate save: OK, written "+file.getAttribute("bytes")+"B");
|
n@589
|
3238 } else {
|
n@589
|
3239 var message = response.getElementsByTagName("message");
|
n@589
|
3240 console.log("Intermediate save: Error! "+message.textContent);
|
n@589
|
3241 }
|
n@587
|
3242 }
|
n@587
|
3243 }
|
n@589
|
3244 xmlhttp.send([hold.innerHTML]);
|
n@584
|
3245 }
|
n@584
|
3246 }
|
n@453
|
3247
|
n@453
|
3248 this.createTestPageStore = function(specification)
|
n@453
|
3249 {
|
n@453
|
3250 var store = new this.pageNode(this,specification);
|
n@453
|
3251 this.testPages.push(store);
|
n@453
|
3252 return this.testPages[this.testPages.length-1];
|
n@453
|
3253 };
|
n@453
|
3254
|
n@453
|
3255 this.surveyNode = function(parent,root,specification)
|
n@453
|
3256 {
|
n@453
|
3257 this.specification = specification;
|
n@453
|
3258 this.parent = parent;
|
n@602
|
3259 this.state = "empty";
|
n@453
|
3260 this.XMLDOM = this.parent.document.createElement('survey');
|
n@453
|
3261 this.XMLDOM.setAttribute('location',this.specification.location);
|
n@602
|
3262 this.XMLDOM.setAttribute("state",this.state);
|
n@453
|
3263 for (var optNode of this.specification.options)
|
n@453
|
3264 {
|
n@453
|
3265 if (optNode.type != 'statement')
|
n@453
|
3266 {
|
n@453
|
3267 var node = this.parent.document.createElement('surveyresult');
|
n@602
|
3268 node.setAttribute("ref",optNode.id);
|
n@453
|
3269 node.setAttribute('type',optNode.type);
|
n@453
|
3270 this.XMLDOM.appendChild(node);
|
n@453
|
3271 }
|
n@453
|
3272 }
|
n@453
|
3273 root.appendChild(this.XMLDOM);
|
n@453
|
3274
|
n@453
|
3275 this.postResult = function(node)
|
n@453
|
3276 {
|
n@453
|
3277 // From popup: node is the popupOption node containing both spec. and results
|
n@453
|
3278 // ID is the position
|
n@453
|
3279 if (node.specification.type == 'statement'){return;}
|
n@602
|
3280 var surveyresult = this.XMLDOM.children[0];
|
n@602
|
3281 while(surveyresult != null) {
|
n@602
|
3282 if (surveyresult.getAttribute("ref") == node.specification.id)
|
n@602
|
3283 {
|
n@602
|
3284 break;
|
n@602
|
3285 }
|
n@602
|
3286 surveyresult = surveyresult.nextElementSibling;
|
n@602
|
3287 }
|
n@453
|
3288 switch(node.specification.type)
|
n@453
|
3289 {
|
n@453
|
3290 case "number":
|
n@453
|
3291 case "question":
|
n@453
|
3292 var child = this.parent.document.createElement('response');
|
n@453
|
3293 child.textContent = node.response;
|
n@453
|
3294 surveyresult.appendChild(child);
|
n@453
|
3295 break;
|
n@453
|
3296 case "radio":
|
n@453
|
3297 var child = this.parent.document.createElement('response');
|
n@453
|
3298 child.setAttribute('name',node.response.name);
|
n@453
|
3299 child.textContent = node.response.text;
|
n@453
|
3300 surveyresult.appendChild(child);
|
n@453
|
3301 break;
|
n@453
|
3302 case "checkbox":
|
n@453
|
3303 for (var i=0; i<node.response.length; i++)
|
n@453
|
3304 {
|
n@453
|
3305 var checkNode = this.parent.document.createElement('response');
|
n@476
|
3306 checkNode.setAttribute('name',node.response[i].name);
|
n@476
|
3307 checkNode.setAttribute('checked',node.response[i].checked);
|
n@455
|
3308 surveyresult.appendChild(checkNode);
|
n@453
|
3309 }
|
n@453
|
3310 break;
|
n@453
|
3311 }
|
n@453
|
3312 };
|
n@602
|
3313 this.complete = function() {
|
n@602
|
3314 this.state = "complete";
|
n@602
|
3315 this.XMLDOM.setAttribute("state",this.state);
|
n@602
|
3316 }
|
n@453
|
3317 };
|
n@453
|
3318
|
n@453
|
3319 this.pageNode = function(parent,specification)
|
n@453
|
3320 {
|
n@453
|
3321 // Create one store per test page
|
n@453
|
3322 this.specification = specification;
|
n@453
|
3323 this.parent = parent;
|
n@602
|
3324 this.state = "empty";
|
n@453
|
3325 this.XMLDOM = this.parent.document.createElement('page');
|
n@602
|
3326 this.XMLDOM.setAttribute('ref',specification.id);
|
n@453
|
3327 this.XMLDOM.setAttribute('presentedId',specification.presentedId);
|
n@602
|
3328 this.XMLDOM.setAttribute("state",this.state);
|
n@474
|
3329 if (specification.preTest != undefined){this.preTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.preTest);}
|
n@474
|
3330 if (specification.postTest != undefined){this.postTest = new this.parent.surveyNode(this.parent,this.XMLDOM,this.specification.postTest);}
|
n@453
|
3331
|
n@453
|
3332 // Add any page metrics
|
n@453
|
3333 var page_metric = this.parent.document.createElement('metric');
|
n@453
|
3334 this.XMLDOM.appendChild(page_metric);
|
n@453
|
3335
|
n@453
|
3336 // Add the audioelement
|
n@453
|
3337 for (var element of this.specification.audioElements)
|
n@453
|
3338 {
|
n@453
|
3339 var aeNode = this.parent.document.createElement('audioelement');
|
n@602
|
3340 aeNode.setAttribute('ref',element.id);
|
n@602
|
3341 if (element.name != undefined){aeNode.setAttribute('name',element.name)};
|
n@453
|
3342 aeNode.setAttribute('type',element.type);
|
n@453
|
3343 aeNode.setAttribute('url', element.url);
|
n@453
|
3344 aeNode.setAttribute('gain', element.gain);
|
n@453
|
3345 if (element.type == 'anchor' || element.type == 'reference')
|
n@453
|
3346 {
|
n@453
|
3347 if (element.marker > 0)
|
n@453
|
3348 {
|
n@453
|
3349 aeNode.setAttribute('marker',element.marker);
|
n@453
|
3350 }
|
n@453
|
3351 }
|
n@453
|
3352 var ae_metric = this.parent.document.createElement('metric');
|
n@453
|
3353 aeNode.appendChild(ae_metric);
|
n@453
|
3354 this.XMLDOM.appendChild(aeNode);
|
n@453
|
3355 }
|
n@453
|
3356
|
n@453
|
3357 this.parent.root.appendChild(this.XMLDOM);
|
n@602
|
3358
|
n@602
|
3359 this.complete = function() {
|
n@602
|
3360 this.state = "complete";
|
n@602
|
3361 this.XMLDOM.setAttribute("state","complete");
|
n@602
|
3362 }
|
n@453
|
3363 };
|
n@589
|
3364 this.update = function() {
|
n@589
|
3365 this.SessionKey.update();
|
n@589
|
3366 }
|
n@453
|
3367 this.finish = function()
|
n@453
|
3368 {
|
n@453
|
3369 if (this.state == 0)
|
n@453
|
3370 {
|
n@589
|
3371 this.update();
|
n@453
|
3372 }
|
n@453
|
3373 this.state = 1;
|
n@453
|
3374 return this.root;
|
n@453
|
3375 };
|
n@453
|
3376 }
|