annotate 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
rev   line source
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