view src/com/aurellem/capture/StdAudio.java @ 4:edaa7e7806e4

migrated IsoTimer
author Robert McIntyre <rlm@mit.edu>
date Tue, 25 Oct 2011 12:03:01 -0700
parents a92de00f0414
children
line wrap: on
line source
1 package com.aurellem.capture;
4 /*************************************************************************
5 * Compilation: javac StdAudio.java
6 * Execution: java StdAudio
7 *
8 * Simple library for reading, writing, and manipulating .wav files.
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 *************************************************************************/
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;
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;
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 {
43 /**
44 * The sample rate - 44,100 Hz for CD quality audio.
45 */
46 public static final int SAMPLE_RATE = 44100;
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;
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
58 // not-instantiable
59 private StdAudio() { }
62 // static initializer
63 //static { init(); }
65 // open up an audio stream
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);
74 line = (SourceDataLine) AudioSystem.getLine(info);
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);
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 }
89 // no sound gets made before this call
90 line.start();
91 }
92 */
95 /**
96 * Close standard audio.
97 */
98 public static void close() {
99 line.drain();
100 line.stop();
101 }
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) {
109 // clip if outside [-1, +1]
110 if (in < -1.0) in = -1.0;
111 if (in > +1.0) in = +1.0;
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
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 }
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 }
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 }
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 }
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 }
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 }
200 return data;
201 }
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) {
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 }
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 }
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);
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 }
263 /*
264 public static void save(String filename, Byte[] data){
265 // now save the file
266 save(filename, ArrayUtils.toPrimitive(data));
268 }
269 */
271 /***********************************************************************
272 * sample test client
273 ***********************************************************************/
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 }
285 /**
286 * Test client - play an A major scale to standard audio.
287 */
288 public static void main(String[] args) {
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 }
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 }
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();
308 // need to terminate a Java program with sound
309 System.exit(0);
310 }
311 }