rlm@3
|
1 package com.aurellem.capture;
|
rlm@3
|
2
|
rlm@3
|
3
|
rlm@3
|
4 /*************************************************************************
|
rlm@3
|
5 * Compilation: javac StdAudio.java
|
rlm@3
|
6 * Execution: java StdAudio
|
rlm@3
|
7 *
|
rlm@3
|
8 * Simple library for reading, writing, and manipulating .wav files.
|
rlm@3
|
9
|
rlm@3
|
10 *
|
rlm@3
|
11 * Limitations
|
rlm@3
|
12 * -----------
|
rlm@3
|
13 * - Does not seem to work properly when reading .wav files from a .jar file.
|
rlm@3
|
14 * - Assumes the audio is monaural, with sampling rate of 44,100.
|
rlm@3
|
15 *
|
rlm@3
|
16 *************************************************************************/
|
rlm@3
|
17
|
rlm@3
|
18 import java.applet.Applet;
|
rlm@3
|
19 import java.applet.AudioClip;
|
rlm@3
|
20 import java.io.ByteArrayInputStream;
|
rlm@3
|
21 import java.io.File;
|
rlm@3
|
22 import java.net.MalformedURLException;
|
rlm@3
|
23 import java.net.URL;
|
rlm@3
|
24
|
rlm@3
|
25 import javax.sound.sampled.AudioFileFormat;
|
rlm@3
|
26 import javax.sound.sampled.AudioFormat;
|
rlm@3
|
27 import javax.sound.sampled.AudioInputStream;
|
rlm@3
|
28 import javax.sound.sampled.AudioSystem;
|
rlm@3
|
29 import javax.sound.sampled.SourceDataLine;
|
rlm@3
|
30
|
rlm@3
|
31 /**
|
rlm@3
|
32 * <i>Standard audio</i>. This class provides a basic capability for
|
rlm@3
|
33 * creating, reading, and saving audio.
|
rlm@3
|
34 * <p>
|
rlm@3
|
35 * The audio format uses a sampling rate of 44,100 (CD quality audio), 16-bit, monaural.
|
rlm@3
|
36 *
|
rlm@3
|
37 * <p>
|
rlm@3
|
38 * For additional documentation, see <a href="http://introcs.cs.princeton.edu/15inout">Section 1.5</a> of
|
rlm@3
|
39 * <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by Robert Sedgewick and Kevin Wayne.
|
rlm@3
|
40 */
|
rlm@3
|
41 public final class StdAudio {
|
rlm@3
|
42
|
rlm@3
|
43 /**
|
rlm@3
|
44 * The sample rate - 44,100 Hz for CD quality audio.
|
rlm@3
|
45 */
|
rlm@3
|
46 public static final int SAMPLE_RATE = 44100;
|
rlm@3
|
47
|
rlm@3
|
48 //private static final int BYTES_PER_SAMPLE = 2; // 16-bit audio
|
rlm@3
|
49 //private static final int BITS_PER_SAMPLE = 16; // 16-bit audio
|
rlm@3
|
50 private static final double MAX_16_BIT = Short.MAX_VALUE; // 32,767
|
rlm@3
|
51 //private static final int SAMPLE_BUFFER_SIZE = 4096;
|
rlm@3
|
52
|
rlm@3
|
53
|
rlm@3
|
54 private static SourceDataLine line; // to play the sound
|
rlm@3
|
55 private static byte[] buffer; // our internal buffer
|
rlm@3
|
56 private static int bufferSize = 0; // number of samples currently in internal buffer
|
rlm@3
|
57
|
rlm@3
|
58 // not-instantiable
|
rlm@3
|
59 private StdAudio() { }
|
rlm@3
|
60
|
rlm@3
|
61
|
rlm@3
|
62 // static initializer
|
rlm@3
|
63 //static { init(); }
|
rlm@3
|
64
|
rlm@3
|
65 // open up an audio stream
|
rlm@3
|
66
|
rlm@3
|
67 /*
|
rlm@3
|
68 private static void init() {
|
rlm@3
|
69 try {
|
rlm@3
|
70 // 44,100 samples per second, 16-bit audio, mono, signed PCM, little Endian
|
rlm@3
|
71 AudioFormat format = new AudioFormat((float) SAMPLE_RATE, BITS_PER_SAMPLE, 1, true, false);
|
rlm@3
|
72 DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
|
rlm@3
|
73
|
rlm@3
|
74 line = (SourceDataLine) AudioSystem.getLine(info);
|
rlm@3
|
75
|
rlm@3
|
76 // RLM: set to 1 and see what happens!
|
rlm@3
|
77 line.open(format, SAMPLE_BUFFER_SIZE);
|
rlm@3
|
78 //line.open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE);
|
rlm@3
|
79
|
rlm@3
|
80 // the internal buffer is a fraction of the actual buffer size, this choice is arbitrary
|
rlm@3
|
81 // it gets divided because we can't expect the buffered data to line up exactly with when
|
rlm@3
|
82 // the sound card decides to push out its samples.
|
rlm@3
|
83 buffer = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE/3];
|
rlm@3
|
84 } catch (Exception e) {
|
rlm@3
|
85 System.out.println(e.getMessage());
|
rlm@3
|
86 System.exit(1);
|
rlm@3
|
87 }
|
rlm@3
|
88
|
rlm@3
|
89 // no sound gets made before this call
|
rlm@3
|
90 line.start();
|
rlm@3
|
91 }
|
rlm@3
|
92 */
|
rlm@3
|
93
|
rlm@3
|
94
|
rlm@3
|
95 /**
|
rlm@3
|
96 * Close standard audio.
|
rlm@3
|
97 */
|
rlm@3
|
98 public static void close() {
|
rlm@3
|
99 line.drain();
|
rlm@3
|
100 line.stop();
|
rlm@3
|
101 }
|
rlm@3
|
102
|
rlm@3
|
103 /**
|
rlm@3
|
104 * Write one sample (between -1.0 and +1.0) to standard audio. If the sample
|
rlm@3
|
105 * is outside the range, it will be clipped.
|
rlm@3
|
106 */
|
rlm@3
|
107 public static void play(double in) {
|
rlm@3
|
108
|
rlm@3
|
109 // clip if outside [-1, +1]
|
rlm@3
|
110 if (in < -1.0) in = -1.0;
|
rlm@3
|
111 if (in > +1.0) in = +1.0;
|
rlm@3
|
112
|
rlm@3
|
113 // convert to bytes
|
rlm@3
|
114 short s = (short) (MAX_16_BIT * in);
|
rlm@3
|
115 buffer[bufferSize++] = (byte) s;
|
rlm@3
|
116 buffer[bufferSize++] = (byte) (s >> 8); // little Endian
|
rlm@3
|
117
|
rlm@3
|
118 // send to sound card if buffer is full
|
rlm@3
|
119 if (bufferSize >= buffer.length) {
|
rlm@3
|
120 line.write(buffer, 0, buffer.length);
|
rlm@3
|
121 bufferSize = 0;
|
rlm@3
|
122 }
|
rlm@3
|
123 }
|
rlm@3
|
124
|
rlm@3
|
125 /**
|
rlm@3
|
126 * Write an array of samples (between -1.0 and +1.0) to standard audio. If a sample
|
rlm@3
|
127 * is outside the range, it will be clipped.
|
rlm@3
|
128 */
|
rlm@3
|
129 public static void play(double[] input) {
|
rlm@3
|
130 for (int i = 0; i < input.length; i++) {
|
rlm@3
|
131 play(input[i]);
|
rlm@3
|
132 }
|
rlm@3
|
133 }
|
rlm@3
|
134
|
rlm@3
|
135 /**
|
rlm@3
|
136 * Read audio samples from a file (in .wav or .au format) and return them as a double array
|
rlm@3
|
137 * with values between -1.0 and +1.0.
|
rlm@3
|
138 */
|
rlm@3
|
139 public static double[] read(String filename) {
|
rlm@3
|
140 byte[] data = readByte(filename);
|
rlm@3
|
141 int N = data.length;
|
rlm@3
|
142 double[] d = new double[N/2];
|
rlm@3
|
143 for (int i = 0; i < N/2; i++) {
|
rlm@3
|
144 d[i] = ((short) (((data[2*i+1] & 0xFF) << 8) + (data[2*i] & 0xFF))) / ((double) MAX_16_BIT);
|
rlm@3
|
145 }
|
rlm@3
|
146 return d;
|
rlm@3
|
147 }
|
rlm@3
|
148
|
rlm@3
|
149
|
rlm@3
|
150
|
rlm@3
|
151
|
rlm@3
|
152 /**
|
rlm@3
|
153 * Play a sound file (in .wav or .au format) in a background thread.
|
rlm@3
|
154 */
|
rlm@3
|
155 public static void play(String filename) {
|
rlm@3
|
156 URL url = null;
|
rlm@3
|
157 try {
|
rlm@3
|
158 File file = new File(filename);
|
rlm@3
|
159 if (file.canRead()) url = file.toURI().toURL();
|
rlm@3
|
160 }
|
rlm@3
|
161 catch (MalformedURLException e) { e.printStackTrace(); }
|
rlm@3
|
162 // URL url = StdAudio.class.getResource(filename);
|
rlm@3
|
163 if (url == null) throw new RuntimeException("audio " + filename + " not found");
|
rlm@3
|
164 AudioClip clip = Applet.newAudioClip(url);
|
rlm@3
|
165 clip.play();
|
rlm@3
|
166 }
|
rlm@3
|
167
|
rlm@3
|
168 /**
|
rlm@3
|
169 * Loop a sound file (in .wav or .au format) in a background thread.
|
rlm@3
|
170 */
|
rlm@3
|
171 public static void loop(String filename) {
|
rlm@3
|
172 URL url = null;
|
rlm@3
|
173 try {
|
rlm@3
|
174 File file = new File(filename);
|
rlm@3
|
175 if (file.canRead()) url = file.toURI().toURL();
|
rlm@3
|
176 }
|
rlm@3
|
177 catch (MalformedURLException e) { e.printStackTrace(); }
|
rlm@3
|
178 // URL url = StdAudio.class.getResource(filename);
|
rlm@3
|
179 if (url == null) throw new RuntimeException("audio " + filename + " not found");
|
rlm@3
|
180 AudioClip clip = Applet.newAudioClip(url);
|
rlm@3
|
181 clip.loop();
|
rlm@3
|
182 }
|
rlm@3
|
183
|
rlm@3
|
184
|
rlm@3
|
185 // return data as a byte array
|
rlm@3
|
186 private static byte[] readByte(String filename) {
|
rlm@3
|
187 byte[] data = null;
|
rlm@3
|
188 AudioInputStream ais = null;
|
rlm@3
|
189 try {
|
rlm@3
|
190 URL url = StdAudio.class.getResource(filename);
|
rlm@3
|
191 ais = AudioSystem.getAudioInputStream(url);
|
rlm@3
|
192 data = new byte[ais.available()];
|
rlm@3
|
193 ais.read(data);
|
rlm@3
|
194 }
|
rlm@3
|
195 catch (Exception e) {
|
rlm@3
|
196 System.out.println(e.getMessage());
|
rlm@3
|
197 throw new RuntimeException("Could not read " + filename);
|
rlm@3
|
198 }
|
rlm@3
|
199
|
rlm@3
|
200 return data;
|
rlm@3
|
201 }
|
rlm@3
|
202
|
rlm@3
|
203
|
rlm@3
|
204
|
rlm@3
|
205 /**
|
rlm@3
|
206 * Save the double array as a sound file (using .wav or .au format).
|
rlm@3
|
207 */
|
rlm@3
|
208 public static void save(String filename, double[] input) {
|
rlm@3
|
209
|
rlm@3
|
210 // assumes 44,100 samples per second
|
rlm@3
|
211 // use 16-bit audio, mono, signed PCM, little Endian
|
rlm@3
|
212 AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false);
|
rlm@3
|
213 byte[] data = new byte[2 * input.length];
|
rlm@3
|
214 for (int i = 0; i < input.length; i++) {
|
rlm@3
|
215 int temp = (short) (input[i] * MAX_16_BIT);
|
rlm@3
|
216 data[2*i + 0] = (byte) temp;
|
rlm@3
|
217 data[2*i + 1] = (byte) (temp >> 8);
|
rlm@3
|
218 }
|
rlm@3
|
219
|
rlm@3
|
220 // now save the file
|
rlm@3
|
221 try {
|
rlm@3
|
222 ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
rlm@3
|
223 AudioInputStream ais = new AudioInputStream(bais, format, input.length);
|
rlm@3
|
224 if (filename.endsWith(".wav") || filename.endsWith(".WAV")) {
|
rlm@3
|
225 AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename));
|
rlm@3
|
226 }
|
rlm@3
|
227 else if (filename.endsWith(".au") || filename.endsWith(".AU")) {
|
rlm@3
|
228 AudioSystem.write(ais, AudioFileFormat.Type.AU, new File(filename));
|
rlm@3
|
229 }
|
rlm@3
|
230 else {
|
rlm@3
|
231 throw new RuntimeException("File format not supported: " + filename);
|
rlm@3
|
232 }
|
rlm@3
|
233 }
|
rlm@3
|
234 catch (Exception e) {
|
rlm@3
|
235 System.out.println(e);
|
rlm@3
|
236 System.exit(1);
|
rlm@3
|
237 }
|
rlm@3
|
238 }
|
rlm@3
|
239
|
rlm@3
|
240 public static void save(String filename, byte[] data){
|
rlm@3
|
241 // now save the file
|
rlm@3
|
242 AudioFormat format = new AudioFormat(SAMPLE_RATE, 32, 1, true, false);
|
rlm@3
|
243
|
rlm@3
|
244 try {
|
rlm@3
|
245 ByteArrayInputStream bais = new ByteArrayInputStream(data);
|
rlm@3
|
246 AudioInputStream ais = new AudioInputStream(bais, format, data.length/2);
|
rlm@3
|
247 if (filename.endsWith(".wav") || filename.endsWith(".WAV")) {
|
rlm@3
|
248 AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename));
|
rlm@3
|
249 }
|
rlm@3
|
250 else if (filename.endsWith(".au") || filename.endsWith(".AU")) {
|
rlm@3
|
251 AudioSystem.write(ais, AudioFileFormat.Type.AU, new File(filename));
|
rlm@3
|
252 }
|
rlm@3
|
253 else {
|
rlm@3
|
254 throw new RuntimeException("File format not supported: " + filename);
|
rlm@3
|
255 }
|
rlm@3
|
256 }
|
rlm@3
|
257 catch (Exception e) {
|
rlm@3
|
258 System.out.println(e);
|
rlm@3
|
259 System.exit(1);
|
rlm@3
|
260 }
|
rlm@3
|
261 }
|
rlm@3
|
262
|
rlm@3
|
263 /*
|
rlm@3
|
264 public static void save(String filename, Byte[] data){
|
rlm@3
|
265 // now save the file
|
rlm@3
|
266 save(filename, ArrayUtils.toPrimitive(data));
|
rlm@3
|
267
|
rlm@3
|
268 }
|
rlm@3
|
269 */
|
rlm@3
|
270
|
rlm@3
|
271 /***********************************************************************
|
rlm@3
|
272 * sample test client
|
rlm@3
|
273 ***********************************************************************/
|
rlm@3
|
274
|
rlm@3
|
275 // create a note (sine wave) of the given frequency (Hz), for the given
|
rlm@3
|
276 // duration (seconds) scaled to the given volume (amplitude)
|
rlm@3
|
277 private static double[] note(double hz, double duration, double amplitude) {
|
rlm@3
|
278 int N = (int) (StdAudio.SAMPLE_RATE * duration);
|
rlm@3
|
279 double[] a = new double[N+1];
|
rlm@3
|
280 for (int i = 0; i <= N; i++)
|
rlm@3
|
281 a[i] = amplitude * Math.sin(2 * Math.PI * i * hz / StdAudio.SAMPLE_RATE);
|
rlm@3
|
282 return a;
|
rlm@3
|
283 }
|
rlm@3
|
284
|
rlm@3
|
285 /**
|
rlm@3
|
286 * Test client - play an A major scale to standard audio.
|
rlm@3
|
287 */
|
rlm@3
|
288 public static void main(String[] args) {
|
rlm@3
|
289
|
rlm@3
|
290 // 440 Hz for 1 sec
|
rlm@3
|
291 double freq = 440.0;
|
rlm@3
|
292 for (int i = 0; i <= StdAudio.SAMPLE_RATE; i++) {
|
rlm@3
|
293 StdAudio.play(0.5 * Math.sin(2*Math.PI * freq * i / StdAudio.SAMPLE_RATE));
|
rlm@3
|
294 }
|
rlm@3
|
295
|
rlm@3
|
296 // scale increments
|
rlm@3
|
297 int[] steps = { 0, 2, 4, 5, 7, 9, 11, 12 };
|
rlm@3
|
298 for (int i = 0; i < steps.length; i++) {
|
rlm@3
|
299 double hz = 440.0 * Math.pow(2, steps[i] / 12.0);
|
rlm@3
|
300 StdAudio.play(note(hz, 1.0, 0.5));
|
rlm@3
|
301 }
|
rlm@3
|
302
|
rlm@3
|
303
|
rlm@3
|
304 // need to call this in non-interactive stuff so the program doesn't terminate
|
rlm@3
|
305 // until all the sound leaves the speaker.
|
rlm@3
|
306 StdAudio.close();
|
rlm@3
|
307
|
rlm@3
|
308 // need to terminate a Java program with sound
|
rlm@3
|
309 System.exit(0);
|
rlm@3
|
310 }
|
rlm@3
|
311 }
|
rlm@3
|
312
|