Mercurial > hg > jslab
comparison src/samer/audio/FileSource.java @ 1:5df24c91468d
Oh my what a mess.
author | samer |
---|---|
date | Fri, 05 Apr 2019 16:26:00 +0100 |
parents | bf79fb79ee13 |
children |
comparison
equal
deleted
inserted
replaced
0:bf79fb79ee13 | 1:5df24c91468d |
---|---|
17 import samer.tools.*; | 17 import samer.tools.*; |
18 import javax.sound.sampled.*; | 18 import javax.sound.sampled.*; |
19 import javax.swing.*; | 19 import javax.swing.*; |
20 import java.io.*; | 20 import java.io.*; |
21 import java.util.*; | 21 import java.util.*; |
22 import java.nio.ByteBuffer; | |
22 | 23 |
23 /** | 24 /** |
24 An AudioSource that read from multiple audio files. Can read any | 25 An AudioSource that read from multiple audio files. Can read any |
25 format for which the appropriate JavaSound plug-in is installed on | 26 format for which the appropriate JavaSound plug-in is installed on |
26 your system, eg WAV, AU, MP3, OGG. The implementation of | 27 your system, eg WAV, AU, MP3, OGG. The implementation of |
43 | 44 |
44 public class FileSource extends Viewable implements AudioSource, Agent | 45 public class FileSource extends Viewable implements AudioSource, Agent |
45 { | 46 { |
46 List<File> list=null; | 47 List<File> list=null; |
47 ListIterator<File> it=null; | 48 ListIterator<File> it=null; |
48 boolean loop; | 49 boolean loop, buffered=true; |
49 int channelsToMix; | 50 int channelsToMix; |
50 VFile curFile; | 51 VFile curFile; |
51 InputStream in=null; | 52 InputStream in=null; |
52 AudioFormat format=null; | 53 AudioFormat format=null, inFormat=null; |
53 byte[] byte_buf=null; | 54 byte[] byte_buf=null; |
54 int chunk=0; | 55 int chunk=0, bytesPerSample=0; |
55 | 56 |
56 /** | 57 /** |
57 * Construct a FileSource initialised with current file and loop initialised | 58 * Construct a FileSource initialised with current file and loop initialised |
58 * from the current Environment. No exception is thrown if the current file | 59 * from the current Environment. No exception is thrown if the current file |
59 * cannot be opened, however, you must be sure to set it to a valid file | 60 * cannot be opened, however, you must be sure to set it to a valid file |
70 | 71 |
71 setAgent(this); | 72 setAgent(this); |
72 Shell.registerViewable(this); | 73 Shell.registerViewable(this); |
73 } | 74 } |
74 | 75 |
76 // AudioSource interface methods | |
75 public void dispose() { | 77 public void dispose() { |
76 close(); | 78 close(); |
77 Shell.deregisterViewable(this); | 79 Shell.deregisterViewable(this); |
78 curFile.dispose(); | 80 curFile.dispose(); |
79 } | 81 } |
80 | 82 |
83 public int getChannels() { return format.getChannels(); } | |
84 public float getRate() { return format.getFrameRate(); } | |
85 | |
81 /** Returns current file */ | 86 /** Returns current file */ |
82 public File getFile() { return curFile.getFile(); } | 87 public File getFile() { return curFile.getFile(); } |
83 public void setTargetFormat(AudioFormat f) { format=f; } | 88 public void setBuffering(boolean b) { buffered=b; } |
89 | |
90 /** The actual format of the stream in. May not be same as target format. **/ | |
91 public AudioFormat getStreamFormat() { return inFormat; } | |
92 | |
93 /** The requested audio format. **/ | |
94 public AudioFormat getTargetFormat() { return format; } | |
95 public void setTargetFormat(AudioFormat f) { format=f; } | |
84 | 96 |
85 /** If true then loop playlist, otherwise, an Exception will be thrown | 97 /** If true then loop playlist, otherwise, an Exception will be thrown |
86 * when the end of the playlist is reached. If there is no playlist, then | 98 * when the end of the playlist is reached. If there is no playlist, then |
87 * looping applies to the current file only. */ | 99 * looping applies to the current file only. */ |
88 public void setLoop(boolean f) { loop=f; } | 100 public void setLoop(boolean f) { loop=f; } |
151 * from head of given file. If setFormat() was called previously, then | 163 * from head of given file. If setFormat() was called previously, then |
152 * we will attempt to do format conversion. */ | 164 * we will attempt to do format conversion. */ |
153 private synchronized void openCurrent() throws Exception | 165 private synchronized void openCurrent() throws Exception |
154 { | 166 { |
155 File file=curFile.getFile(); | 167 File file=curFile.getFile(); |
156 Shell.trace("\nOpening audio file: "+file); | 168 Shell.trace("\nFileSource:Opening audio file "+file); |
157 | 169 |
158 AudioInputStream s=AudioSystem.getAudioInputStream(file); | 170 AudioInputStream s=AudioSystem.getAudioInputStream(file); |
159 AudioFormat af=s.getFormat(); | 171 AudioFormat fmt1, af=s.getFormat(); |
160 long frames=file.length()/af.getFrameSize(); | |
161 | 172 |
162 Shell.trace(" format: "+af); | 173 Shell.trace(" format: "+af); |
163 Shell.trace(" duration: "+(long)(frames/af.getFrameRate())+" s"); | |
164 | 174 |
165 // convert to target format if required | 175 // convert to target format if required |
166 if (format!=null) { | 176 if (format!=null) { |
167 if (!format.equals(af)) { | 177 if (!format.equals(af)) { |
168 Shell.trace(" converting to "+format); | 178 Shell.trace(" converting to "+format); |
169 if (af.getChannels()>format.getChannels()) { | 179 if (af.getChannels()>format.getChannels()) { |
180 int frameSize = af.getChannels()*format.getSampleSizeInBits()/8; | |
170 Shell.trace(" channels mix down required."); | 181 Shell.trace(" channels mix down required."); |
171 AudioFormat fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(), | 182 fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(), |
172 format.getSampleSizeInBits(), | 183 format.getSampleSizeInBits(), |
173 af.getChannels(), format.getFrameSize(), format.getFrameRate(), format.isBigEndian()); | 184 af.getChannels(), frameSize, format.getFrameRate(), format.isBigEndian()); |
174 | 185 |
175 channelsToMix = af.getChannels(); | 186 channelsToMix = af.getChannels(); |
176 s=convertFormat(s,af,fmt1); | |
177 } else { | 187 } else { |
178 channelsToMix = 0; | 188 channelsToMix = 0; |
179 s=convertFormat(s,af,format); | 189 fmt1=format; |
180 } | 190 } |
181 } | 191 Shell.trace(" converting via "+fmt1); |
182 } | 192 s=convertFormat(s,af,fmt1); |
193 inFormat = fmt1; | |
194 } else { | |
195 Shell.trace(" no formation conversion required"); | |
196 channelsToMix = 0; | |
197 inFormat = af; | |
198 } | |
199 } else { | |
200 Shell.trace(" using stream native format"); | |
201 channelsToMix = 0; | |
202 inFormat = af; | |
203 } | |
204 in=s; | |
205 if (buffered) in = new BufferedInputStream(in,64*1024); | |
183 | 206 |
184 // If we have a reader task, then update the byte buffer | 207 // If we have a reader task, then update the byte buffer |
185 if (chunk>0) setByteBuffer(); | 208 if (chunk>0) setByteBuffer(); |
186 | |
187 // Shell.trace("stream format: "+s.getFormat()); | |
188 in = new BufferedInputStream(s,16*1024); | |
189 } | 209 } |
190 | 210 |
191 /** Returns number of bytes available in current file */ | 211 /** Returns number of bytes available in current file */ |
192 public int available() throws Exception { return in.available(); } | 212 public int available() throws Exception { return in.available(); } |
193 | 213 |
194 /** Reopen current file, so that next read will be from head of file. */ | 214 /** Reopen current file, so that next read will be from head of file. */ |
195 public synchronized void reopen() throws Exception { close(); openCurrent(); } | 215 public synchronized void reopen() throws Exception { close(); openCurrent(); } |
196 | 216 |
197 private void setChunkSize(int s) { chunk=s; } | 217 private void setBlockSize(int s) { chunk=s; } |
198 private void setByteBuffer() { | 218 private void setByteBuffer() { |
199 byte_buf = new byte[2*chunk*(channelsToMix>0 ? channelsToMix : 1)]; | 219 bytesPerSample = 2*(channelsToMix>0 ? channelsToMix : 1); |
220 byte_buf = new byte[chunk*bytesPerSample]; | |
221 } | |
222 | |
223 public int readInto(ByteBuffer buf, int offset, int len) throws IOException { | |
224 return in.read(buf.array(), offset, len); | |
200 } | 225 } |
201 | 226 |
202 /** Returns a Task which copies samples as doubles into the given | 227 /** Returns a Task which copies samples as doubles into the given |
203 * buffer between the given positions. */ | 228 * buffer between the given positions. */ |
204 public Task reader(final double [] dbuf, final int off, final int len) { | 229 public Task reader(final double [] dbuf, final int off, final int len) { |
205 setChunkSize(len); | 230 setBlockSize(len); |
206 if (in!=null) setByteBuffer(); | 231 if (in!=null) setByteBuffer(); |
207 | 232 |
208 return new AnonymousTask() { | 233 return new AnonymousTask() { |
209 public void run() throws Exception { | 234 public void run() throws Exception { |
210 int n = 0; | |
211 | |
212 synchronized (FileSource.this) { | 235 synchronized (FileSource.this) { |
213 int blen = byte_buf.length; | 236 // loop until len samples copied into dbuf |
214 while (n < blen) { | 237 int rem=len, pos=off; |
215 int count = in.read(byte_buf, n, blen - n); | 238 while (rem>0) { |
216 if (count > 0) n+=count; | 239 int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample); |
217 else if (it!=null) next(); | 240 if (bytesRead > 0) { // append this chunk to output |
241 int count=bytesRead/bytesPerSample; | |
242 if (channelsToMix>0) { | |
243 Util.shortToDoubleMixDown(byte_buf,dbuf,pos,count,channelsToMix); | |
244 } else { | |
245 Util.shortToDouble(byte_buf,dbuf,pos,count); | |
246 } | |
247 pos+=count; rem-=count; | |
248 } else if (it!=null) next(); // next file if there is one | |
249 else if (!loop) throw new EOFException(); // not looping and no more files | |
250 else reopen(); // back to first file | |
251 | |
252 // if (rem>0) Shell.trace("Read "+bytesRead+" bytes, need "+rem+" more samples."); | |
253 } | |
254 } | |
255 } | |
256 }; | |
257 } | |
258 | |
259 /** Returns a Task which copies samples as floats into the given | |
260 * buffer between the given positions. */ | |
261 public Task reader(final float [] dbuf, final int off, final int len) { | |
262 setBlockSize(len); | |
263 if (in!=null) setByteBuffer(); | |
264 | |
265 return new AnonymousTask() { | |
266 public synchronized void run() throws Exception { | |
267 synchronized (FileSource.this) { | |
268 // loop until len samples copied into dbuf | |
269 int rem=len, pos=off; | |
270 while (rem>0) { | |
271 int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample); | |
272 if (bytesRead > 0) { | |
273 int count=bytesRead/bytesPerSample; | |
274 if (channelsToMix>0) { | |
275 Util.shortToFloatMixDown(byte_buf,dbuf,pos,count,channelsToMix); | |
276 } else { | |
277 Util.shortToFloat(byte_buf,dbuf,pos,count); | |
278 } | |
279 pos+=count; rem-=count; | |
280 } else if (it!=null) next(); | |
218 else if (!loop) throw new EOFException(); | 281 else if (!loop) throw new EOFException(); |
219 else reopen(); | 282 else reopen(); |
220 } | 283 } |
221 } | 284 } |
222 if (channelsToMix>0) Util.shortToDoubleMixDown(byte_buf,dbuf,off,len,channelsToMix); | |
223 else Util.shortToDouble(byte_buf,dbuf,off,len); | |
224 } | |
225 }; | |
226 } | |
227 | |
228 /** Returns a Task which copies samples as floats into the given | |
229 * buffer between the given positions. */ | |
230 public Task reader(final float [] dbuf, final int off, final int len) { | |
231 setChunkSize(len); | |
232 if (in!=null) setByteBuffer(); | |
233 | |
234 return new AnonymousTask() { | |
235 public synchronized void run() throws Exception { | |
236 int n = 0; | |
237 | |
238 synchronized (FileSource.this) { | |
239 int blen = byte_buf.length; | |
240 while (n < blen) { | |
241 int count = in.read(byte_buf, n, blen - n); | |
242 if (count > 0) n+=count; | |
243 else if (it!=null) next(); | |
244 else if (!loop) throw new EOFException(); | |
245 else reopen(); | |
246 } | |
247 } | |
248 if (channelsToMix>0) Util.shortToFloatMixDown(byte_buf,dbuf,off,len,channelsToMix); | |
249 else Util.shortToFloat(byte_buf,dbuf,off,len); | |
250 } | 285 } |
251 }; | 286 }; |
252 } | 287 } |
253 | 288 |
254 // Agent | 289 // Agent |
316 | 351 |
317 | 352 |
318 | 353 |
319 private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception | 354 private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception |
320 { | 355 { |
321 if (fin==fout) return sin; | 356 Shell.trace("\nconvertFormat:"); |
357 Shell.trace(" | source: "+fin.toString()); | |
358 Shell.trace(" | target: "+fout.toString()); | |
359 | |
360 if (fin.equals(fout)) return sin; | |
322 else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) { | 361 else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) { |
323 try { return AudioSystem.getAudioInputStream(fout,sin); } | 362 try { |
363 Shell.trace(" | Trying direct (PCM) from "+fin.getEncoding().toString()); | |
364 return AudioSystem.getAudioInputStream(fout,sin); | |
365 } | |
324 catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); } | 366 catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); } |
325 | 367 |
326 AudioFormat fint = new AudioFormat( // PCM | 368 AudioFormat fint = new AudioFormat( // PCM |
327 fout.getSampleRate(), fout.getSampleSizeInBits(), | 369 fout.getSampleRate(), fout.getSampleSizeInBits(), |
328 fin.getChannels(), true, fout.isBigEndian()); | 370 fin.getChannels(), true, fout.isBigEndian()); |
329 Shell.trace("Trying PCM conversion via "+fint.toString()); | 371 Shell.trace(" | Trying PCM conversion via "+fint.toString()); |
330 return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin)); | 372 return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin)); |
331 } else { | 373 } else { |
332 // First, check for MP3 - if so, cannot convert number of channels | 374 // First, check for MP3 - if so, cannot convert number of channels |
333 if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) { | 375 if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) { |
334 Shell.trace("Trying direct decoding from "+fin.getEncoding().toString()); | 376 Shell.trace(" | Trying decoding from "+fin.getEncoding().toString()); |
335 return AudioSystem.getAudioInputStream(fout,sin); | 377 return AudioSystem.getAudioInputStream(fout,sin); |
336 } else { | 378 } else { |
337 AudioFormat fint = new AudioFormat( | 379 AudioFormat fint = new AudioFormat( |
338 fin.getSampleRate(), fout.getSampleSizeInBits(), | 380 fin.getSampleRate(), fout.getSampleSizeInBits(), |
339 fin.getChannels(), true, fout.isBigEndian()); | 381 fin.getChannels(), true, fout.isBigEndian()); |
340 Shell.trace("Trying conversion via "+fint.toString()); | 382 Shell.trace(" | Trying recursive via "+fint.toString()); |
341 return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout); | 383 return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout); |
342 } | 384 } |
343 } | 385 } |
344 } | 386 } |
345 } | 387 } |