rlm@3: package com.aurellem.capture; rlm@3: rlm@3: rlm@3: /************************************************************************* rlm@3: * Compilation: javac StdAudio.java rlm@3: * Execution: java StdAudio rlm@3: * rlm@3: * Simple library for reading, writing, and manipulating .wav files. rlm@3: rlm@3: * rlm@3: * Limitations rlm@3: * ----------- rlm@3: * - Does not seem to work properly when reading .wav files from a .jar file. rlm@3: * - Assumes the audio is monaural, with sampling rate of 44,100. rlm@3: * rlm@3: *************************************************************************/ rlm@3: rlm@3: import java.applet.Applet; rlm@3: import java.applet.AudioClip; rlm@3: import java.io.ByteArrayInputStream; rlm@3: import java.io.File; rlm@3: import java.net.MalformedURLException; rlm@3: import java.net.URL; rlm@3: rlm@3: import javax.sound.sampled.AudioFileFormat; rlm@3: import javax.sound.sampled.AudioFormat; rlm@3: import javax.sound.sampled.AudioInputStream; rlm@3: import javax.sound.sampled.AudioSystem; rlm@3: import javax.sound.sampled.SourceDataLine; rlm@3: rlm@3: /** rlm@3: * Standard audio. This class provides a basic capability for rlm@3: * creating, reading, and saving audio. rlm@3: *

rlm@3: * The audio format uses a sampling rate of 44,100 (CD quality audio), 16-bit, monaural. rlm@3: * rlm@3: *

rlm@3: * For additional documentation, see Section 1.5 of rlm@3: * Introduction to Programming in Java: An Interdisciplinary Approach by Robert Sedgewick and Kevin Wayne. rlm@3: */ rlm@3: public final class StdAudio { rlm@3: rlm@3: /** rlm@3: * The sample rate - 44,100 Hz for CD quality audio. rlm@3: */ rlm@3: public static final int SAMPLE_RATE = 44100; rlm@3: rlm@3: //private static final int BYTES_PER_SAMPLE = 2; // 16-bit audio rlm@3: //private static final int BITS_PER_SAMPLE = 16; // 16-bit audio rlm@3: private static final double MAX_16_BIT = Short.MAX_VALUE; // 32,767 rlm@3: //private static final int SAMPLE_BUFFER_SIZE = 4096; rlm@3: rlm@3: rlm@3: private static SourceDataLine line; // to play the sound rlm@3: private static byte[] buffer; // our internal buffer rlm@3: private static int bufferSize = 0; // number of samples currently in internal buffer rlm@3: rlm@3: // not-instantiable rlm@3: private StdAudio() { } rlm@3: rlm@3: rlm@3: // static initializer rlm@3: //static { init(); } rlm@3: rlm@3: // open up an audio stream rlm@3: rlm@3: /* rlm@3: private static void init() { rlm@3: try { rlm@3: // 44,100 samples per second, 16-bit audio, mono, signed PCM, little Endian rlm@3: AudioFormat format = new AudioFormat((float) SAMPLE_RATE, BITS_PER_SAMPLE, 1, true, false); rlm@3: DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); rlm@3: rlm@3: line = (SourceDataLine) AudioSystem.getLine(info); rlm@3: rlm@3: // RLM: set to 1 and see what happens! rlm@3: line.open(format, SAMPLE_BUFFER_SIZE); rlm@3: //line.open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE); rlm@3: rlm@3: // the internal buffer is a fraction of the actual buffer size, this choice is arbitrary rlm@3: // it gets divided because we can't expect the buffered data to line up exactly with when rlm@3: // the sound card decides to push out its samples. rlm@3: buffer = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE/3]; rlm@3: } catch (Exception e) { rlm@3: System.out.println(e.getMessage()); rlm@3: System.exit(1); rlm@3: } rlm@3: rlm@3: // no sound gets made before this call rlm@3: line.start(); rlm@3: } rlm@3: */ rlm@3: rlm@3: rlm@3: /** rlm@3: * Close standard audio. rlm@3: */ rlm@3: public static void close() { rlm@3: line.drain(); rlm@3: line.stop(); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Write one sample (between -1.0 and +1.0) to standard audio. If the sample rlm@3: * is outside the range, it will be clipped. rlm@3: */ rlm@3: public static void play(double in) { rlm@3: rlm@3: // clip if outside [-1, +1] rlm@3: if (in < -1.0) in = -1.0; rlm@3: if (in > +1.0) in = +1.0; rlm@3: rlm@3: // convert to bytes rlm@3: short s = (short) (MAX_16_BIT * in); rlm@3: buffer[bufferSize++] = (byte) s; rlm@3: buffer[bufferSize++] = (byte) (s >> 8); // little Endian rlm@3: rlm@3: // send to sound card if buffer is full rlm@3: if (bufferSize >= buffer.length) { rlm@3: line.write(buffer, 0, buffer.length); rlm@3: bufferSize = 0; rlm@3: } rlm@3: } rlm@3: rlm@3: /** rlm@3: * Write an array of samples (between -1.0 and +1.0) to standard audio. If a sample rlm@3: * is outside the range, it will be clipped. rlm@3: */ rlm@3: public static void play(double[] input) { rlm@3: for (int i = 0; i < input.length; i++) { rlm@3: play(input[i]); rlm@3: } rlm@3: } rlm@3: rlm@3: /** rlm@3: * Read audio samples from a file (in .wav or .au format) and return them as a double array rlm@3: * with values between -1.0 and +1.0. rlm@3: */ rlm@3: public static double[] read(String filename) { rlm@3: byte[] data = readByte(filename); rlm@3: int N = data.length; rlm@3: double[] d = new double[N/2]; rlm@3: for (int i = 0; i < N/2; i++) { rlm@3: d[i] = ((short) (((data[2*i+1] & 0xFF) << 8) + (data[2*i] & 0xFF))) / ((double) MAX_16_BIT); rlm@3: } rlm@3: return d; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: rlm@3: /** rlm@3: * Play a sound file (in .wav or .au format) in a background thread. rlm@3: */ rlm@3: public static void play(String filename) { rlm@3: URL url = null; rlm@3: try { rlm@3: File file = new File(filename); rlm@3: if (file.canRead()) url = file.toURI().toURL(); rlm@3: } rlm@3: catch (MalformedURLException e) { e.printStackTrace(); } rlm@3: // URL url = StdAudio.class.getResource(filename); rlm@3: if (url == null) throw new RuntimeException("audio " + filename + " not found"); rlm@3: AudioClip clip = Applet.newAudioClip(url); rlm@3: clip.play(); rlm@3: } rlm@3: rlm@3: /** rlm@3: * Loop a sound file (in .wav or .au format) in a background thread. rlm@3: */ rlm@3: public static void loop(String filename) { rlm@3: URL url = null; rlm@3: try { rlm@3: File file = new File(filename); rlm@3: if (file.canRead()) url = file.toURI().toURL(); rlm@3: } rlm@3: catch (MalformedURLException e) { e.printStackTrace(); } rlm@3: // URL url = StdAudio.class.getResource(filename); rlm@3: if (url == null) throw new RuntimeException("audio " + filename + " not found"); rlm@3: AudioClip clip = Applet.newAudioClip(url); rlm@3: clip.loop(); rlm@3: } rlm@3: rlm@3: rlm@3: // return data as a byte array rlm@3: private static byte[] readByte(String filename) { rlm@3: byte[] data = null; rlm@3: AudioInputStream ais = null; rlm@3: try { rlm@3: URL url = StdAudio.class.getResource(filename); rlm@3: ais = AudioSystem.getAudioInputStream(url); rlm@3: data = new byte[ais.available()]; rlm@3: ais.read(data); rlm@3: } rlm@3: catch (Exception e) { rlm@3: System.out.println(e.getMessage()); rlm@3: throw new RuntimeException("Could not read " + filename); rlm@3: } rlm@3: rlm@3: return data; rlm@3: } rlm@3: rlm@3: rlm@3: rlm@3: /** rlm@3: * Save the double array as a sound file (using .wav or .au format). rlm@3: */ rlm@3: public static void save(String filename, double[] input) { rlm@3: rlm@3: // assumes 44,100 samples per second rlm@3: // use 16-bit audio, mono, signed PCM, little Endian rlm@3: AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); rlm@3: byte[] data = new byte[2 * input.length]; rlm@3: for (int i = 0; i < input.length; i++) { rlm@3: int temp = (short) (input[i] * MAX_16_BIT); rlm@3: data[2*i + 0] = (byte) temp; rlm@3: data[2*i + 1] = (byte) (temp >> 8); rlm@3: } rlm@3: rlm@3: // now save the file rlm@3: try { rlm@3: ByteArrayInputStream bais = new ByteArrayInputStream(data); rlm@3: AudioInputStream ais = new AudioInputStream(bais, format, input.length); rlm@3: if (filename.endsWith(".wav") || filename.endsWith(".WAV")) { rlm@3: AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename)); rlm@3: } rlm@3: else if (filename.endsWith(".au") || filename.endsWith(".AU")) { rlm@3: AudioSystem.write(ais, AudioFileFormat.Type.AU, new File(filename)); rlm@3: } rlm@3: else { rlm@3: throw new RuntimeException("File format not supported: " + filename); rlm@3: } rlm@3: } rlm@3: catch (Exception e) { rlm@3: System.out.println(e); rlm@3: System.exit(1); rlm@3: } rlm@3: } rlm@3: rlm@3: public static void save(String filename, byte[] data){ rlm@3: // now save the file rlm@3: AudioFormat format = new AudioFormat(SAMPLE_RATE, 32, 1, true, false); rlm@3: rlm@3: try { rlm@3: ByteArrayInputStream bais = new ByteArrayInputStream(data); rlm@3: AudioInputStream ais = new AudioInputStream(bais, format, data.length/2); rlm@3: if (filename.endsWith(".wav") || filename.endsWith(".WAV")) { rlm@3: AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename)); rlm@3: } rlm@3: else if (filename.endsWith(".au") || filename.endsWith(".AU")) { rlm@3: AudioSystem.write(ais, AudioFileFormat.Type.AU, new File(filename)); rlm@3: } rlm@3: else { rlm@3: throw new RuntimeException("File format not supported: " + filename); rlm@3: } rlm@3: } rlm@3: catch (Exception e) { rlm@3: System.out.println(e); rlm@3: System.exit(1); rlm@3: } rlm@3: } rlm@3: rlm@3: /* rlm@3: public static void save(String filename, Byte[] data){ rlm@3: // now save the file rlm@3: save(filename, ArrayUtils.toPrimitive(data)); rlm@3: rlm@3: } rlm@3: */ rlm@3: rlm@3: /*********************************************************************** rlm@3: * sample test client rlm@3: ***********************************************************************/ rlm@3: rlm@3: // create a note (sine wave) of the given frequency (Hz), for the given rlm@3: // duration (seconds) scaled to the given volume (amplitude) rlm@3: private static double[] note(double hz, double duration, double amplitude) { rlm@3: int N = (int) (StdAudio.SAMPLE_RATE * duration); rlm@3: double[] a = new double[N+1]; rlm@3: for (int i = 0; i <= N; i++) rlm@3: a[i] = amplitude * Math.sin(2 * Math.PI * i * hz / StdAudio.SAMPLE_RATE); rlm@3: return a; rlm@3: } rlm@3: rlm@3: /** rlm@3: * Test client - play an A major scale to standard audio. rlm@3: */ rlm@3: public static void main(String[] args) { rlm@3: rlm@3: // 440 Hz for 1 sec rlm@3: double freq = 440.0; rlm@3: for (int i = 0; i <= StdAudio.SAMPLE_RATE; i++) { rlm@3: StdAudio.play(0.5 * Math.sin(2*Math.PI * freq * i / StdAudio.SAMPLE_RATE)); rlm@3: } rlm@3: rlm@3: // scale increments rlm@3: int[] steps = { 0, 2, 4, 5, 7, 9, 11, 12 }; rlm@3: for (int i = 0; i < steps.length; i++) { rlm@3: double hz = 440.0 * Math.pow(2, steps[i] / 12.0); rlm@3: StdAudio.play(note(hz, 1.0, 0.5)); rlm@3: } rlm@3: rlm@3: rlm@3: // need to call this in non-interactive stuff so the program doesn't terminate rlm@3: // until all the sound leaves the speaker. rlm@3: StdAudio.close(); rlm@3: rlm@3: // need to terminate a Java program with sound rlm@3: System.exit(0); rlm@3: } rlm@3: } rlm@3: