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 }