comparison core.js @ 444:9c9fd68693b1

Merge. Pull of revision info from dev_main.
author Nicholas Jillings <n.g.r.jillings@se14.qmul.ac.uk>
date Wed, 23 Dec 2015 14:36:00 +0000
parents da368f23bcd3
children 63c4163fc705
comparison
equal deleted inserted replaced
442:1081368deed7 444:9c9fd68693b1
14 var testState; 14 var testState;
15 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order 15 var currentTrackOrder = []; // Hold the current XML tracks in their (randomised) order
16 var audioEngineContext; // The custome AudioEngine object 16 var audioEngineContext; // The custome AudioEngine object
17 var projectReturn; // Hold the URL for the return 17 var projectReturn; // Hold the URL for the return
18 18
19
20 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
21 AudioBufferSourceNode.prototype.owner = undefined;
22
23 window.onload = function() { 19 window.onload = function() {
24 // Function called once the browser has loaded all files. 20 // Function called once the browser has loaded all files.
25 // This should perform any initial commands such as structure / loading documents 21 // This should perform any initial commands such as structure / loading documents
26 22
27 // Create a web audio API context 23 // Create a web audio API context
30 audioContext = new AudioContext; 26 audioContext = new AudioContext;
31 27
32 // Create test state 28 // Create test state
33 testState = new stateMachine(); 29 testState = new stateMachine();
34 30
35 // Create the audio engine object
36 audioEngineContext = new AudioEngine();
37
38 // Create the popup interface object 31 // Create the popup interface object
39 popup = new interfacePopup(); 32 popup = new interfacePopup();
40 33
41 // Create the specification object 34 // Create the specification object
42 specification = new Specification(); 35 specification = new Specification();
43 36
44 // Create the interface object 37 // Create the interface object
45 interfaceContext = new Interface(specification); 38 interfaceContext = new Interface(specification);
39 // Define window callbacks for interface
40 window.onresize = function(event){interfaceContext.resizeWindow(event);};
41
42 // Add a prototype to the bufferSourceNode to reference to the audioObject holding it
43 AudioBufferSourceNode.prototype.owner = undefined;
44 // Add a prototype to the bufferNode to hold the desired LINEAR gain
45 AudioBuffer.prototype.gain = 1.0;
46 // Add a prototype to the bufferNode to hold the computed LUFS loudness
47 AudioBuffer.prototype.lufs = -23;
46 }; 48 };
49
50 function loadProjectSpec(url) {
51 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
52 // If url is null, request client to upload project XML document
53 var r = new XMLHttpRequest();
54 r.open('GET',url,true);
55 r.onload = function() {
56 loadProjectSpecCallback(r.response);
57 };
58 r.send();
59 };
60
61 function loadProjectSpecCallback(response) {
62 // Function called after asynchronous download of XML project specification
63 //var decode = $.parseXML(response);
64 //projectXML = $(decode);
65
66 var parse = new DOMParser();
67 projectXML = parse.parseFromString(response,'text/xml');
68 var errorNode = projectXML.getElementsByTagName('parsererror');
69 if (errorNode.length >= 1)
70 {
71 var msg = document.createElement("h3");
72 msg.textContent = "FATAL ERROR";
73 var span = document.createElement("span");
74 span.textContent = "The XML parser returned the following errors when decoding your XML file";
75 document.getElementsByTagName('body')[0].innerHTML = null;
76 document.getElementsByTagName('body')[0].appendChild(msg);
77 document.getElementsByTagName('body')[0].appendChild(span);
78 document.getElementsByTagName('body')[0].appendChild(errorNode[0]);
79 return;
80 }
81
82 // Build the specification
83 specification.decode(projectXML);
84
85 // Detect the interface to use and load the relevant javascripts.
86 var interfaceJS = document.createElement('script');
87 interfaceJS.setAttribute("type","text/javascript");
88 if (specification.interfaceType == 'APE') {
89 interfaceJS.setAttribute("src","ape.js");
90
91 // APE comes with a css file
92 var css = document.createElement('link');
93 css.rel = 'stylesheet';
94 css.type = 'text/css';
95 css.href = 'ape.css';
96
97 document.getElementsByTagName("head")[0].appendChild(css);
98 } else if (specification.interfaceType == "MUSHRA")
99 {
100 interfaceJS.setAttribute("src","mushra.js");
101
102 // MUSHRA comes with a css file
103 var css = document.createElement('link');
104 css.rel = 'stylesheet';
105 css.type = 'text/css';
106 css.href = 'mushra.css';
107
108 document.getElementsByTagName("head")[0].appendChild(css);
109 }
110 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
111
112 // Create the audio engine object
113 audioEngineContext = new AudioEngine(specification);
114
115 testState.stateMap.push(specification.preTest);
116
117 $(specification.audioHolders).each(function(index,elem){
118 testState.stateMap.push(elem);
119 $(elem.audioElements).each(function(i,audioElem){
120 var URL = audioElem.parent.hostURL + audioElem.url;
121 var buffer = null;
122 for (var i=0; i<audioEngineContext.buffers.length; i++)
123 {
124 if (URL == audioEngineContext.buffers[i].url)
125 {
126 buffer = audioEngineContext.buffers[i];
127 break;
128 }
129 }
130 if (buffer == null)
131 {
132 buffer = new audioEngineContext.bufferObj();
133 buffer.getMedia(URL);
134 audioEngineContext.buffers.push(buffer);
135 }
136 });
137 });
138
139 testState.stateMap.push(specification.postTest);
140 }
141
142 function createProjectSave(destURL) {
143 // Save the data from interface into XML and send to destURL
144 // If destURL is null then download XML in client
145 // Now time to render file locally
146 var xmlDoc = interfaceXMLSave();
147 var parent = document.createElement("div");
148 parent.appendChild(xmlDoc);
149 var file = [parent.innerHTML];
150 if (destURL == "null" || destURL == undefined) {
151 var bb = new Blob(file,{type : 'application/xml'});
152 var dnlk = window.URL.createObjectURL(bb);
153 var a = document.createElement("a");
154 a.hidden = '';
155 a.href = dnlk;
156 a.download = "save.xml";
157 a.textContent = "Save File";
158
159 popup.showPopup();
160 popup.popupContent.innerHTML = null;
161 popup.popupContent.appendChild(a);
162 } else {
163 var xmlhttp = new XMLHttpRequest;
164 xmlhttp.open("POST",destURL,true);
165 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
166 xmlhttp.onerror = function(){
167 console.log('Error saving file to server! Presenting download locally');
168 createProjectSave(null);
169 };
170 xmlhttp.onreadystatechange = function() {
171 console.log(xmlhttp.status);
172 if (xmlhttp.status != 200 && xmlhttp.readyState == 4) {
173 createProjectSave(null);
174 } else {
175 if (xmlhttp.responseXML == null)
176 {
177 return createProjectSave(null);
178 }
179 var response = xmlhttp.responseXML.childNodes[0];
180 if (response.getAttribute('state') == "OK")
181 {
182 var file = response.getElementsByTagName('file')[0];
183 console.log('Save OK: Filename '+file.textContent+','+file.getAttribute('bytes')+'B');
184 popup.showPopup();
185 popup.popupContent.innerHTML = null;
186 popup.popupContent.textContent = "Thank you!";
187 } else {
188 var message = response.getElementsByTagName('message')[0];
189 errorSessionDump(message.textContent);
190 }
191 }
192 };
193 xmlhttp.send(file);
194 }
195 }
196
197 function errorSessionDump(msg){
198 // Create the partial interface XML save
199 // Include error node with message on why the dump occured
200 popup.showPopup();
201 popup.popupContent.innerHTML = null;
202 var err = document.createElement('error');
203 var parent = document.createElement("div");
204 if (typeof msg === "object")
205 {
206 err.appendChild(msg);
207 popup.popupContent.appendChild(msg);
208
209 } else {
210 err.textContent = msg;
211 popup.popupContent.innerHTML = "ERROR : "+msg;
212 }
213 var xmlDoc = interfaceXMLSave();
214 xmlDoc.appendChild(err);
215 parent.appendChild(xmlDoc);
216 var file = [parent.innerHTML];
217 var bb = new Blob(file,{type : 'application/xml'});
218 var dnlk = window.URL.createObjectURL(bb);
219 var a = document.createElement("a");
220 a.hidden = '';
221 a.href = dnlk;
222 a.download = "save.xml";
223 a.textContent = "Save File";
224
225
226
227 popup.popupContent.appendChild(a);
228 }
229
230 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
231 function interfaceXMLSave(){
232 // Create the XML string to be exported with results
233 var xmlDoc = document.createElement("BrowserEvaluationResult");
234 var projectDocument = specification.projectXML;
235 projectDocument.setAttribute('file-name',url);
236 xmlDoc.appendChild(projectDocument);
237 xmlDoc.appendChild(returnDateNode());
238 xmlDoc.appendChild(interfaceContext.returnNavigator());
239 for (var i=0; i<testState.stateResults.length; i++)
240 {
241 xmlDoc.appendChild(testState.stateResults[i]);
242 }
243
244 return xmlDoc;
245 }
246
247 function linearToDecibel(gain)
248 {
249 return 20.0*Math.log10(gain);
250 }
251
252 function decibelToLinear(gain)
253 {
254 return Math.pow(10,gain/20.0);
255 }
47 256
48 function interfacePopup() { 257 function interfacePopup() {
49 // Creates an object to manage the popup 258 // Creates an object to manage the popup
50 this.popup = null; 259 this.popup = null;
51 this.popupContent = null; 260 this.popupContent = null;
54 this.buttonProceed = null; 263 this.buttonProceed = null;
55 this.buttonPrevious = null; 264 this.buttonPrevious = null;
56 this.popupOptions = null; 265 this.popupOptions = null;
57 this.currentIndex = null; 266 this.currentIndex = null;
58 this.responses = null; 267 this.responses = null;
268 $(window).keypress(function(e){
269 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
270 {
271 console.log(e);
272 popup.buttonProceed.onclick();
273 e.preventDefault();
274 }
275 });
59 276
60 this.createPopup = function(){ 277 this.createPopup = function(){
61 // Create popup window interface 278 // Create popup window interface
62 var insertPoint = document.getElementById("topLevelBody"); 279 var insertPoint = document.getElementById("topLevelBody");
63 var blank = document.createElement('div'); 280 var blank = document.createElement('div');
131 this.popup.style.zIndex = 3; 348 this.popup.style.zIndex = 3;
132 this.popup.style.visibility = 'visible'; 349 this.popup.style.visibility = 'visible';
133 var blank = document.getElementsByClassName('testHalt')[0]; 350 var blank = document.getElementsByClassName('testHalt')[0];
134 blank.style.zIndex = 2; 351 blank.style.zIndex = 2;
135 blank.style.visibility = 'visible'; 352 blank.style.visibility = 'visible';
136 $(window).keypress(function(e){
137 if (e.keyCode == 13 && popup.popup.style.visibility == 'visible')
138 {
139 popup.buttonProceed.onclick();
140 }
141 });
142 }; 353 };
143 354
144 this.hidePopup = function(){ 355 this.hidePopup = function(){
145 this.popup.style.zIndex = -1; 356 this.popup.style.zIndex = -1;
146 this.popup.style.visibility = 'hidden'; 357 this.popup.style.visibility = 'hidden';
147 var blank = document.getElementsByClassName('testHalt')[0]; 358 var blank = document.getElementsByClassName('testHalt')[0];
148 blank.style.zIndex = -2; 359 blank.style.zIndex = -2;
149 blank.style.visibility = 'hidden'; 360 blank.style.visibility = 'hidden';
150 this.buttonPrevious.style.visibility = 'inherit'; 361 this.buttonPrevious.style.visibility = 'inherit';
151 $(window).keypress(function(e){});
152 }; 362 };
153 363
154 this.postNode = function() { 364 this.postNode = function() {
155 // This will take the node from the popupOptions and display it 365 // This will take the node from the popupOptions and display it
156 var node = this.popupOptions[this.currentIndex]; 366 var node = this.popupOptions[this.currentIndex];
304 } 514 }
305 this.responses.appendChild(hold); 515 this.responses.appendChild(hold);
306 } else if (node.type == "radio") { 516 } else if (node.type == "radio") {
307 var optHold = this.popupResponse; 517 var optHold = this.popupResponse;
308 var hold = document.createElement('radio'); 518 var hold = document.createElement('radio');
519 console.log("Checkbox: "+ node.statement);
309 var responseID = null; 520 var responseID = null;
310 var i=0; 521 var i=0;
311 while(responseID == null) { 522 while(responseID == null) {
312 var input = optHold.childNodes[i].getElementsByTagName('input')[0]; 523 var input = optHold.childNodes[i].getElementsByTagName('input')[0];
313 if (input.checked == true) { 524 if (input.checked == true) {
314 responseID = i; 525 responseID = i;
526 console.log("Selected: "+ node.options[i].name);
315 } 527 }
316 i++; 528 i++;
317 } 529 }
318 hold.id = node.id; 530 hold.id = node.id;
319 hold.setAttribute('name',node.options[responseID].name); 531 hold.setAttribute('name',node.options[responseID].name);
394 break; 606 break;
395 } 607 }
396 } 608 }
397 } 609 }
398 } 610 }
611 };
612
613 this.resize = function(event)
614 {
615 // Called on window resize;
616 this.popup.style.left = (window.innerWidth/2)-250 + 'px';
617 this.popup.style.top = (window.innerHeight/2)-125 + 'px';
618 var blank = document.getElementsByClassName('testHalt')[0];
619 blank.style.width = window.innerWidth;
620 blank.style.height = window.innerHeight;
399 }; 621 };
400 } 622 }
401 623
402 function advanceState() 624 function advanceState()
403 { 625 {
456 createProjectSave(specification.projectReturn); 678 createProjectSave(specification.projectReturn);
457 } else { 679 } else {
458 this.currentStateMap = this.stateMap[this.stateIndex]; 680 this.currentStateMap = this.stateMap[this.stateIndex];
459 if (this.currentStateMap.type == "audioHolder") { 681 if (this.currentStateMap.type == "audioHolder") {
460 console.log('Loading test page'); 682 console.log('Loading test page');
461 loadTest(this.currentStateMap); 683 interfaceContext.newPage(this.currentStateMap);
462 this.initialiseInnerState(this.currentStateMap); 684 this.initialiseInnerState(this.currentStateMap);
463 } else if (this.currentStateMap.type == "pretest" || this.currentStateMap.type == "posttest") { 685 } else if (this.currentStateMap.type == "pretest" || this.currentStateMap.type == "posttest") {
464 if (this.currentStateMap.options.length >= 1) { 686 if (this.currentStateMap.options.length >= 1) {
465 popup.initState(this.currentStateMap); 687 popup.initState(this.currentStateMap);
466 } else { 688 } else {
475 } 697 }
476 }; 698 };
477 699
478 this.testPageCompleted = function(store, testXML, testId) { 700 this.testPageCompleted = function(store, testXML, testId) {
479 // Function called each time a test page has been completed 701 // Function called each time a test page has been completed
480 // Can be used to over-rule default behaviour 702 var metric = document.createElement('metric');
481 703 if (audioEngineContext.metric.enableTestTimer)
704 {
705 var testTime = document.createElement('metricResult');
706 testTime.id = 'testTime';
707 testTime.textContent = audioEngineContext.timer.testDuration;
708 metric.appendChild(testTime);
709 }
710 store.appendChild(metric);
711 var audioObjects = audioEngineContext.audioObjects;
712 for (var i=0; i<audioObjects.length; i++)
713 {
714 var audioElement = audioEngineContext.audioObjects[i].exportXMLDOM();
715 audioElement.setAttribute('presentedId',i);
716 store.appendChild(audioElement);
717 }
718 $(interfaceContext.commentQuestions).each(function(index,element){
719 var node = element.exportXMLDOM();
720 store.appendChild(node);
721 });
482 pageXMLSave(store, testXML); 722 pageXMLSave(store, testXML);
483 }; 723 };
484 724
485 this.initialiseInnerState = function(node) { 725 this.initialiseInnerState = function(node) {
486 // Parses the received testXML for pre and post test options 726 // Parses the received testXML for pre and post test options
516 }; 756 };
517 757
518 this.previousState = function(){}; 758 this.previousState = function(){};
519 } 759 }
520 760
521 function testEnded(testId) 761 function AudioEngine(specification) {
522 {
523 pageXMLSave(testId);
524 if (testXMLSetups.length-1 > testId)
525 {
526 // Yes we have another test to perform
527 testId = (Number(testId)+1);
528 currentState = 'testRun-'+testId;
529 loadTest(testId);
530 } else {
531 console.log('Testing Completed!');
532 currentState = 'postTest';
533 // Check for any post tests
534 var xmlSetup = projectXML.find('setup');
535 var postTest = xmlSetup.find('PostTest')[0];
536 popup.initState(postTest);
537 }
538 }
539
540 function loadProjectSpec(url) {
541 // Load the project document from the given URL, decode the XML and instruct audioEngine to get audio data
542 // If url is null, request client to upload project XML document
543 var r = new XMLHttpRequest();
544 r.open('GET',url,true);
545 r.onload = function() {
546 loadProjectSpecCallback(r.response);
547 };
548 r.send();
549 };
550
551 function loadProjectSpecCallback(response) {
552 // Function called after asynchronous download of XML project specification
553 //var decode = $.parseXML(response);
554 //projectXML = $(decode);
555
556 var parse = new DOMParser();
557 projectXML = parse.parseFromString(response,'text/xml');
558
559 // Build the specification
560 specification.decode();
561
562 testState.stateMap.push(specification.preTest);
563
564 $(specification.audioHolders).each(function(index,elem){
565 testState.stateMap.push(elem);
566 });
567
568 testState.stateMap.push(specification.postTest);
569
570 // Obtain the metrics enabled
571 $(specification.metrics).each(function(index,node){
572 var enabled = node.textContent;
573 switch(node.enabled)
574 {
575 case 'testTimer':
576 sessionMetrics.prototype.enableTestTimer = true;
577 break;
578 case 'elementTimer':
579 sessionMetrics.prototype.enableElementTimer = true;
580 break;
581 case 'elementTracker':
582 sessionMetrics.prototype.enableElementTracker = true;
583 break;
584 case 'elementListenTracker':
585 sessionMetrics.prototype.enableElementListenTracker = true;
586 break;
587 case 'elementInitialPosition':
588 sessionMetrics.prototype.enableElementInitialPosition = true;
589 break;
590 case 'elementFlagListenedTo':
591 sessionMetrics.prototype.enableFlagListenedTo = true;
592 break;
593 case 'elementFlagMoved':
594 sessionMetrics.prototype.enableFlagMoved = true;
595 break;
596 case 'elementFlagComments':
597 sessionMetrics.prototype.enableFlagComments = true;
598 break;
599 }
600 });
601
602
603
604 // Detect the interface to use and load the relevant javascripts.
605 var interfaceJS = document.createElement('script');
606 interfaceJS.setAttribute("type","text/javascript");
607 if (specification.interfaceType == 'APE') {
608 interfaceJS.setAttribute("src","ape.js");
609
610 // APE comes with a css file
611 var css = document.createElement('link');
612 css.rel = 'stylesheet';
613 css.type = 'text/css';
614 css.href = 'ape.css';
615
616 document.getElementsByTagName("head")[0].appendChild(css);
617 } else if (specification.interfaceType == "MUSHRA")
618 {
619 interfaceJS.setAttribute("src","mushra.js");
620
621 // MUSHRA comes with a css file
622 var css = document.createElement('link');
623 css.rel = 'stylesheet';
624 css.type = 'text/css';
625 css.href = 'mushra.css';
626
627 document.getElementsByTagName("head")[0].appendChild(css);
628 }
629 document.getElementsByTagName("head")[0].appendChild(interfaceJS);
630
631 // Define window callbacks for interface
632 window.onresize = function(event){interfaceContext.resizeWindow(event);};
633 }
634
635 function createProjectSave(destURL) {
636 // Save the data from interface into XML and send to destURL
637 // If destURL is null then download XML in client
638 // Now time to render file locally
639 var xmlDoc = interfaceXMLSave();
640 var parent = document.createElement("div");
641 parent.appendChild(xmlDoc);
642 var file = [parent.innerHTML];
643 if (destURL == "null" || destURL == undefined) {
644 var bb = new Blob(file,{type : 'application/xml'});
645 var dnlk = window.URL.createObjectURL(bb);
646 var a = document.createElement("a");
647 a.hidden = '';
648 a.href = dnlk;
649 a.download = "save.xml";
650 a.textContent = "Save File";
651
652 popup.showPopup();
653 popup.popupContent.innerHTML = null;
654 popup.popupContent.appendChild(a);
655 } else {
656 var xmlhttp = new XMLHttpRequest;
657 xmlhttp.open("POST",destURL,true);
658 xmlhttp.setRequestHeader('Content-Type', 'text/xml');
659 xmlhttp.onerror = function(){
660 console.log('Error saving file to server! Presenting download locally');
661 createProjectSave(null);
662 };
663 xmlhttp.onreadystatechange = function() {
664 console.log(xmlhttp.status);
665 if (xmlhttp.status != 200 && xmlhttp.readyState == 4) {
666 createProjectSave(null);
667 } else {
668 if (xmlhttp.responseXML == null)
669 {
670 return createProjectSave(null);
671 }
672 var response = xmlhttp.responseXML.childNodes[0];
673 if (response.getAttribute('state') == "OK")
674 {
675 var file = response.getElementsByTagName('file')[0];
676 console.log('Save OK: Filename '+file.textContent+','+file.getAttribute('bytes')+'B');
677 popup.showPopup();
678 popup.popupContent.innerHTML = null;
679 popup.popupContent.textContent = "Thank you!";
680 } else {
681 var message = response.getElementsByTagName('message')[0];
682 errorSessionDump(message.textContent);
683 }
684 }
685 };
686 xmlhttp.send(file);
687 }
688 }
689
690 function errorSessionDump(msg){
691 // Create the partial interface XML save
692 // Include error node with message on why the dump occured
693 var xmlDoc = interfaceXMLSave();
694 var err = document.createElement('error');
695 err.textContent = msg;
696 xmlDoc.appendChild(err);
697 var parent = document.createElement("div");
698 parent.appendChild(xmlDoc);
699 var file = [parent.innerHTML];
700 var bb = new Blob(file,{type : 'application/xml'});
701 var dnlk = window.URL.createObjectURL(bb);
702 var a = document.createElement("a");
703 a.hidden = '';
704 a.href = dnlk;
705 a.download = "save.xml";
706 a.textContent = "Save File";
707
708 popup.showPopup();
709 popup.popupContent.innerHTML = "ERROR : "+msg;
710 popup.popupContent.appendChild(a);
711 }
712
713 // Only other global function which must be defined in the interface class. Determines how to create the XML document.
714 function interfaceXMLSave(){
715 // Create the XML string to be exported with results
716 var xmlDoc = document.createElement("BrowserEvaluationResult");
717 var projectDocument = specification.projectXML;
718 projectDocument.setAttribute('file-name',url);
719 xmlDoc.appendChild(projectDocument);
720 xmlDoc.appendChild(returnDateNode());
721 xmlDoc.appendChild(interfaceContext.returnNavigator());
722 for (var i=0; i<testState.stateResults.length; i++)
723 {
724 xmlDoc.appendChild(testState.stateResults[i]);
725 }
726
727 return xmlDoc;
728 }
729
730 function AudioEngine() {
731 762
732 // Create two output paths, the main outputGain and fooGain. 763 // Create two output paths, the main outputGain and fooGain.
733 // Output gain is default to 1 and any items for playback route here 764 // Output gain is default to 1 and any items for playback route here
734 // Foo gain is used for analysis to ensure paths get processed, but are not heard 765 // Foo gain is used for analysis to ensure paths get processed, but are not heard
735 // because web audio will optimise and any route which does not go to the destination gets ignored. 766 // because web audio will optimise and any route which does not go to the destination gets ignored.
745 this.fooGain.connect(audioContext.destination); 776 this.fooGain.connect(audioContext.destination);
746 777
747 // Create the timer Object 778 // Create the timer Object
748 this.timer = new timer(); 779 this.timer = new timer();
749 // Create session metrics 780 // Create session metrics
750 this.metric = new sessionMetrics(this); 781 this.metric = new sessionMetrics(this,specification);
751 782
752 this.loopPlayback = false; 783 this.loopPlayback = false;
753 784
754 // Create store for new audioObjects 785 // Create store for new audioObjects
755 this.audioObjects = []; 786 this.audioObjects = [];
787
788 this.buffers = [];
789 this.bufferObj = function()
790 {
791 this.url = null;
792 this.buffer = null;
793 this.xmlRequest = new XMLHttpRequest();
794 this.xmlRequest.parent = this;
795 this.users = [];
796 this.getMedia = function(url) {
797 this.url = url;
798 this.xmlRequest.open('GET',this.url,true);
799 this.xmlRequest.responseType = 'arraybuffer';
800
801 var bufferObj = this;
802
803 // Create callback to decode the data asynchronously
804 this.xmlRequest.onloadend = function() {
805 audioContext.decodeAudioData(bufferObj.xmlRequest.response, function(decodedData) {
806 bufferObj.buffer = decodedData;
807 for (var i=0; i<bufferObj.users.length; i++)
808 {
809 bufferObj.users[i].state = 1;
810 if (bufferObj.users[i].interfaceDOM != null)
811 {
812 bufferObj.users[i].bufferLoaded(bufferObj);
813 }
814 }
815 //calculateLoudness(bufferObj.buffer,"I");
816 }, function(){
817 // Should only be called if there was an error, but sometimes gets called continuously
818 // Check here if the error is genuine
819 if (bufferObj.buffer == undefined) {
820 // Genuine error
821 console.log('FATAL - Error loading buffer on '+audioObj.id);
822 if (request.status == 404)
823 {
824 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
825 console.log('URL: '+audioObj.url);
826 errorSessionDump('Fragment '+audioObj.id+' 404 error');
827 }
828 }
829 });
830 };
831 this.progress = 0;
832 this.progressCallback = function(event){
833 if (event.lengthComputable)
834 {
835 this.parent.progress = event.loaded / event.total;
836 for (var i=0; i<this.parent.users.length; i++)
837 {
838 if(this.parent.users[i].interfaceDOM != null)
839 {
840 if (typeof this.parent.users[i].interfaceDOM.updateLoading === "function")
841 {
842 this.parent.users[i].interfaceDOM.updateLoading(this.parent.progress*100);
843 }
844 }
845 }
846 }
847 };
848 this.xmlRequest.addEventListener("progress", this.progressCallback);
849 this.xmlRequest.send();
850 };
851 };
756 852
757 this.play = function(id) { 853 this.play = function(id) {
758 // Start the timer and set the audioEngine state to playing (1) 854 // Start the timer and set the audioEngine state to playing (1)
759 if (this.status == 0 && this.loopPlayback) { 855 if (this.status == 0 && this.loopPlayback) {
760 // Check if all audioObjects are ready 856 // Check if all audioObjects are ready
792 { 888 {
793 if (i != id) { 889 if (i != id) {
794 this.audioObjects[i].outputGain.gain.value = 0.0; 890 this.audioObjects[i].outputGain.gain.value = 0.0;
795 this.audioObjects[i].stop(); 891 this.audioObjects[i].stop();
796 } else if (i == id) { 892 } else if (i == id) {
797 this.audioObjects[id].outputGain.gain.value = 1.0; 893 this.audioObjects[id].outputGain.gain.value = this.audioObjects[id].specification.gain*this.audioObjects[id].buffer.buffer.gain;
798 this.audioObjects[id].play(audioContext.currentTime+0.01); 894 this.audioObjects[id].play(audioContext.currentTime+0.01);
799 } 895 }
800 } 896 }
801 } 897 }
802 interfaceContext.playhead.start(); 898 interfaceContext.playhead.start();
821 917
822 // Create the audioObject with ID of the new track length; 918 // Create the audioObject with ID of the new track length;
823 audioObjectId = this.audioObjects.length; 919 audioObjectId = this.audioObjects.length;
824 this.audioObjects[audioObjectId] = new audioObject(audioObjectId); 920 this.audioObjects[audioObjectId] = new audioObject(audioObjectId);
825 921
826 // AudioObject will get track itself. 922 // Check if audioObject buffer is currently stored by full URL
923 var URL = element.parent.hostURL + element.url;
924 var buffer = null;
925 for (var i=0; i<this.buffers.length; i++)
926 {
927 if (URL == this.buffers[i].url)
928 {
929 buffer = this.buffers[i];
930 break;
931 }
932 }
933 if (buffer == null)
934 {
935 console.log("[WARN]: Buffer was not loaded in pre-test! "+URL);
936 buffer = new this.bufferObj();
937 buffer.getMedia(URL);
938 this.buffers.push(buffer);
939 }
827 this.audioObjects[audioObjectId].specification = element; 940 this.audioObjects[audioObjectId].specification = element;
828 this.audioObjects[audioObjectId].constructTrack(element.parent.hostURL + element.url); 941 this.audioObjects[audioObjectId].url = URL;
942 buffer.users.push(this.audioObjects[audioObjectId]);
943 if (buffer.buffer != null)
944 {
945 this.audioObjects[audioObjectId].bufferLoaded(buffer);
946 }
829 return this.audioObjects[audioObjectId]; 947 return this.audioObjects[audioObjectId];
830 }; 948 };
831 949
832 this.newTestPage = function() { 950 this.newTestPage = function() {
833 this.state = 0; 951 this.state = 0;
834 this.audioObjectsReady = false; 952 this.audioObjectsReady = false;
835 this.metric.reset(); 953 this.metric.reset();
954 for (var i=0; i < this.buffers.length; i++)
955 {
956 this.buffers[i].users = [];
957 }
836 this.audioObjects = []; 958 this.audioObjects = [];
837 }; 959 };
838 960
839 this.checkAllPlayed = function() { 961 this.checkAllPlayed = function() {
840 arr = []; 962 arr = [];
859 }; 981 };
860 982
861 this.setSynchronousLoop = function() { 983 this.setSynchronousLoop = function() {
862 // Pads the signals so they are all exactly the same length 984 // Pads the signals so they are all exactly the same length
863 var length = 0; 985 var length = 0;
864 var lens = [];
865 var maxId; 986 var maxId;
866 for (var i=0; i<this.audioObjects.length; i++) 987 for (var i=0; i<this.audioObjects.length; i++)
867 { 988 {
868 lens.push(this.audioObjects[i].buffer.length); 989 if (length < this.audioObjects[i].buffer.buffer.length)
869 if (length < this.audioObjects[i].buffer.length) 990 {
870 { 991 length = this.audioObjects[i].buffer.buffer.length;
871 length = this.audioObjects[i].buffer.length;
872 maxId = i; 992 maxId = i;
873 } 993 }
874 } 994 }
875 // Perform difference
876 for (var i=0; i<lens.length; i++)
877 {
878 lens[i] = length - lens[i];
879 }
880 // Extract the audio and zero-pad 995 // Extract the audio and zero-pad
881 for (var i=0; i<lens.length; i++) 996 for (var i=0; i<this.audioObjects.length; i++)
882 { 997 {
883 var orig = this.audioObjects[i].buffer; 998 var orig = this.audioObjects[i].buffer.buffer;
884 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate); 999 var hold = audioContext.createBuffer(orig.numberOfChannels,length,orig.sampleRate);
885 for (var c=0; c<orig.numberOfChannels; c++) 1000 for (var c=0; c<orig.numberOfChannels; c++)
886 { 1001 {
887 var inData = hold.getChannelData(c); 1002 var inData = hold.getChannelData(c);
888 var outData = orig.getChannelData(c); 1003 var outData = orig.getChannelData(c);
889 for (var n=0; n<orig.length; n++) 1004 for (var n=0; n<orig.length; n++)
890 {inData[n] = outData[n];} 1005 {inData[n] = outData[n];}
891 } 1006 }
892 this.audioObjects[i].buffer = hold; 1007 hold.gain = orig.gain;
893 delete orig; 1008 hold.lufs = orig.lufs;
1009 this.audioObjects[i].buffer.buffer = hold;
894 } 1010 }
895 }; 1011 };
896 1012
897 } 1013 }
898 1014
920 this.outputGain.connect(audioEngineContext.outputGain); 1036 this.outputGain.connect(audioEngineContext.outputGain);
921 1037
922 // the audiobuffer is not designed for multi-start playback 1038 // the audiobuffer is not designed for multi-start playback
923 // When stopeed, the buffer node is deleted and recreated with the stored buffer. 1039 // When stopeed, the buffer node is deleted and recreated with the stored buffer.
924 this.buffer; 1040 this.buffer;
1041
1042 this.bufferLoaded = function(callee)
1043 {
1044 // Called by the associated buffer when it has finished loading, will then 'bind' the buffer to the
1045 // audioObject and trigger the interfaceDOM.enable() function for user feedback
1046 if (audioEngineContext.loopPlayback){
1047 // First copy the buffer into this.buffer
1048 this.buffer = new audioEngineContext.bufferObj();
1049 this.buffer.url = callee.url;
1050 this.buffer.buffer = audioContext.createBuffer(callee.buffer.numberOfChannels, callee.buffer.length, callee.buffer.sampleRate);
1051 for (var c=0; c<callee.buffer.numberOfChannels; c++)
1052 {
1053 var src = callee.buffer.getChannelData(c);
1054 var dst = this.buffer.buffer.getChannelData(c);
1055 for (var n=0; n<src.length; n++)
1056 {
1057 dst[n] = src[n];
1058 }
1059 }
1060 } else {
1061 this.buffer = callee;
1062 }
1063 this.state = 1;
1064 this.buffer.buffer.gain = callee.buffer.gain;
1065 this.buffer.buffer.lufs = callee.buffer.lufs;
1066 /*
1067 var targetLUFS = this.specification.parent.loudness;
1068 if (typeof targetLUFS === "number")
1069 {
1070 this.buffer.buffer.gain = decibelToLinear(targetLUFS - this.buffer.buffer.lufs);
1071 } else {
1072 this.buffer.buffer.gain = 1.0;
1073 }
1074 */
1075 if (this.interfaceDOM != null) {
1076 this.interfaceDOM.enable();
1077 }
1078 };
925 1079
926 this.loopStart = function() { 1080 this.loopStart = function() {
927 this.outputGain.gain.value = 1.0; 1081 this.outputGain.gain.value = this.specification.gain*this.buffer.buffer.gain;
928 this.metric.startListening(audioEngineContext.timer.getTestTime()); 1082 this.metric.startListening(audioEngineContext.timer.getTestTime());
929 }; 1083 };
930 1084
931 this.loopStop = function() { 1085 this.loopStop = function() {
932 if (this.outputGain.gain.value != 0.0) { 1086 if (this.outputGain.gain.value != 0.0) {
934 this.metric.stopListening(audioEngineContext.timer.getTestTime()); 1088 this.metric.stopListening(audioEngineContext.timer.getTestTime());
935 } 1089 }
936 }; 1090 };
937 1091
938 this.play = function(startTime) { 1092 this.play = function(startTime) {
939 if (this.bufferNode == undefined) { 1093 if (this.bufferNode == undefined && this.buffer.buffer != undefined) {
940 this.bufferNode = audioContext.createBufferSource(); 1094 this.bufferNode = audioContext.createBufferSource();
941 this.bufferNode.owner = this; 1095 this.bufferNode.owner = this;
942 this.bufferNode.connect(this.outputGain); 1096 this.bufferNode.connect(this.outputGain);
943 this.bufferNode.buffer = this.buffer; 1097 this.bufferNode.buffer = this.buffer.buffer;
944 this.bufferNode.loop = audioEngineContext.loopPlayback; 1098 this.bufferNode.loop = audioEngineContext.loopPlayback;
945 this.bufferNode.onended = function(event) { 1099 this.bufferNode.onended = function(event) {
946 // Safari does not like using 'this' to reference the calling object! 1100 // Safari does not like using 'this' to reference the calling object!
947 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition()); 1101 //event.currentTarget.owner.metric.stopListening(audioEngineContext.timer.getTestTime(),event.currentTarget.owner.getCurrentPosition());
948 event.currentTarget.owner.stop(); 1102 event.currentTarget.owner.stop();
966 this.getCurrentPosition = function() { 1120 this.getCurrentPosition = function() {
967 var time = audioEngineContext.timer.getTestTime(); 1121 var time = audioEngineContext.timer.getTestTime();
968 if (this.bufferNode != undefined) { 1122 if (this.bufferNode != undefined) {
969 if (this.bufferNode.loop == true) { 1123 if (this.bufferNode.loop == true) {
970 if (audioEngineContext.status == 1) { 1124 if (audioEngineContext.status == 1) {
971 return (time-this.metric.listenStart)%this.buffer.duration; 1125 return (time-this.metric.listenStart)%this.buffer.buffer.duration;
972 } else { 1126 } else {
973 return 0; 1127 return 0;
974 } 1128 }
975 } else { 1129 } else {
976 if (this.metric.listenHold) { 1130 if (this.metric.listenHold) {
981 } 1135 }
982 } else { 1136 } else {
983 return 0; 1137 return 0;
984 } 1138 }
985 }; 1139 };
986
987 this.constructTrack = function(url) {
988 var request = new XMLHttpRequest();
989 this.url = url;
990 request.open('GET',url,true);
991 request.responseType = 'arraybuffer';
992
993 var audioObj = this;
994
995 // Create callback to decode the data asynchronously
996 request.onloadend = function() {
997 audioContext.decodeAudioData(request.response, function(decodedData) {
998 audioObj.buffer = decodedData;
999 audioObj.state = 1;
1000 if (audioObj.specification.type != 'outsidereference')
1001 {audioObj.interfaceDOM.enable();}
1002 }, function(){
1003 // Should only be called if there was an error, but sometimes gets called continuously
1004 // Check here if the error is genuine
1005 if (audioObj.state == 0 || audioObj.buffer == undefined) {
1006 // Genuine error
1007 console.log('FATAL - Error loading buffer on '+audioObj.id);
1008 if (request.status == 404)
1009 {
1010 console.log('FATAL - Fragment '+audioObj.id+' 404 error');
1011 console.log('URL: '+audioObj.url);
1012 errorSessionDump('Fragment '+audioObj.id+' 404 error');
1013 }
1014 }
1015 });
1016 };
1017 request.send();
1018 };
1019 1140
1020 this.exportXMLDOM = function() { 1141 this.exportXMLDOM = function() {
1021 var root = document.createElement('audioElement'); 1142 var root = document.createElement('audioElement');
1022 root.id = this.specification.id; 1143 root.id = this.specification.id;
1023 root.setAttribute('url',this.url); 1144 root.setAttribute('url',this.specification.url);
1024 var file = document.createElement('file'); 1145 var file = document.createElement('file');
1025 file.setAttribute('sampleRate',this.buffer.sampleRate); 1146 file.setAttribute('sampleRate',this.buffer.buffer.sampleRate);
1026 file.setAttribute('channels',this.buffer.numberOfChannels); 1147 file.setAttribute('channels',this.buffer.buffer.numberOfChannels);
1027 file.setAttribute('sampleCount',this.buffer.length); 1148 file.setAttribute('sampleCount',this.buffer.buffer.length);
1028 file.setAttribute('duration',this.buffer.duration); 1149 file.setAttribute('duration',this.buffer.buffer.duration);
1029 root.appendChild(file); 1150 root.appendChild(file);
1030 if (this.specification.type != 'outsidereference') { 1151 if (this.specification.type != 'outsidereference') {
1031 root.appendChild(this.interfaceDOM.exportXMLDOM(this)); 1152 var interfaceXML = this.interfaceDOM.exportXMLDOM(this);
1153 if (interfaceXML.length == undefined) {
1154 root.appendChild(interfaceXML);
1155 } else {
1156 for (var i=0; i<interfaceXML.length; i++)
1157 {
1158 root.appendChild(interfaceXML[i]);
1159 }
1160 }
1032 root.appendChild(this.commentDOM.exportXMLDOM(this)); 1161 root.appendChild(this.commentDOM.exportXMLDOM(this));
1033 if(this.specification.type == 'anchor') { 1162 if(this.specification.type == 'anchor') {
1034 root.setAttribute('anchor',true); 1163 root.setAttribute('anchor',true);
1035 } else if(this.specification.type == 'reference') { 1164 } else if(this.specification.type == 'reference') {
1036 root.setAttribute('reference',true); 1165 root.setAttribute('reference',true);
1082 this.updateTestTime(); 1211 this.updateTestTime();
1083 return this.testDuration; 1212 return this.testDuration;
1084 }; 1213 };
1085 } 1214 }
1086 1215
1087 function sessionMetrics(engine) 1216 function sessionMetrics(engine,specification)
1088 { 1217 {
1089 /* Used by audioEngine to link to audioObjects to minimise the timer call timers; 1218 /* Used by audioEngine to link to audioObjects to minimise the timer call timers;
1090 */ 1219 */
1091 this.engine = engine; 1220 this.engine = engine;
1092 this.lastClicked = -1; 1221 this.lastClicked = -1;
1093 this.data = -1; 1222 this.data = -1;
1094 this.reset = function() { 1223 this.reset = function() {
1095 this.lastClicked = -1; 1224 this.lastClicked = -1;
1096 this.data = -1; 1225 this.data = -1;
1097 }; 1226 };
1227
1228 this.enableElementInitialPosition = false;
1229 this.enableElementListenTracker = false;
1230 this.enableElementTimer = false;
1231 this.enableElementTracker = false;
1232 this.enableFlagListenedTo = false;
1233 this.enableFlagMoved = false;
1234 this.enableTestTimer = false;
1235 // Obtain the metrics enabled
1236 for (var i=0; i<specification.metrics.length; i++)
1237 {
1238 var node = specification.metrics[i];
1239 switch(node.enabled)
1240 {
1241 case 'testTimer':
1242 this.enableTestTimer = true;
1243 break;
1244 case 'elementTimer':
1245 this.enableElementTimer = true;
1246 break;
1247 case 'elementTracker':
1248 this.enableElementTracker = true;
1249 break;
1250 case 'elementListenTracker':
1251 this.enableElementListenTracker = true;
1252 break;
1253 case 'elementInitialPosition':
1254 this.enableElementInitialPosition = true;
1255 break;
1256 case 'elementFlagListenedTo':
1257 this.enableFlagListenedTo = true;
1258 break;
1259 case 'elementFlagMoved':
1260 this.enableFlagMoved = true;
1261 break;
1262 case 'elementFlagComments':
1263 this.enableFlagComments = true;
1264 break;
1265 }
1266 }
1098 this.initialiseTest = function(){}; 1267 this.initialiseTest = function(){};
1099 } 1268 }
1100 1269
1101 function metricTracker(caller) 1270 function metricTracker(caller)
1102 { 1271 {
1302 time.setAttributeNode(minute); 1471 time.setAttributeNode(minute);
1303 time.setAttributeNode(secs); 1472 time.setAttributeNode(secs);
1304 1473
1305 hold.appendChild(date); 1474 hold.appendChild(date);
1306 hold.appendChild(time); 1475 hold.appendChild(time);
1307 return hold 1476 return hold;
1308 1477
1309 } 1478 }
1310 1479
1311 function Specification() { 1480 function Specification() {
1312 // Handles the decoding of the project specification XML into a simple JavaScript Object. 1481 // Handles the decoding of the project specification XML into a simple JavaScript Object.
1313 1482
1314 this.interfaceType = null; 1483 this.interfaceType = null;
1315 this.commonInterface = null; 1484 this.commonInterface = new function()
1485 {
1486 this.options = [];
1487 this.optionNode = function(input)
1488 {
1489 var name = input.getAttribute('name');
1490 this.type = name;
1491 if(this.type == "option")
1492 {
1493 this.name = input.id;
1494 } else if (this.type == "check")
1495 {
1496 this.check = input.id;
1497 }
1498 };
1499 };
1500
1501 this.randomiseOrder = function(input)
1502 {
1503 // This takes an array of information and randomises the order
1504 var N = input.length;
1505
1506 var inputSequence = []; // For safety purposes: keep track of randomisation
1507 for (var counter = 0; counter < N; ++counter)
1508 inputSequence.push(counter) // Fill array
1509 var inputSequenceClone = inputSequence.slice(0);
1510
1511 var holdArr = [];
1512 var outputSequence = [];
1513 for (var n=0; n<N; n++)
1514 {
1515 // First pick a random number
1516 var r = Math.random();
1517 // Multiply and floor by the number of elements left
1518 r = Math.floor(r*input.length);
1519 // Pick out that element and delete from the array
1520 holdArr.push(input.splice(r,1)[0]);
1521 // Do the same with sequence
1522 outputSequence.push(inputSequence.splice(r,1)[0]);
1523 }
1524 console.log(inputSequenceClone.toString()); // print original array to console
1525 console.log(outputSequence.toString()); // print randomised array to console
1526 return holdArr;
1527 };
1316 this.projectReturn = null; 1528 this.projectReturn = null;
1317 this.randomiseOrder = null; 1529 this.randomiseOrder = null;
1318 this.collectMetrics = null; 1530 this.collectMetrics = null;
1319 this.testPages = null; 1531 this.testPages = null;
1320 this.preTest = null;
1321 this.postTest = null;
1322 this.metrics =[];
1323
1324 this.audioHolders = []; 1532 this.audioHolders = [];
1325 1533 this.metrics = [];
1326 this.decode = function() { 1534 this.loudness = null;
1535
1536 this.decode = function(projectXML) {
1327 // projectXML - DOM Parsed document 1537 // projectXML - DOM Parsed document
1328 this.projectXML = projectXML.childNodes[0]; 1538 this.projectXML = projectXML.childNodes[0];
1329 var setupNode = projectXML.getElementsByTagName('setup')[0]; 1539 var setupNode = projectXML.getElementsByTagName('setup')[0];
1330 this.interfaceType = setupNode.getAttribute('interface'); 1540 this.interfaceType = setupNode.getAttribute('interface');
1331 this.projectReturn = setupNode.getAttribute('projectReturn'); 1541 this.projectReturn = setupNode.getAttribute('projectReturn');
1341 this.testPages = null; 1551 this.testPages = null;
1342 } else { 1552 } else {
1343 this.testPages = Number(this.testPages); 1553 this.testPages = Number(this.testPages);
1344 if (this.testPages == 0) {this.testPages = null;} 1554 if (this.testPages == 0) {this.testPages = null;}
1345 } 1555 }
1556 if (setupNode.getAttribute('loudness') != null)
1557 {
1558 var XMLloudness = setupNode.getAttribute('loudness');
1559 if (isNaN(Number(XMLloudness)) == false)
1560 {
1561 this.loudness = Number(XMLloudness);
1562 }
1563 }
1346 var metricCollection = setupNode.getElementsByTagName('Metric'); 1564 var metricCollection = setupNode.getElementsByTagName('Metric');
1347 1565
1348 this.preTest = new this.prepostNode('pretest',setupNode.getElementsByTagName('PreTest')); 1566 var setupPreTestNode = setupNode.getElementsByTagName('PreTest');
1349 this.postTest = new this.prepostNode('posttest',setupNode.getElementsByTagName('PostTest')); 1567 if (setupPreTestNode.length != 0)
1568 {
1569 setupPreTestNode = setupPreTestNode[0];
1570 this.preTest.construct(setupPreTestNode);
1571 }
1572
1573 var setupPostTestNode = setupNode.getElementsByTagName('PostTest');
1574 if (setupPostTestNode.length != 0)
1575 {
1576 setupPostTestNode = setupPostTestNode[0];
1577 this.postTest.construct(setupPostTestNode);
1578 }
1350 1579
1351 if (metricCollection.length > 0) { 1580 if (metricCollection.length > 0) {
1352 metricCollection = metricCollection[0].getElementsByTagName('metricEnable'); 1581 metricCollection = metricCollection[0].getElementsByTagName('metricEnable');
1353 for (var i=0; i<metricCollection.length; i++) { 1582 for (var i=0; i<metricCollection.length; i++) {
1354 this.metrics.push(new this.metricNode(metricCollection[i].textContent)); 1583 this.metrics.push(new this.metricNode(metricCollection[i].textContent));
1386 } else { 1615 } else {
1387 this.max = Number(this.max); 1616 this.max = Number(this.max);
1388 } 1617 }
1389 } 1618 }
1390 } else if (this.type == 'anchor' || this.type == 'reference') { 1619 } else if (this.type == 'anchor' || this.type == 'reference') {
1391 Console.log("WARNING: Anchor and Reference tags in the <interface> node are depricated"); 1620 this.value = Number(child.textContent);
1621 this.enforce = child.getAttribute('enforce');
1622 if (this.enforce == 'true') {this.enforce = true;}
1623 else {this.enforce = false;}
1392 } 1624 }
1393 }; 1625 };
1394 this.options = []; 1626 this.options = [];
1395 if (commonInterfaceNode != undefined) { 1627 if (commonInterfaceNode != undefined) {
1396 var child = commonInterfaceNode.firstElementChild; 1628 var child = commonInterfaceNode.firstElementChild;
1401 } 1633 }
1402 }; 1634 };
1403 1635
1404 var audioHolders = projectXML.getElementsByTagName('audioHolder'); 1636 var audioHolders = projectXML.getElementsByTagName('audioHolder');
1405 for (var i=0; i<audioHolders.length; i++) { 1637 for (var i=0; i<audioHolders.length; i++) {
1406 this.audioHolders.push(new this.audioHolderNode(this,audioHolders[i])); 1638 var node = new this.audioHolderNode(this);
1639 node.decode(this,audioHolders[i]);
1640 this.audioHolders.push(node);
1407 } 1641 }
1408 1642
1409 // New check if we need to randomise the test order 1643 // New check if we need to randomise the test order
1410 if (this.randomiseOrder) 1644 if (this.randomiseOrder && typeof randomiseOrder === "function")
1411 { 1645 {
1412 this.audioHolders = randomiseOrder(this.audioHolders); 1646 this.audioHolders = randomiseOrder(this.audioHolders);
1413 for (var i=0; i<this.audioHolders.length; i++) 1647 for (var i=0; i<this.audioHolders.length; i++)
1414 { 1648 {
1415 this.audioHolders[i].presentedId = i; 1649 this.audioHolders[i].presentedId = i;
1430 this.audioHolders.push(aH[i]); 1664 this.audioHolders.push(aH[i]);
1431 } 1665 }
1432 } 1666 }
1433 }; 1667 };
1434 1668
1435 this.prepostNode = function(type,Collection) { 1669 this.encode = function()
1670 {
1671 var root = document.implementation.createDocument(null,"BrowserEvalProjectDocument");
1672 // First get all the <setup> tag compiled
1673 var setupNode = root.createElement("setup");
1674 setupNode.setAttribute('interface',this.interfaceType);
1675 setupNode.setAttribute('projectReturn',this.projectReturn);
1676 setupNode.setAttribute('randomiseOrder',this.randomiseOrder);
1677 setupNode.setAttribute('collectMetrics',this.collectMetrics);
1678 setupNode.setAttribute('testPages',this.testPages);
1679 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
1680
1681 var setupPreTest = root.createElement("PreTest");
1682 for (var i=0; i<this.preTest.options.length; i++)
1683 {
1684 setupPreTest.appendChild(this.preTest.options[i].exportXML(root));
1685 }
1686
1687 var setupPostTest = root.createElement("PostTest");
1688 for (var i=0; i<this.postTest.options.length; i++)
1689 {
1690 setupPostTest.appendChild(this.postTest.options[i].exportXML(root));
1691 }
1692
1693 setupNode.appendChild(setupPreTest);
1694 setupNode.appendChild(setupPostTest);
1695
1696 // <Metric> tag
1697 var Metric = root.createElement("Metric");
1698 for (var i=0; i<this.metrics.length; i++)
1699 {
1700 var metricEnable = root.createElement("metricEnable");
1701 metricEnable.textContent = this.metrics[i].enabled;
1702 Metric.appendChild(metricEnable);
1703 }
1704 setupNode.appendChild(Metric);
1705
1706 // <interface> tag
1707 var CommonInterface = root.createElement("interface");
1708 for (var i=0; i<this.commonInterface.options.length; i++)
1709 {
1710 var CIObj = this.commonInterface.options[i];
1711 var CINode = root.createElement(CIObj.type);
1712 if (CIObj.type == "check") {CINode.setAttribute("name",CIObj.check);}
1713 else {CINode.setAttribute("name",CIObj.name);}
1714 CommonInterface.appendChild(CINode);
1715 }
1716 setupNode.appendChild(CommonInterface);
1717
1718 root.getElementsByTagName("BrowserEvalProjectDocument")[0].appendChild(setupNode);
1719 // Time for the <audioHolder> tags
1720 for (var ahIndex = 0; ahIndex < this.audioHolders.length; ahIndex++)
1721 {
1722 var node = this.audioHolders[ahIndex].encode(root);
1723 root.getElementsByTagName("BrowserEvalProjectDocument")[0].appendChild(node);
1724 }
1725 return root;
1726 };
1727
1728 this.prepostNode = function(type) {
1436 this.type = type; 1729 this.type = type;
1437 this.options = []; 1730 this.options = [];
1438 1731
1439 this.OptionNode = function(child) { 1732 this.OptionNode = function() {
1440 1733
1441 this.childOption = function(element) { 1734 this.childOption = function() {
1442 this.type = 'option'; 1735 this.type = 'option';
1443 this.id = element.id; 1736 this.id = null;
1444 this.name = element.getAttribute('name'); 1737 this.name = undefined;
1445 this.text = element.textContent; 1738 this.text = null;
1446 }; 1739 };
1447 1740
1448 this.type = child.nodeName; 1741 this.type = undefined;
1449 if (child.nodeName == "question") { 1742 this.id = undefined;
1450 this.id = child.id; 1743 this.mandatory = undefined;
1451 this.mandatory; 1744 this.question = undefined;
1452 if (child.getAttribute('mandatory') == "true") {this.mandatory = true;} 1745 this.statement = undefined;
1453 else {this.mandatory = false;} 1746 this.boxsize = undefined;
1454 this.question = child.textContent; 1747 this.options = [];
1455 if (child.getAttribute('boxsize') == null) { 1748 this.min = undefined;
1456 this.boxsize = 'normal'; 1749 this.max = undefined;
1457 } else { 1750 this.step = undefined;
1458 this.boxsize = child.getAttribute('boxsize'); 1751
1459 } 1752 this.decode = function(child)
1460 } else if (child.nodeName == "statement") { 1753 {
1461 this.statement = child.textContent; 1754 this.type = child.nodeName;
1462 } else if (child.nodeName == "checkbox" || child.nodeName == "radio") { 1755 if (child.nodeName == "question") {
1463 var element = child.firstElementChild; 1756 this.id = child.id;
1464 this.id = child.id; 1757 this.mandatory;
1465 if (element == null) { 1758 if (child.getAttribute('mandatory') == "true") {this.mandatory = true;}
1466 console.log('Malformed' +child.nodeName+ 'entry'); 1759 else {this.mandatory = false;}
1467 this.statement = 'Malformed' +child.nodeName+ 'entry'; 1760 this.question = child.textContent;
1468 this.type = 'statement'; 1761 if (child.getAttribute('boxsize') == null) {
1469 } else { 1762 this.boxsize = 'normal';
1470 this.options = []; 1763 } else {
1471 while (element != null) { 1764 this.boxsize = child.getAttribute('boxsize');
1472 if (element.nodeName == 'statement' && this.statement == undefined){ 1765 }
1473 this.statement = element.textContent; 1766 } else if (child.nodeName == "statement") {
1474 } else if (element.nodeName == 'option') { 1767 this.statement = child.textContent;
1475 this.options.push(new this.childOption(element)); 1768 } else if (child.nodeName == "checkbox" || child.nodeName == "radio") {
1769 var element = child.firstElementChild;
1770 this.id = child.id;
1771 if (element == null) {
1772 console.log('Malformed' +child.nodeName+ 'entry');
1773 this.statement = 'Malformed' +child.nodeName+ 'entry';
1774 this.type = 'statement';
1775 } else {
1776 this.options = [];
1777 while (element != null) {
1778 if (element.nodeName == 'statement' && this.statement == undefined){
1779 this.statement = element.textContent;
1780 } else if (element.nodeName == 'option') {
1781 var node = new this.childOption();
1782 node.id = element.id;
1783 node.name = element.getAttribute('name');
1784 node.text = element.textContent;
1785 this.options.push(node);
1786 }
1787 element = element.nextElementSibling;
1476 } 1788 }
1477 element = element.nextElementSibling;
1478 } 1789 }
1479 } 1790 } else if (child.nodeName == "number") {
1480 } else if (child.nodeName == "number") { 1791 this.statement = child.textContent;
1481 this.statement = child.textContent; 1792 this.id = child.id;
1482 this.id = child.id; 1793 this.min = child.getAttribute('min');
1483 this.min = child.getAttribute('min'); 1794 this.max = child.getAttribute('max');
1484 this.max = child.getAttribute('max'); 1795 this.step = child.getAttribute('step');
1485 this.step = child.getAttribute('step'); 1796 }
1486 } 1797 };
1798
1799 this.exportXML = function(root)
1800 {
1801 var node = root.createElement(this.type);
1802 switch(this.type)
1803 {
1804 case "statement":
1805 node.textContent = this.statement;
1806 break;
1807 case "question":
1808 node.id = this.id;
1809 node.setAttribute("mandatory",this.mandatory);
1810 node.setAttribute("boxsize",this.boxsize);
1811 node.textContent = this.question;
1812 break;
1813 case "number":
1814 node.id = this.id;
1815 node.setAttribute("mandatory",this.mandatory);
1816 node.setAttribute("min", this.min);
1817 node.setAttribute("max", this.max);
1818 node.setAttribute("step", this.step);
1819 node.textContent = this.statement;
1820 break;
1821 case "checkbox":
1822 node.id = this.id;
1823 var statement = root.createElement("statement");
1824 statement.textContent = this.statement;
1825 node.appendChild(statement);
1826 for (var i=0; i<this.options.length; i++)
1827 {
1828 var option = this.options[i];
1829 var optionNode = root.createElement("option");
1830 optionNode.id = option.id;
1831 optionNode.textContent = option.text;
1832 node.appendChild(optionNode);
1833 }
1834 break;
1835 case "radio":
1836 node.id = this.id;
1837 var statement = root.createElement("statement");
1838 statement.textContent = this.statement;
1839 node.appendChild(statement);
1840 for (var i=0; i<this.options.length; i++)
1841 {
1842 var option = this.options[i];
1843 var optionNode = root.createElement("option");
1844 optionNode.setAttribute("name",option.name);
1845 optionNode.textContent = option.text;
1846 node.appendChild(optionNode);
1847 }
1848 break;
1849 }
1850 return node;
1851 };
1487 }; 1852 };
1488 1853 this.construct = function(Collection)
1489 // On construction: 1854 {
1490 if (Collection.length != 0) {
1491 Collection = Collection[0];
1492 if (Collection.childElementCount != 0) { 1855 if (Collection.childElementCount != 0) {
1493 var child = Collection.firstElementChild; 1856 var child = Collection.firstElementChild;
1494 this.options.push(new this.OptionNode(child)); 1857 var node = new this.OptionNode();
1858 node.decode(child);
1859 this.options.push(node);
1495 while (child.nextElementSibling != null) { 1860 while (child.nextElementSibling != null) {
1496 child = child.nextElementSibling; 1861 child = child.nextElementSibling;
1497 this.options.push(new this.OptionNode(child)); 1862 node = new this.OptionNode();
1498 } 1863 node.decode(child);
1499 } 1864 this.options.push(node);
1500 } 1865 }
1501 }; 1866 }
1867 };
1868 };
1869 this.preTest = new this.prepostNode("pretest");
1870 this.postTest = new this.prepostNode("posttest");
1502 1871
1503 this.metricNode = function(name) { 1872 this.metricNode = function(name) {
1504 this.enabled = name; 1873 this.enabled = name;
1505 }; 1874 };
1506 1875
1507 this.audioHolderNode = function(parent,xml) { 1876 this.audioHolderNode = function(parent) {
1508 this.type = 'audioHolder'; 1877 this.type = 'audioHolder';
1509 this.presentedId = parent.audioHolders.length; 1878 this.presentedId = undefined;
1510 this.interfaceNode = function(DOM) { 1879 this.id = undefined;
1511 var title = DOM.getElementsByTagName('title'); 1880 this.hostURL = undefined;
1512 if (title.length == 0) {this.title = null;} 1881 this.sampleRate = undefined;
1513 else {this.title = title[0].textContent;} 1882 this.randomiseOrder = undefined;
1514 this.options = parent.commonInterface.options; 1883 this.loop = undefined;
1515 var scale = DOM.getElementsByTagName('scale'); 1884 this.elementComments = undefined;
1885 this.outsideReference = null;
1886 this.loudness = null;
1887 this.initialPosition = null;
1888 this.preTest = new parent.prepostNode("pretest");
1889 this.postTest = new parent.prepostNode("pretest");
1890 this.interfaces = [];
1891 this.commentBoxPrefix = "Comment on track";
1892 this.audioElements = [];
1893 this.commentQuestions = [];
1894
1895 this.decode = function(parent,xml)
1896 {
1897 this.presentedId = parent.audioHolders.length;
1898 this.id = xml.id;
1899 this.hostURL = xml.getAttribute('hostURL');
1900 this.sampleRate = xml.getAttribute('sampleRate');
1901 if (xml.getAttribute('randomiseOrder') == "true") {this.randomiseOrder = true;}
1902 else {this.randomiseOrder = false;}
1903 this.repeatCount = xml.getAttribute('repeatCount');
1904 if (xml.getAttribute('loop') == 'true') {this.loop = true;}
1905 else {this.loop == false;}
1906 if (xml.getAttribute('elementComments') == "true") {this.elementComments = true;}
1907 else {this.elementComments = false;}
1908 if (typeof parent.loudness === "number")
1909 {
1910 this.loudness = parent.loudness;
1911 }
1912 if (typeof xml.getAttribute('initial-position') === "string")
1913 {
1914 var xmlInitialPosition = Number(xml.getAttribute('initial-position'));
1915 if (isNaN(xmlInitialPosition) == false)
1916 {
1917 if (xmlInitialPosition > 1)
1918 {
1919 xmlInitialPosition /= 100;
1920 }
1921 this.initialPosition = xmlInitialPosition;
1922 }
1923 }
1924 if (xml.getAttribute('loudness') != null)
1925 {
1926 var XMLloudness = xml.getAttribute('loudness');
1927 if (isNaN(Number(XMLloudness)) == false)
1928 {
1929 this.loudness = Number(XMLloudness);
1930 }
1931 }
1932 var setupPreTestNode = xml.getElementsByTagName('PreTest');
1933 if (setupPreTestNode.length != 0)
1934 {
1935 setupPreTestNode = setupPreTestNode[0];
1936 this.preTest.construct(setupPreTestNode);
1937 }
1938
1939 var setupPostTestNode = xml.getElementsByTagName('PostTest');
1940 if (setupPostTestNode.length != 0)
1941 {
1942 setupPostTestNode = setupPostTestNode[0];
1943 this.postTest.construct(setupPostTestNode);
1944 }
1945
1946 var interfaceDOM = xml.getElementsByTagName('interface');
1947 for (var i=0; i<interfaceDOM.length; i++) {
1948 var node = new this.interfaceNode();
1949 node.decode(interfaceDOM[i]);
1950 this.interfaces.push(node);
1951 }
1952 this.commentBoxPrefix = xml.getElementsByTagName('commentBoxPrefix');
1953 if (this.commentBoxPrefix.length != 0) {
1954 this.commentBoxPrefix = this.commentBoxPrefix[0].textContent;
1955 } else {
1956 this.commentBoxPrefix = "Comment on track";
1957 }
1958 var audioElementsDOM = xml.getElementsByTagName('audioElements');
1959 var outsideReferenceHolder = null;
1960 for (var i=0; i<audioElementsDOM.length; i++) {
1961 var node = new this.audioElementNode();
1962 node.decode(this,audioElementsDOM[i]);
1963 if (audioElementsDOM[i].getAttribute('type') == 'outsidereference') {
1964 if (this.outsideReference == null) {
1965 outsideReferenceHolder = node;
1966 this.outsideReference = i;
1967 } else {
1968 console.log('Error only one audioelement can be of type outsidereference per audioholder');
1969 this.audioElements.push(node);
1970 console.log('Element id '+audioElementsDOM[i].id+' made into normal node');
1971 }
1972 } else {
1973 this.audioElements.push(node);
1974 }
1975 }
1976
1977 if (this.randomiseOrder == true && typeof randomiseOrder === "function")
1978 {
1979 this.audioElements = randomiseOrder(this.audioElements);
1980 }
1981 if (outsideReferenceHolder != null)
1982 {
1983 this.audioElements.push(outsideReferenceHolder);
1984 this.outsideReference = this.audioElements.length-1;
1985 }
1986
1987
1988 var commentQuestionsDOM = xml.getElementsByTagName('CommentQuestion');
1989 for (var i=0; i<commentQuestionsDOM.length; i++) {
1990 var node = new this.commentQuestionNode();
1991 node.decode(commentQuestionsDOM[i]);
1992 this.commentQuestions.push(node);
1993 }
1994 };
1995
1996 this.encode = function(root)
1997 {
1998 var AHNode = root.createElement("audioHolder");
1999 AHNode.id = this.id;
2000 AHNode.setAttribute("hostURL",this.hostURL);
2001 AHNode.setAttribute("sampleRate",this.sampleRate);
2002 AHNode.setAttribute("randomiseOrder",this.randomiseOrder);
2003 AHNode.setAttribute("repeatCount",this.repeatCount);
2004 AHNode.setAttribute("loop",this.loop);
2005 AHNode.setAttribute("elementComments",this.elementComments);
2006 if(this.loudness != null) {AHNode.setAttribute("loudness",this.loudness);}
2007 if(this.initialPosition != null) {
2008 AHNode.setAttribute("loudness",this.initialPosition*100);
2009 }
2010 for (var i=0; i<this.interfaces.length; i++)
2011 {
2012 AHNode.appendChild(this.interfaces[i].encode(root));
2013 }
2014
2015 for (var i=0; i<this.audioElements.length; i++) {
2016 AHNode.appendChild(this.audioElements[i].encode(root));
2017 }
2018 // Create <CommentQuestion>
2019 for (var i=0; i<this.commentQuestions.length; i++)
2020 {
2021 AHNode.appendChild(this.commentQuestions[i].exportXML(root));
2022 }
2023
2024 // Create <PreTest>
2025 var AHPreTest = root.createElement("PreTest");
2026 for (var i=0; i<this.preTest.options.length; i++)
2027 {
2028 AHPreTest.appendChild(this.preTest.options[i].exportXML(root));
2029 }
2030
2031 var AHPostTest = root.createElement("PostTest");
2032 for (var i=0; i<this.postTest.options.length; i++)
2033 {
2034 AHPostTest.appendChild(this.postTest.options[i].exportXML(root));
2035 }
2036 AHNode.appendChild(AHPreTest);
2037 AHNode.appendChild(AHPostTest);
2038 return AHNode;
2039 };
2040
2041 this.interfaceNode = function() {
2042 this.title = undefined;
2043 this.options = [];
1516 this.scale = []; 2044 this.scale = [];
1517 for (var i=0; i<scale.length; i++) { 2045 this.name = undefined;
1518 var arr = [null, null]; 2046 this.decode = function(DOM)
1519 arr[0] = scale[i].getAttribute('position'); 2047 {
1520 arr[1] = scale[i].textContent; 2048 var title = DOM.getElementsByTagName('title');
1521 this.scale.push(arr); 2049 if (title.length == 0) {this.title = null;}
1522 } 2050 else {this.title = title[0].textContent;}
2051 var name = DOM.getAttribute("name");
2052 if (name != undefined) {this.name = name;}
2053 this.options = parent.commonInterface.options;
2054 var scale = DOM.getElementsByTagName('scale');
2055 this.scale = [];
2056 for (var i=0; i<scale.length; i++) {
2057 var arr = [null, null];
2058 arr[0] = scale[i].getAttribute('position');
2059 arr[1] = scale[i].textContent;
2060 this.scale.push(arr);
2061 }
2062 };
2063 this.encode = function(root)
2064 {
2065 var node = root.createElement("interface");
2066 if (this.title != undefined)
2067 {
2068 var title = root.createElement("title");
2069 title.textContent = this.title;
2070 node.appendChild(title);
2071 }
2072 for (var i=0; i<this.options.length; i++)
2073 {
2074 var optionNode = root.createElement(this.options[i].type);
2075 if (this.options[i].type == "option")
2076 {
2077 optionNode.setAttribute("name",this.options[i].name);
2078 } else if (this.options[i].type == "check") {
2079 optionNode.setAttribute("check",this.options[i].check);
2080 } else if (this.options[i].type == "scalerange") {
2081 optionNode.setAttribute("min",this.options[i].min*100);
2082 optionNode.setAttribute("max",this.options[i].max*100);
2083 }
2084 node.appendChild(optionNode);
2085 }
2086 for (var i=0; i<this.scale.length; i++) {
2087 var scale = root.createElement("scale");
2088 scale.setAttribute("position",this.scale[i][0]);
2089 scale.textContent = this.scale[i][1];
2090 node.appendChild(scale);
2091 }
2092 return node;
2093 };
1523 }; 2094 };
1524 2095
1525 this.audioElementNode = function(parent,xml) { 2096 this.audioElementNode = function() {
1526 this.url = xml.getAttribute('url'); 2097 this.url = null;
1527 this.id = xml.id; 2098 this.id = null;
1528 this.parent = parent; 2099 this.parent = null;
1529 this.type = xml.getAttribute('type'); 2100 this.type = "normal";
1530 if (this.type == null) {this.type = "normal";} 2101 this.marker = false;
1531 if (this.type == 'anchor') {this.anchor = true;} 2102 this.enforce = false;
1532 else {this.anchor = false;} 2103 this.gain = 1.0;
1533 if (this.type == 'reference') {this.reference = true;} 2104 this.decode = function(parent,xml)
1534 else {this.reference = false;} 2105 {
1535 2106 this.url = xml.getAttribute('url');
1536 if (this.anchor == true || this.reference == true) 2107 this.id = xml.id;
1537 { 2108 this.parent = parent;
1538 this.marker = xml.getAttribute('marker'); 2109 this.type = xml.getAttribute('type');
1539 if (this.marker != undefined) 2110 var gain = xml.getAttribute('gain');
2111 if (isNaN(gain) == false && gain != null)
1540 { 2112 {
1541 this.marker = Number(this.marker); 2113 this.gain = decibelToLinear(Number(gain));
1542 if (isNaN(this.marker) == false) 2114 }
2115 if (this.type == null) {this.type = "normal";}
2116 if (this.type == 'anchor') {this.anchor = true;}
2117 else {this.anchor = false;}
2118 if (this.type == 'reference') {this.reference = true;}
2119 else {this.reference = false;}
2120 if (this.anchor == true || this.reference == true)
2121 {
2122 this.marker = xml.getAttribute('marker');
2123 if (this.marker != undefined)
1543 { 2124 {
1544 if (this.marker > 1) 2125 this.marker = Number(this.marker);
1545 { this.marker /= 100.0;} 2126 if (isNaN(this.marker) == false)
1546 if (this.marker >= 0 && this.marker <= 1)
1547 { 2127 {
1548 this.enforce = true; 2128 if (this.marker > 1)
1549 return; 2129 { this.marker /= 100.0;}
2130 if (this.marker >= 0 && this.marker <= 1)
2131 {
2132 this.enforce = true;
2133 return;
2134 } else {
2135 console.log("ERROR - Marker of audioElement "+this.id+" is not between 0 and 1 (float) or 0 and 100 (integer)!");
2136 console.log("ERROR - Marker not enforced!");
2137 }
1550 } else { 2138 } else {
1551 console.log("ERROR - Marker of audioElement "+this.id+" is not between 0 and 1 (float) or 0 and 100 (integer)!"); 2139 console.log("ERROR - Marker of audioElement "+this.id+" is not a number!");
1552 console.log("ERROR - Marker not enforced!"); 2140 console.log("ERROR - Marker not enforced!");
1553 } 2141 }
1554 } else {
1555 console.log("ERROR - Marker of audioElement "+this.id+" is not a number!");
1556 console.log("ERROR - Marker not enforced!");
1557 } 2142 }
1558 } 2143 }
1559 } 2144 };
1560 this.marker = false; 2145 this.encode = function(root)
1561 this.enforce = false; 2146 {
2147 var AENode = root.createElement("audioElements");
2148 AENode.id = this.id;
2149 AENode.setAttribute("url",this.url);
2150 AENode.setAttribute("type",this.type);
2151 AENode.setAttribute("gain",linearToDecibel(this.gain));
2152 if (this.marker != false)
2153 {
2154 AENode.setAttribute("marker",this.marker*100);
2155 }
2156 return AENode;
2157 };
1562 }; 2158 };
1563 2159
1564 this.commentQuestionNode = function(xml) { 2160 this.commentQuestionNode = function(xml) {
1565 this.childOption = function(element) { 2161 this.id = null;
2162 this.type = undefined;
2163 this.question = undefined;
2164 this.options = [];
2165 this.statement = undefined;
2166
2167 this.childOption = function() {
1566 this.type = 'option'; 2168 this.type = 'option';
1567 this.name = element.getAttribute('name'); 2169 this.name = null;
1568 this.text = element.textContent; 2170 this.text = null;
1569 }; 2171 };
1570 this.id = xml.id; 2172 this.exportXML = function(root)
1571 if (xml.getAttribute('mandatory') == 'true') {this.mandatory = true;} 2173 {
1572 else {this.mandatory = false;} 2174 var CQNode = root.createElement("CommentQuestion");
1573 this.type = xml.getAttribute('type'); 2175 CQNode.id = this.id;
1574 if (this.type == undefined) {this.type = 'text';} 2176 CQNode.setAttribute("type",this.type);
1575 switch (this.type) { 2177 switch(this.type)
1576 case 'text': 2178 {
1577 this.question = xml.textContent; 2179 case "text":
1578 break; 2180 CQNode.textContent = this.question;
1579 case 'radio': 2181 break;
1580 var child = xml.firstElementChild; 2182 case "radio":
1581 this.options = []; 2183 var statement = root.createElement("statement");
1582 while (child != undefined) { 2184 statement.textContent = this.statement;
1583 if (child.nodeName == 'statement' && this.statement == undefined) { 2185 CQNode.appendChild(statement);
1584 this.statement = child.textContent; 2186 for (var i=0; i<this.options.length; i++)
1585 } else if (child.nodeName == 'option') { 2187 {
1586 this.options.push(new this.childOption(child)); 2188 var optionNode = root.createElement("option");
2189 optionNode.setAttribute("name",this.options[i].name);
2190 optionNode.textContent = this.options[i].text;
2191 CQNode.appendChild(optionNode);
1587 } 2192 }
1588 child = child.nextElementSibling; 2193 break;
1589 } 2194 case "checkbox":
1590 break; 2195 var statement = root.createElement("statement");
1591 case 'checkbox': 2196 statement.textContent = this.statement;
1592 var child = xml.firstElementChild; 2197 CQNode.appendChild(statement);
1593 this.options = []; 2198 for (var i=0; i<this.options.length; i++)
1594 while (child != undefined) { 2199 {
1595 if (child.nodeName == 'statement' && this.statement == undefined) { 2200 var optionNode = root.createElement("option");
1596 this.statement = child.textContent; 2201 optionNode.setAttribute("name",this.options[i].name);
1597 } else if (child.nodeName == 'option') { 2202 optionNode.textContent = this.options[i].text;
1598 this.options.push(new this.childOption(child)); 2203 CQNode.appendChild(optionNode);
1599 } 2204 }
1600 child = child.nextElementSibling; 2205 break;
1601 } 2206 }
1602 break; 2207 return CQNode;
1603 } 2208 };
2209 this.decode = function(xml) {
2210 this.id = xml.id;
2211 if (xml.getAttribute('mandatory') == 'true') {this.mandatory = true;}
2212 else {this.mandatory = false;}
2213 this.type = xml.getAttribute('type');
2214 if (this.type == undefined) {this.type = 'text';}
2215 switch (this.type) {
2216 case 'text':
2217 this.question = xml.textContent;
2218 break;
2219 case 'radio':
2220 var child = xml.firstElementChild;
2221 this.options = [];
2222 while (child != undefined) {
2223 if (child.nodeName == 'statement' && this.statement == undefined) {
2224 this.statement = child.textContent;
2225 } else if (child.nodeName == 'option') {
2226 var node = new this.childOption();
2227 node.name = child.getAttribute('name');
2228 node.text = child.textContent;
2229 this.options.push(node);
2230 }
2231 child = child.nextElementSibling;
2232 }
2233 break;
2234 case 'checkbox':
2235 var child = xml.firstElementChild;
2236 this.options = [];
2237 while (child != undefined) {
2238 if (child.nodeName == 'statement' && this.statement == undefined) {
2239 this.statement = child.textContent;
2240 } else if (child.nodeName == 'option') {
2241 var node = new this.childOption();
2242 node.name = child.getAttribute('name');
2243 node.text = child.textContent;
2244 this.options.push(node);
2245 }
2246 child = child.nextElementSibling;
2247 }
2248 break;
2249 }
2250 };
1604 }; 2251 };
1605
1606 this.id = xml.id;
1607 this.hostURL = xml.getAttribute('hostURL');
1608 this.sampleRate = xml.getAttribute('sampleRate');
1609 if (xml.getAttribute('randomiseOrder') == "true") {this.randomiseOrder = true;}
1610 else {this.randomiseOrder = false;}
1611 this.repeatCount = xml.getAttribute('repeatCount');
1612 if (xml.getAttribute('loop') == 'true') {this.loop = true;}
1613 else {this.loop == false;}
1614 if (xml.getAttribute('elementComments') == "true") {this.elementComments = true;}
1615 else {this.elementComments = false;}
1616
1617 this.preTest = new parent.prepostNode('pretest',xml.getElementsByTagName('PreTest'));
1618 this.postTest = new parent.prepostNode('posttest',xml.getElementsByTagName('PostTest'));
1619
1620 this.interfaces = [];
1621 var interfaceDOM = xml.getElementsByTagName('interface');
1622 for (var i=0; i<interfaceDOM.length; i++) {
1623 this.interfaces.push(new this.interfaceNode(interfaceDOM[i]));
1624 }
1625
1626 this.commentBoxPrefix = xml.getElementsByTagName('commentBoxPrefix');
1627 if (this.commentBoxPrefix.length != 0) {
1628 this.commentBoxPrefix = this.commentBoxPrefix[0].textContent;
1629 } else {
1630 this.commentBoxPrefix = "Comment on track";
1631 }
1632
1633 this.audioElements =[];
1634 var audioElementsDOM = xml.getElementsByTagName('audioElements');
1635 this.outsideReference = null;
1636 for (var i=0; i<audioElementsDOM.length; i++) {
1637 if (audioElementsDOM[i].getAttribute('type') == 'outsidereference') {
1638 if (this.outsideReference == null) {
1639 this.outsideReference = new this.audioElementNode(this,audioElementsDOM[i]);
1640 } else {
1641 console.log('Error only one audioelement can be of type outsidereference per audioholder');
1642 this.audioElements.push(new this.audioElementNode(this,audioElementsDOM[i]));
1643 console.log('Element id '+audioElementsDOM[i].id+' made into normal node');
1644 }
1645 } else {
1646 this.audioElements.push(new this.audioElementNode(this,audioElementsDOM[i]));
1647 }
1648 }
1649
1650 if (this.randomiseOrder) {
1651 this.audioElements = randomiseOrder(this.audioElements);
1652 }
1653
1654 this.commentQuestions = [];
1655 var commentQuestionsDOM = xml.getElementsByTagName('CommentQuestion');
1656 for (var i=0; i<commentQuestionsDOM.length; i++) {
1657 this.commentQuestions.push(new this.commentQuestionNode(commentQuestionsDOM[i]));
1658 }
1659 }; 2252 };
1660 } 2253 }
1661 2254
1662 function Interface(specificationObject) { 2255 function Interface(specificationObject) {
1663 // This handles the bindings between the interface and the audioEngineContext; 2256 // This handles the bindings between the interface and the audioEngineContext;
1664 this.specification = specificationObject; 2257 this.specification = specificationObject;
1665 this.insertPoint = document.getElementById("topLevelBody"); 2258 this.insertPoint = document.getElementById("topLevelBody");
2259
2260 this.newPage = function(audioHolderObject)
2261 {
2262 audioEngineContext.newTestPage();
2263 /// CHECK FOR SAMPLE RATE COMPATIBILITY
2264 if (audioHolderObject.sampleRate != undefined) {
2265 if (Number(audioHolderObject.sampleRate) != audioContext.sampleRate) {
2266 var errStr = 'Sample rates do not match! Requested '+Number(audioHolderObject.sampleRate)+', got '+audioContext.sampleRate+'. Please set the sample rate to match before completing this test.';
2267 alert(errStr);
2268 return;
2269 }
2270 }
2271
2272 audioEngineContext.loopPlayback = audioHolderObject.loop;
2273 // Delete any previous audioObjects associated with the audioEngine
2274 audioEngineContext.audioObjects = [];
2275 interfaceContext.deleteCommentBoxes();
2276 interfaceContext.deleteCommentQuestions();
2277 loadTest(audioHolderObject);
2278 };
1666 2279
1667 // Bounded by interface!! 2280 // Bounded by interface!!
1668 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels 2281 // Interface object MUST have an exportXMLDOM method which returns the various DOM levels
1669 // For example, APE returns the slider position normalised in a <value> tag. 2282 // For example, APE returns the slider position normalised in a <value> tag.
1670 this.interfaceObjects = []; 2283 this.interfaceObjects = [];
1671 this.interfaceObject = function(){}; 2284 this.interfaceObject = function(){};
1672 2285
1673 this.resizeWindow = function(event) 2286 this.resizeWindow = function(event)
1674 { 2287 {
2288 popup.resize(event);
1675 for(var i=0; i<this.commentBoxes.length; i++) 2289 for(var i=0; i<this.commentBoxes.length; i++)
1676 {this.commentBoxes[i].resize();} 2290 {this.commentBoxes[i].resize();}
1677 for(var i=0; i<this.commentQuestions.length; i++) 2291 for(var i=0; i<this.commentQuestions.length; i++)
1678 {this.commentQuestions[i].resize();} 2292 {this.commentQuestions[i].resize();}
1679 try 2293 try
2087 this.playbackObject; 2701 this.playbackObject;
2088 2702
2089 this.setTimePerPixel = function(audioObject) { 2703 this.setTimePerPixel = function(audioObject) {
2090 //maxTime must be in seconds 2704 //maxTime must be in seconds
2091 this.playbackObject = audioObject; 2705 this.playbackObject = audioObject;
2092 this.maxTime = audioObject.buffer.duration; 2706 this.maxTime = audioObject.buffer.buffer.duration;
2093 var width = 490; //500 - 10, 5 each side of the tracker head 2707 var width = 490; //500 - 10, 5 each side of the tracker head
2094 this.timePerPixel = this.maxTime/490; 2708 this.timePerPixel = this.maxTime/490;
2095 if (this.maxTime < 60) { 2709 if (this.maxTime < 60) {
2096 this.curTimeSpan.textContent = '0.00'; 2710 this.curTimeSpan.textContent = '0.00';
2097 } else { 2711 } else {
2199 var check_pass = true; 2813 var check_pass = true;
2200 var error_obj = []; 2814 var error_obj = [];
2201 for (var i = 0; i<audioEngineContext.audioObjects.length; i++) 2815 for (var i = 0; i<audioEngineContext.audioObjects.length; i++)
2202 { 2816 {
2203 var object = audioEngineContext.audioObjects[i]; 2817 var object = audioEngineContext.audioObjects[i];
2204 var time = object.buffer.duration; 2818 var time = object.buffer.buffer.duration;
2205 var metric = object.metric; 2819 var metric = object.metric;
2206 var passed = false; 2820 var passed = false;
2207 for (var j=0; j<metric.listenTracker.length; j++) 2821 for (var j=0; j<metric.listenTracker.length; j++)
2208 { 2822 {
2209 var bt = metric.listenTracker[j].getElementsByTagName('buffertime'); 2823 var bt = metric.listenTracker[j].getElementsByTagName('buffertime');
2223 error_obj.push(i); 2837 error_obj.push(i);
2224 } 2838 }
2225 } 2839 }
2226 if (check_pass == false) 2840 if (check_pass == false)
2227 { 2841 {
2228 var str_start = "You have not listened to fragments "; 2842 var str_start = "You have not completely listened to fragments ";
2229 for (var i=0; i<error_obj.length; i++) 2843 for (var i=0; i<error_obj.length; i++)
2230 { 2844 {
2231 str_start += error_obj[i]; 2845 str_start += error_obj[i];
2232 if (i != error_obj.length-1) 2846 if (i != error_obj.length-1)
2233 { 2847 {
2237 str_start += ". Please keep listening"; 2851 str_start += ". Please keep listening";
2238 console.log("[ALERT]: "+str_start); 2852 console.log("[ALERT]: "+str_start);
2239 alert(str_start); 2853 alert(str_start);
2240 } 2854 }
2241 }; 2855 };
2856 this.checkAllMoved = function()
2857 {
2858 var str = "You have not moved ";
2859 var failed = [];
2860 for (var i in audioEngineContext.audioObjects)
2861 {
2862 if(audioEngineContext.audioObjects[i].metric.wasMoved == false && audioEngineContext.audioObjects[i].specification.type != 'outsidereference')
2863 {
2864 failed.push(audioEngineContext.audioObjects[i].id);
2865 }
2866 }
2867 if (failed.length == 0)
2868 {
2869 return true;
2870 } else if (failed.length == 1)
2871 {
2872 str += 'track '+failed[0];
2873 } else {
2874 str += 'tracks ';
2875 for (var i=0; i<failed.length-1; i++)
2876 {
2877 str += failed[i]+', ';
2878 }
2879 str += 'and '+failed[i];
2880 }
2881 str +='.';
2882 alert(str);
2883 console.log(str);
2884 return false;
2885 };
2886 this.checkAllPlayed = function()
2887 {
2888 var str = "You have not played ";
2889 var failed = [];
2890 for (var i in audioEngineContext.audioObjects)
2891 {
2892 if(audioEngineContext.audioObjects[i].metric.wasListenedTo == false)
2893 {
2894 failed.push(audioEngineContext.audioObjects[i].id);
2895 }
2896 }
2897 if (failed.length == 0)
2898 {
2899 return true;
2900 } else if (failed.length == 1)
2901 {
2902 str += 'track '+failed[0];
2903 } else {
2904 str += 'tracks ';
2905 for (var i=0; i<failed.length-1; i++)
2906 {
2907 str += failed[i]+', ';
2908 }
2909 str += 'and '+failed[i];
2910 }
2911 str +='.';
2912 alert(str);
2913 console.log(str);
2914 return false;
2915 };
2242 } 2916 }