Merge branch 'Jalview-BH/JAL-3026-JAL-3063-JAXB' of
[jalview.git] / srcjar / javajs / util / JSAudioThread.java
1 package javajs.util;    
2
3 import javax.sound.sampled.AudioFormat;
4 import javax.sound.sampled.AudioSystem;
5 import javax.sound.sampled.DataLine;
6 import javax.sound.sampled.SourceDataLine;
7
8 import sun.audio.AudioData;
9 import sun.audio.AudioDataStream;
10 import sun.audio.AudioPlayer;
11
12
13 /**
14  * The JSAudioThread adds a layer to JSThread that is specific for audio.
15  * This class utilizes JSAudioLine, which is not fully fleshed out.
16  * 
17  * Two very simple interfaces,
18  * 
19  * (new JSAudioThread()).playULawData(byte[] b);
20  * 
21  * and
22  * 
23  * (new JSAudioThread(audioFormat)).playOnce(byte[] b, offset, length);
24  * 
25  * allow straightforward audio production without having to work with
26  * SourceDataLine directly.
27  * 
28  * As a JSThread, this class implements myInit(), isLooping(), myLoop(),
29  * whenDone(), onException(), and doFinally().
30  * 
31  * If the constructor JSAudioThread(JSAudioThreadUser, AudioFormat, byte[]) is
32  * used, then the JSAudioThreadUser must simply implement fillAudioBuffer(),
33  * checkSoundStatus(), and audioThreadExiting() for very simple streaming audio.
34  * JSAudioThread will then take care of all the timing issues.
35  * 
36  * But the implementer is welcome to override any of the JSThread overrides
37  * themselves in order to customize this further.
38  * 
39  * The standard streaming case, then is:
40  * 
41  * audioThread = new JSAudioThread(audioUser, audioFormat, audioByteBuffer);
42  * audioThread.start();
43  * 
44  * where audioUser provides 
45  * 
46  * checkSoundStatus() (called in isLooping()),
47  * 
48  * fillAudioBuffer() (called in myLoop()),
49  * 
50  * and
51  * 
52  * audioThreadExiting() (called in doFinally()).
53  * 
54  * @author Bob Hanson
55  * 
56  */
57 public class JSAudioThread extends JSThread {
58         
59         protected Owner owner;
60         protected boolean done;
61         protected int myBufferLength;
62         protected SourceDataLine line;
63         protected int rate, nChannels, bitsPerSample;
64         
65         protected byte[] audioByteBuffer;
66         protected int audioBufferByteLength;
67
68         private AudioFormat audioFormat;
69         private int myBufferOffset;
70         private int playCount;
71         
72         public JSAudioThread(Owner owner, AudioFormat audioFormat, byte[] audioByteBuffer) {
73                 this.owner = owner;
74                 setFormat(audioFormat);
75                 setBuffer(audioByteBuffer);
76         }
77
78         /**
79          * A convenience constructor requiring standard settings of 
80          * signed (for 8-bit) and littleEndian (for 16-bit)
81          *   
82          * @param owner
83          * @param rate
84          * @param bitsPerSample
85          * @param nChannels
86          * @param audioByteBuffer
87          */
88         public JSAudioThread(Owner owner, int rate, int bitsPerSample, int nChannels, byte[] audioByteBuffer) {
89                 this.owner = owner;
90                 setFormat(new AudioFormat(rate, bitsPerSample, nChannels, true, false));
91                 setBuffer(audioByteBuffer);
92         }
93         
94         /**
95          * primarily available for (new JSAudioThread()).playULawData
96          * 
97          */
98         public JSAudioThread() {
99         }
100
101         /**
102          * primarily available for (new JSAudioThread()).playOnce
103          * 
104          */
105         public JSAudioThread(AudioFormat audioFormat) {
106                 setFormat(audioFormat);
107         }
108
109         /**
110          * 
111          * A simple 8-bit uLaw data player
112          * 
113          * @param data
114          */
115         public void playULawData(byte[] data) {
116                 // this constructor uses default new AudioFormat(ULAW,8000,8,1,1,8000,true)
117                 // threading is taken care of by the browser in JavaScript
118                 AudioPlayer.player.start(new AudioDataStream(new AudioData(data)));
119         }
120
121
122         /**
123          * Just play once through; no additions
124          * 
125          * @param data
126          */
127         public void playOnce(byte[] data, int offset, int length) {
128                 setBuffer(data);
129                 myBufferOffset = offset;
130                 myBufferLength = length;
131                 playCount = 1;
132                 start();
133         }
134
135         public void setBuffer(byte[] audioByteBuffer) {
136                 this.audioByteBuffer = audioByteBuffer;
137                 audioBufferByteLength = audioByteBuffer.length;
138         }
139         
140         public SourceDataLine getLine() {
141                 return line;
142         }
143         
144         public AudioFormat getFormat() {
145                 return audioFormat;
146         }
147                         
148         public void setFormat(AudioFormat audioFormat) {
149                 this.audioFormat = audioFormat;
150                 rate = (int) audioFormat.getSampleRate();
151                 bitsPerSample = audioFormat.getSampleSizeInBits();
152                 nChannels = audioFormat.getChannels();                  
153         }
154
155         public void resetAudio() {
156                 if (line == null)
157                         return;
158                 line.flush();
159                 line.close();
160                 line = null;
161         }
162
163         /**
164          * Standard initialization of a SourceDataLine
165          * 
166          */
167         @Override
168         protected boolean myInit() {
169                 try {
170                         DataLine.Info info = new DataLine.Info(SourceDataLine.class, audioFormat);
171                         if (line != null)
172                                 line.close();
173                         line = (SourceDataLine) AudioSystem.getLine(info);
174                         line.open(audioFormat, audioBufferByteLength);
175                         line.start();
176                 } catch (Exception e) {
177                         e.printStackTrace();
178                         return false;
179                 }
180                 return true;
181         }
182         
183         @Override
184         protected boolean isLooping() {
185                 return !done && (--playCount >= 0 || owner != null && owner.checkSoundStatus());
186         }
187
188         @Override
189         protected boolean myLoop() {
190                 if (!done) {
191                         if ((myBufferLength = (owner == null ? myBufferLength : owner.fillAudioBuffer())) <= 0)
192                                 return !(done = true);
193                         try {
194                                 if (line == null)
195                                         myInit();                                       
196                                 line.write(audioByteBuffer, myBufferOffset, myBufferLength);
197                         } catch (Exception e) {
198                                 e.printStackTrace();
199                                 done = true;
200                         }
201                 }
202                 return !done;
203         }
204
205         @Override
206         protected void whenDone() {
207                 done = true;
208                 resetAudio();
209         }
210
211         @Override
212         protected int getDelayMillis() {
213                 // about 25% of the actual play time
214         return 1000                                        // ms/sec 
215                         * (myBufferLength  * 8 / bitsPerSample)        // * samples 
216                         / rate                                         // * seconds/sample) 
217                         / nChannels                                    // / number of channels
218                         / 4;                                           // * 25%
219         }
220
221         @Override
222         protected void onException(Exception e) {
223                 e.printStackTrace();
224         }
225
226         @Override
227         protected void doFinally() {
228                 if (owner != null)
229                         owner.audioThreadExiting();
230         }
231
232         public interface Owner {
233
234                 /**
235                  * 
236                  * @return true if thread should continue; false if not
237                  * 
238                  */
239                 boolean checkSoundStatus();
240
241                 /** fill audio buffer
242                  * 
243                  * @return number of bytes to write to audio line
244                  *
245                  */
246                 int fillAudioBuffer();
247
248                 /**
249                  *  called from the finally clause when complete 
250                  * 
251                  */
252                 void audioThreadExiting();
253                 
254
255         }
256
257 }
258