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