samer@1
|
1 /*
|
samer@1
|
2 * FileSource.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 */
|
samer@1
|
26
|
samer@1
|
27
|
samer@1
|
28 public abstract class MultiFileAudioStream extends InputStream
|
samer@1
|
29 {
|
samer@1
|
30 List<File> list=null;
|
samer@1
|
31 ListIterator<File> it=null;
|
samer@1
|
32 int channelsToMix;
|
samer@1
|
33 File curFile=null;
|
samer@1
|
34 InputStream in=null;
|
samer@1
|
35 AudioFormat format=null, inFormat=null;
|
samer@1
|
36 boolean loop=false;
|
samer@1
|
37
|
samer@1
|
38 /**
|
samer@1
|
39 * Construct a FileSource initialised with current file and loop initialised
|
samer@1
|
40 * from the current Environment. No exception is thrown if the current file
|
samer@1
|
41 * cannot be opened, however, you must be sure to set it to a valid file
|
samer@1
|
42 * (or to set a playlist) before trying to read any samples.
|
samer@1
|
43 */
|
samer@1
|
44 public MultiFileAudioStream(AudioFormat target, List<File> files)
|
samer@1
|
45 {
|
samer@1
|
46 list=files;
|
samer@1
|
47 format=target;
|
samer@1
|
48 }
|
samer@1
|
49
|
samer@1
|
50 public AudioFormat getFormat() { return format; }
|
samer@1
|
51
|
samer@1
|
52 /** Set playlist to all WAV files in given directory */
|
samer@1
|
53 public List<File> directory(File dir, String ext) {
|
samer@1
|
54 return Arrays.asList(dir.listFiles(getFileFilter(ext)));
|
samer@1
|
55 }
|
samer@1
|
56
|
samer@1
|
57 private static java.io.FileFilter getFileFilter(final String ext) {
|
samer@1
|
58 return new java.io.FileFilter() {
|
samer@1
|
59 public boolean accept(File file) {
|
samer@1
|
60 return file.getName().toLowerCase().endsWith(ext);
|
samer@1
|
61 }
|
samer@1
|
62 };
|
samer@1
|
63 }
|
samer@1
|
64
|
samer@1
|
65 public synchronized List<File> getPlaylist() { return list; }
|
samer@1
|
66
|
samer@1
|
67 /** Go back to start of playlist. Next block of samples will be from head
|
samer@1
|
68 * of first file in playlist. Will throw an exception if there is no playlist */
|
samer@1
|
69 public synchronized void rewind() throws IOException {
|
samer@1
|
70 it=list.listIterator(); next();
|
samer@1
|
71 }
|
samer@1
|
72
|
samer@1
|
73 /** Move to head of next file in playlist */
|
samer@1
|
74 public synchronized void next() throws IOException
|
samer@1
|
75 {
|
samer@1
|
76 boolean wasOpen=isOpen();
|
samer@1
|
77
|
samer@1
|
78 if (wasOpen) close();
|
samer@1
|
79 if (!it.hasNext()) {
|
samer@1
|
80 if (!loop) throw new EOFException();
|
samer@1
|
81 it=list.listIterator();
|
samer@1
|
82 if (!it.hasNext()) throw new IOException("no files in playlist");
|
samer@1
|
83 }
|
samer@1
|
84 curFile=it.next();
|
samer@1
|
85 if (wasOpen) openCurrent();
|
samer@1
|
86 }
|
samer@1
|
87
|
samer@1
|
88 /** Move to head of previous file in playlist */
|
samer@1
|
89 public synchronized void prev() throws Exception
|
samer@1
|
90 {
|
samer@1
|
91 boolean wasOpen=isOpen();
|
samer@1
|
92 if (wasOpen) close();
|
samer@1
|
93 if (!it.hasPrevious()) {
|
samer@1
|
94 if (!loop) throw new EOFException();
|
samer@1
|
95 it=list.listIterator(list.size());
|
samer@1
|
96 if (!it.hasPrevious()) throw new Exception("no files in playlist");
|
samer@1
|
97 }
|
samer@1
|
98 curFile=it.previous();
|
samer@1
|
99 if (wasOpen) openCurrent();
|
samer@1
|
100 }
|
samer@1
|
101
|
samer@1
|
102 public boolean isOpen() { return in!=null; }
|
samer@1
|
103
|
samer@1
|
104 /** Closes current input stream */
|
samer@1
|
105 public synchronized void close() {
|
samer@1
|
106 try {
|
samer@1
|
107 if (in!=null) {
|
samer@1
|
108 Shell.trace("Closing audio stream...");
|
samer@1
|
109 in.close(); in=null;
|
samer@1
|
110 }
|
samer@1
|
111 }
|
samer@1
|
112 catch (IOException ex) {}
|
samer@1
|
113 }
|
samer@1
|
114
|
samer@1
|
115 /** Opens the playlist starting with the first file. */
|
samer@1
|
116 public synchronized void open() throws Exception { rewind(); if (!isOpen()) openCurrent(); }
|
samer@1
|
117
|
samer@1
|
118 /** Opens the current file. Next read will returns samples
|
samer@1
|
119 * from head of given file. If setFormat() was called previously, then
|
samer@1
|
120 * we will attempt to do format conversion. */
|
samer@1
|
121 private synchronized void openCurrent() throws IOException
|
samer@1
|
122 {
|
samer@1
|
123 try {
|
samer@1
|
124 File file=curFile;
|
samer@1
|
125 Shell.trace("\nFileSource:Opening audio file "+file);
|
samer@1
|
126
|
samer@1
|
127 AudioInputStream s=AudioSystem.getAudioInputStream(file);
|
samer@1
|
128 AudioFormat fmt1, af=s.getFormat();
|
samer@1
|
129
|
samer@1
|
130 Shell.trace(" format: "+af);
|
samer@1
|
131
|
samer@1
|
132 // convert to target format if required
|
samer@1
|
133 if (format!=null) {
|
samer@1
|
134 if (!format.equals(af)) {
|
samer@1
|
135 Shell.trace(" converting to "+format);
|
samer@1
|
136 if (af.getChannels()>format.getChannels()) {
|
samer@1
|
137 int frameSize = af.getChannels()*format.getSampleSizeInBits()/8;
|
samer@1
|
138 Shell.trace(" channels mix down required.");
|
samer@1
|
139 fmt1 = new AudioFormat( format.getEncoding(), format.getSampleRate(),
|
samer@1
|
140 format.getSampleSizeInBits(),
|
samer@1
|
141 af.getChannels(), frameSize, format.getFrameRate(), format.isBigEndian());
|
samer@1
|
142
|
samer@1
|
143 channelsToMix = af.getChannels();
|
samer@1
|
144 } else {
|
samer@1
|
145 channelsToMix = 0;
|
samer@1
|
146 fmt1=format;
|
samer@1
|
147 }
|
samer@1
|
148 Shell.trace(" converting via "+fmt1);
|
samer@1
|
149 s=convertFormat(s,af,fmt1);
|
samer@1
|
150 inFormat = fmt1;
|
samer@1
|
151 } else {
|
samer@1
|
152 Shell.trace(" no formation conversion required");
|
samer@1
|
153 channelsToMix = 0;
|
samer@1
|
154 inFormat = af;
|
samer@1
|
155 }
|
samer@1
|
156 } else {
|
samer@1
|
157 Shell.trace(" using stream native format");
|
samer@1
|
158 channelsToMix = 0;
|
samer@1
|
159 inFormat = af;
|
samer@1
|
160 }
|
samer@1
|
161 in=s;
|
samer@1
|
162 }
|
samer@1
|
163 catch (Exception ex) { throw new IOException("Failed to open audio file"); }
|
samer@1
|
164 }
|
samer@1
|
165
|
samer@1
|
166 /** Returns number of bytes available in current file */
|
samer@1
|
167 public int available() {
|
samer@1
|
168 try {
|
samer@1
|
169 return in.available();
|
samer@1
|
170 }
|
samer@1
|
171 catch (Exception ex) { return 0; }
|
samer@1
|
172 }
|
samer@1
|
173
|
samer@1
|
174 /** Returns a Task which copies samples as doubles into the given
|
samer@1
|
175 * buffer between the given positions. */
|
samer@1
|
176 public int read(byte [] buf, int off, int len) throws IOException {
|
samer@1
|
177
|
samer@1
|
178 // loop until len samples copied into dbuf
|
samer@1
|
179 int rem=len, pos=off;
|
samer@1
|
180 while (rem>0) {
|
samer@1
|
181 int chunk = in.read(buf, pos, rem);
|
samer@1
|
182 if (chunk > 0) { // append this chunk to output
|
samer@1
|
183 pos+=chunk; rem-=chunk;
|
samer@1
|
184 } else if (it!=null) next(); // next file if there is one
|
samer@1
|
185 else if (!loop) throw new EOFException(); // not looping and no more files
|
samer@1
|
186 else rewind(); // back to first file
|
samer@1
|
187 }
|
samer@1
|
188 return len;
|
samer@1
|
189 }
|
samer@1
|
190
|
samer@1
|
191
|
samer@1
|
192 private static AudioInputStream convertFormat(AudioInputStream sin, AudioFormat fin, AudioFormat fout) throws Exception
|
samer@1
|
193 {
|
samer@1
|
194 Shell.trace("\nconvertFormat:");
|
samer@1
|
195 Shell.trace(" | source: "+fin.toString());
|
samer@1
|
196 Shell.trace(" | target: "+fout.toString());
|
samer@1
|
197
|
samer@1
|
198 if (fin.equals(fout)) return sin;
|
samer@1
|
199 else if (fin.getEncoding()==AudioFormat.Encoding.PCM_SIGNED) {
|
samer@1
|
200 try {
|
samer@1
|
201 Shell.trace(" | Trying direct (PCM) from "+fin.getEncoding().toString());
|
samer@1
|
202 return AudioSystem.getAudioInputStream(fout,sin);
|
samer@1
|
203 }
|
samer@1
|
204 catch (IllegalArgumentException ex) { Shell.trace("Direct conversion failed"); }
|
samer@1
|
205
|
samer@1
|
206 AudioFormat fint = new AudioFormat( // PCM
|
samer@1
|
207 fout.getSampleRate(), fout.getSampleSizeInBits(),
|
samer@1
|
208 fin.getChannels(), true, fout.isBigEndian());
|
samer@1
|
209 Shell.trace(" | Trying PCM conversion via "+fint.toString());
|
samer@1
|
210 return AudioSystem.getAudioInputStream(fout,AudioSystem.getAudioInputStream(fint,sin));
|
samer@1
|
211 } else {
|
samer@1
|
212 // First, check for MP3 - if so, cannot convert number of channels
|
samer@1
|
213 if (fin.getChannels()==fout.getChannels() && fin.getSampleRate()==fout.getSampleRate()) {
|
samer@1
|
214 Shell.trace(" | Trying decoding from "+fin.getEncoding().toString());
|
samer@1
|
215 return AudioSystem.getAudioInputStream(fout,sin);
|
samer@1
|
216 } else {
|
samer@1
|
217 AudioFormat fint = new AudioFormat(
|
samer@1
|
218 fin.getSampleRate(), fout.getSampleSizeInBits(),
|
samer@1
|
219 fin.getChannels(), true, fout.isBigEndian());
|
samer@1
|
220 Shell.trace(" | Trying recursive via "+fint.toString());
|
samer@1
|
221 return convertFormat(AudioSystem.getAudioInputStream(fint,sin),fint,fout);
|
samer@1
|
222 }
|
samer@1
|
223 }
|
samer@1
|
224 }
|
samer@1
|
225 }
|
samer@1
|
226
|