samer@1
|
1 /*
|
samer@1
|
2 * FileSource2.java
|
samer@1
|
3 *
|
samer@1
|
4 * Copyright (c) 2000, Samer Abdallah, King's College London.
|
samer@1
|
5 * All rights reserved.
|
samer@1
|
6 *
|
samer@1
|
7 * This software is provided AS iS and WITHOUT ANY WARRANTY;
|
samer@1
|
8 * without even the implied warranty of MERCHANTABILITY or
|
samer@1
|
9 * FITNESS FOR A PARTICULAR PURPOSE.
|
samer@1
|
10 */
|
samer@1
|
11
|
samer@1
|
12 package samer.audio;
|
samer@1
|
13
|
samer@1
|
14 import samer.core.*;
|
samer@1
|
15 import samer.core.types.*;
|
samer@1
|
16 import samer.core.util.*;
|
samer@1
|
17 import samer.tools.*;
|
samer@1
|
18 import javax.sound.sampled.*;
|
samer@1
|
19 import javax.swing.*;
|
samer@1
|
20 import java.io.*;
|
samer@1
|
21 import java.util.*;
|
samer@1
|
22 import java.nio.ByteBuffer;
|
samer@1
|
23
|
samer@1
|
24 /**
|
samer@1
|
25 An AudioSource that read from multiple audio files. Can read any
|
samer@1
|
26 format for which the appropriate JavaSound plug-in is installed on
|
samer@1
|
27 your system, eg WAV, AU, MP3, OGG. The implementation of
|
samer@1
|
28 AudioSource.reader() returns a Task which gets samples from the audio
|
samer@1
|
29 files, going through the play list one by one.
|
samer@1
|
30 An EOFException is thrown if an attempt is made
|
samer@1
|
31 to read beyond the end of the last file.
|
samer@1
|
32
|
samer@1
|
33 FileSource2 is a Viewable called "playlist", and an Agent with commands
|
samer@1
|
34 next, view.
|
samer@1
|
35
|
samer@1
|
36 */
|
samer@1
|
37
|
samer@1
|
38 public class FileSource2 extends Viewable implements AudioSource, Agent
|
samer@1
|
39 {
|
samer@1
|
40 List<File> list=null;
|
samer@1
|
41 ListIterator<File> it=null;
|
samer@1
|
42 int channelsToMix;
|
samer@1
|
43 File curFile=null;
|
samer@1
|
44 InputStream in=null;
|
samer@1
|
45 AudioFormat targetFormat=null;
|
samer@1
|
46 byte[] byte_buf=null;
|
samer@1
|
47 int chunk=0, bytesPerSample=0;
|
samer@1
|
48
|
samer@1
|
49 /**
|
samer@1
|
50 * Construct a FileSource2.
|
samer@1
|
51 */
|
samer@1
|
52 public FileSource2(AudioFormat targetFormat, List<File> files)
|
samer@1
|
53 {
|
samer@1
|
54 super("playlist");
|
samer@1
|
55
|
samer@1
|
56 setAgent(this);
|
samer@1
|
57 Shell.registerViewable(this);
|
samer@1
|
58 this.targetFormat=targetFormat;
|
samer@1
|
59 this.list=files;
|
samer@1
|
60 it=list.listIterator();
|
samer@1
|
61 }
|
samer@1
|
62
|
samer@1
|
63 // AudioSource interface methods
|
samer@1
|
64 public void dispose() {
|
samer@1
|
65 close();
|
samer@1
|
66 Shell.deregisterViewable(this);
|
samer@1
|
67 }
|
samer@1
|
68
|
samer@1
|
69 public int getChannels() { return targetFormat.getChannels(); }
|
samer@1
|
70 public float getRate() { return targetFormat.getFrameRate(); }
|
samer@1
|
71
|
samer@1
|
72 /** Returns current file */
|
samer@1
|
73 public File getFile() { return curFile; }
|
samer@1
|
74
|
samer@1
|
75 /** The requested audio format. **/
|
samer@1
|
76 public AudioFormat getTargetFormat() { return targetFormat; }
|
samer@1
|
77
|
samer@1
|
78 /** Set playlist to all WAV files in given directory */
|
samer@1
|
79 public static List<File> directory(File dir, String ext) {
|
samer@1
|
80 return Arrays.asList(dir.listFiles(getFileFilter(ext)));
|
samer@1
|
81 }
|
samer@1
|
82
|
samer@1
|
83 public synchronized List<File> getPlaylist() { return list; }
|
samer@1
|
84
|
samer@1
|
85 /** Move to head of next file in playlist */
|
samer@1
|
86 public synchronized void next() throws Exception
|
samer@1
|
87 {
|
samer@1
|
88 boolean wasOpen=isOpen();
|
samer@1
|
89
|
samer@1
|
90 if (wasOpen) close();
|
samer@1
|
91 if (!it.hasNext()) throw new EOFException();
|
samer@1
|
92 curFile=it.next();
|
samer@1
|
93 if (wasOpen) openCurrent();
|
samer@1
|
94 }
|
samer@1
|
95
|
samer@1
|
96 public boolean isOpen() { return in!=null; }
|
samer@1
|
97
|
samer@1
|
98 /** Closes current input stream */
|
samer@1
|
99 public synchronized void close() {
|
samer@1
|
100 try {
|
samer@1
|
101 if (in!=null) {
|
samer@1
|
102 Shell.trace("Closing audio stream...");
|
samer@1
|
103 in.close(); in=null; byte_buf=null;
|
samer@1
|
104 }
|
samer@1
|
105 }
|
samer@1
|
106 catch (IOException ex) {}
|
samer@1
|
107 }
|
samer@1
|
108
|
samer@1
|
109 /** Opens the playlist starting with the first file. */
|
samer@1
|
110 public synchronized void open() throws Exception {
|
samer@1
|
111 it=list.listIterator(); next();
|
samer@1
|
112 if (!isOpen()) openCurrent();
|
samer@1
|
113 }
|
samer@1
|
114
|
samer@1
|
115 /** Opens the current file. Next read will returns samples
|
samer@1
|
116 * from head of given file. If setFormat() was called previously, then
|
samer@1
|
117 * we will attempt to do format conversion. */
|
samer@1
|
118 private synchronized void openCurrent() throws Exception
|
samer@1
|
119 {
|
samer@1
|
120 File file=curFile;
|
samer@1
|
121 Shell.trace("\nFileSource:Opening audio file "+file);
|
samer@1
|
122
|
samer@1
|
123 AudioInputStream ain=AudioSystem.getAudioInputStream(file);
|
samer@1
|
124
|
samer@1
|
125 // convert to target format if required
|
samer@1
|
126 if (targetFormat==null) {
|
samer@1
|
127 AudioFormat fin=ain.getFormat();
|
samer@1
|
128 ain=convertFormat(new AudioFormat( fin.getSampleRate(), 16, fin.getChannels(), true, false), ain);
|
samer@1
|
129 } else {
|
samer@1
|
130 ain=convertFormat(targetFormat, ain);
|
samer@1
|
131 }
|
samer@1
|
132 bytesPerSample=ain.getFormat().getSampleSizeInBits()/8;
|
samer@1
|
133 if (bytesPerSample!=2) throw new Exception("Must be 16 bits per sample");
|
samer@1
|
134
|
samer@1
|
135 in = new BufferedInputStream(ain,64*1024);
|
samer@1
|
136
|
samer@1
|
137 // If we have a reader task, then update the byte buffer
|
samer@1
|
138 if (chunk>0) setByteBuffer();
|
samer@1
|
139 }
|
samer@1
|
140
|
samer@1
|
141 /** Returns number of bytes available in current file */
|
samer@1
|
142 public int available() throws Exception { return in.available(); }
|
samer@1
|
143
|
samer@1
|
144 private void setBlockSize(int s) { chunk=s; }
|
samer@1
|
145 private void setByteBuffer() {
|
samer@1
|
146 byte_buf = new byte[chunk*bytesPerSample];
|
samer@1
|
147 }
|
samer@1
|
148
|
samer@1
|
149 public int readInto(ByteBuffer buf, int offset, int len) throws IOException {
|
samer@1
|
150 return in.read(buf.array(), offset, len);
|
samer@1
|
151 }
|
samer@1
|
152
|
samer@1
|
153 /** Returns a Task which copies samples as doubles into the given
|
samer@1
|
154 * buffer between the given positions. */
|
samer@1
|
155 public Task reader(final double [] dbuf, final int off, final int len) {
|
samer@1
|
156 setBlockSize(len);
|
samer@1
|
157 if (in!=null) setByteBuffer();
|
samer@1
|
158
|
samer@1
|
159 return new AnonymousTask() {
|
samer@1
|
160 public void run() throws Exception {
|
samer@1
|
161 synchronized (FileSource2.this) {
|
samer@1
|
162 // loop until len samples copied into dbuf
|
samer@1
|
163 int rem=len, pos=off;
|
samer@1
|
164 while (rem>0) {
|
samer@1
|
165 int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample);
|
samer@1
|
166 if (bytesRead > 0) { // append this chunk to output
|
samer@1
|
167 int count=bytesRead/bytesPerSample;
|
samer@1
|
168 Util.shortToDouble(byte_buf,dbuf,pos,count);
|
samer@1
|
169 pos+=count; rem-=count;
|
samer@1
|
170 } else if (it!=null) next(); // next file if there is one
|
samer@1
|
171 else throw new EOFException(); // not looping and no more files
|
samer@1
|
172 }
|
samer@1
|
173 }
|
samer@1
|
174 }
|
samer@1
|
175 };
|
samer@1
|
176 }
|
samer@1
|
177
|
samer@1
|
178 /** Returns a Task which copies samples as floats into the given
|
samer@1
|
179 * buffer between the given positions. */
|
samer@1
|
180 public Task reader(final float [] dbuf, final int off, final int len) {
|
samer@1
|
181 setBlockSize(len);
|
samer@1
|
182 if (in!=null) setByteBuffer();
|
samer@1
|
183
|
samer@1
|
184 return new AnonymousTask() {
|
samer@1
|
185 public synchronized void run() throws Exception {
|
samer@1
|
186 synchronized (FileSource2.this) {
|
samer@1
|
187 // loop until len samples copied into dbuf
|
samer@1
|
188 int rem=len, pos=off;
|
samer@1
|
189 while (rem>0) {
|
samer@1
|
190 int bytesRead = in.read(byte_buf, 0, rem*bytesPerSample);
|
samer@1
|
191 if (bytesRead > 0) {
|
samer@1
|
192 int count=bytesRead/bytesPerSample;
|
samer@1
|
193 Util.shortToFloat(byte_buf,dbuf,pos,count);
|
samer@1
|
194 pos+=count; rem-=count;
|
samer@1
|
195 } else if (it!=null) next();
|
samer@1
|
196 else throw new EOFException();
|
samer@1
|
197 }
|
samer@1
|
198 }
|
samer@1
|
199 }
|
samer@1
|
200 };
|
samer@1
|
201 }
|
samer@1
|
202
|
samer@1
|
203 // Agent
|
samer@1
|
204 public void getCommands(Agent.Registry r) {
|
samer@1
|
205 r.add("next").add("view");
|
samer@1
|
206 }
|
samer@1
|
207
|
samer@1
|
208 public void execute(String cmd, Environment env) throws Exception {
|
samer@1
|
209 if (cmd.equals("next")) next();
|
samer@1
|
210 else if (cmd.equals("view")) {
|
samer@1
|
211 if (list!=null) {
|
samer@1
|
212 Shell.expose(
|
samer@1
|
213 new JScrollPane(new JList(list.toArray())),
|
samer@1
|
214 "playlist");
|
samer@1
|
215 }
|
samer@1
|
216 }
|
samer@1
|
217 }
|
samer@1
|
218
|
samer@1
|
219 // Viewable
|
samer@1
|
220 public Viewer getViewer() { return new FileSourceViewer(); }
|
samer@1
|
221 class FileSourceViewer extends DefaultViewer implements Agent {
|
samer@1
|
222 public FileSourceViewer() {
|
samer@1
|
223 super(FileSource2.this);
|
samer@1
|
224 //add(curFile);
|
samer@1
|
225 add(Shell.createButtonsFor(this));
|
samer@1
|
226 }
|
samer@1
|
227
|
samer@1
|
228 public void getCommands(Agent.Registry r) {
|
samer@1
|
229 r.add("next").add("view");
|
samer@1
|
230 }
|
samer@1
|
231 public void execute(String cmd, Environment env) throws Exception {
|
samer@1
|
232 FileSource2.this.execute(cmd,env);
|
samer@1
|
233 }
|
samer@1
|
234 };
|
samer@1
|
235
|
samer@1
|
236 private static java.io.FileFilter getFileFilter(final String ext) {
|
samer@1
|
237 return new java.io.FileFilter() {
|
samer@1
|
238 public boolean accept(File file) {
|
samer@1
|
239 return file.getName().toLowerCase().endsWith(ext);
|
samer@1
|
240 }
|
samer@1
|
241 };
|
samer@1
|
242 }
|
samer@1
|
243
|
samer@1
|
244 private static AudioInputStream convertVia(AudioFormat fout, AudioInputStream sin, AudioFormat fint) throws Exception
|
samer@1
|
245 {
|
samer@1
|
246 Shell.trace(" | Trying recursive via "+fint.toString());
|
samer@1
|
247 AudioInputStream sint=AudioSystem.getAudioInputStream(fint,sin);
|
samer@1
|
248 Shell.trace(" | Obtained "+sint.getFormat().toString());
|
samer@1
|
249 return convertFormat(fout, sint);
|
samer@1
|
250 }
|
samer@1
|
251
|
samer@1
|
252 private static AudioInputStream convertFormat(AudioFormat fout, AudioInputStream sin) throws Exception
|
samer@1
|
253 {
|
samer@1
|
254 AudioFormat fin=sin.getFormat();
|
samer@1
|
255
|
samer@1
|
256 if (fin.equals(fout)) return sin;
|
samer@1
|
257
|
samer@1
|
258 Shell.trace("\nconvertFormat:");
|
samer@1
|
259 Shell.trace(" | source: "+fin.toString());
|
samer@1
|
260 Shell.trace(" | target: "+fout.toString());
|
samer@1
|
261
|
samer@1
|
262 if (fin.getEncoding()!=AudioFormat.Encoding.PCM_SIGNED) {
|
samer@1
|
263 // first get into PCM encoding, then try recursive
|
samer@1
|
264 try {
|
samer@1
|
265 return convertVia( fout, sin, new AudioFormat(
|
samer@1
|
266 fin.getSampleRate(), fout.getSampleSizeInBits(),
|
samer@1
|
267 fin.getChannels(), true, fout.isBigEndian()));
|
samer@1
|
268 } catch (IllegalArgumentException ex) {
|
samer@1
|
269 Shell.trace("Direct conversion failed");
|
samer@1
|
270 }
|
samer@1
|
271 return convertVia( fout, sin, new AudioFormat(
|
samer@1
|
272 fin.getSampleRate(), fin.getSampleSizeInBits(),
|
samer@1
|
273 fin.getChannels(), true, fout.isBigEndian()));
|
samer@1
|
274 }
|
samer@1
|
275
|
samer@1
|
276 if (fin.getChannels()!=fout.getChannels() || fin.getSampleSizeInBits()!=fout.getSampleSizeInBits()) {
|
samer@1
|
277 // convert these before doing any sample rate conversion
|
samer@1
|
278 return convertVia(fout, sin, new AudioFormat(
|
samer@1
|
279 fin.getSampleRate(), fout.getSampleSizeInBits(),
|
samer@1
|
280 fout.getChannels(), true, fout.isBigEndian()));
|
samer@1
|
281 }
|
samer@1
|
282
|
samer@1
|
283 // the only thing left is sample rate
|
samer@1
|
284 return AudioSystem.getAudioInputStream(fout,sin);
|
samer@1
|
285 }
|
samer@1
|
286 }
|
samer@1
|
287
|