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