Mercurial > jmeCapture
diff 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 |
line wrap: on
line diff
1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/src/com/aurellem/capture/StdAudio.java Tue Oct 25 11:55:55 2011 -0700 1.3 @@ -0,0 +1,312 @@ 1.4 +package com.aurellem.capture; 1.5 + 1.6 + 1.7 +/************************************************************************* 1.8 + * Compilation: javac StdAudio.java 1.9 + * Execution: java StdAudio 1.10 + * 1.11 + * Simple library for reading, writing, and manipulating .wav files. 1.12 + 1.13 + * 1.14 + * Limitations 1.15 + * ----------- 1.16 + * - Does not seem to work properly when reading .wav files from a .jar file. 1.17 + * - Assumes the audio is monaural, with sampling rate of 44,100. 1.18 + * 1.19 + *************************************************************************/ 1.20 + 1.21 +import java.applet.Applet; 1.22 +import java.applet.AudioClip; 1.23 +import java.io.ByteArrayInputStream; 1.24 +import java.io.File; 1.25 +import java.net.MalformedURLException; 1.26 +import java.net.URL; 1.27 + 1.28 +import javax.sound.sampled.AudioFileFormat; 1.29 +import javax.sound.sampled.AudioFormat; 1.30 +import javax.sound.sampled.AudioInputStream; 1.31 +import javax.sound.sampled.AudioSystem; 1.32 +import javax.sound.sampled.SourceDataLine; 1.33 + 1.34 +/** 1.35 + * <i>Standard audio</i>. This class provides a basic capability for 1.36 + * creating, reading, and saving audio. 1.37 + * <p> 1.38 + * The audio format uses a sampling rate of 44,100 (CD quality audio), 16-bit, monaural. 1.39 + * 1.40 + * <p> 1.41 + * For additional documentation, see <a href="http://introcs.cs.princeton.edu/15inout">Section 1.5</a> of 1.42 + * <i>Introduction to Programming in Java: An Interdisciplinary Approach</i> by Robert Sedgewick and Kevin Wayne. 1.43 + */ 1.44 +public final class StdAudio { 1.45 + 1.46 + /** 1.47 + * The sample rate - 44,100 Hz for CD quality audio. 1.48 + */ 1.49 + public static final int SAMPLE_RATE = 44100; 1.50 + 1.51 + //private static final int BYTES_PER_SAMPLE = 2; // 16-bit audio 1.52 + //private static final int BITS_PER_SAMPLE = 16; // 16-bit audio 1.53 + private static final double MAX_16_BIT = Short.MAX_VALUE; // 32,767 1.54 + //private static final int SAMPLE_BUFFER_SIZE = 4096; 1.55 + 1.56 + 1.57 + private static SourceDataLine line; // to play the sound 1.58 + private static byte[] buffer; // our internal buffer 1.59 + private static int bufferSize = 0; // number of samples currently in internal buffer 1.60 + 1.61 + // not-instantiable 1.62 + private StdAudio() { } 1.63 + 1.64 + 1.65 + // static initializer 1.66 + //static { init(); } 1.67 + 1.68 + // open up an audio stream 1.69 + 1.70 + /* 1.71 + private static void init() { 1.72 + try { 1.73 + // 44,100 samples per second, 16-bit audio, mono, signed PCM, little Endian 1.74 + AudioFormat format = new AudioFormat((float) SAMPLE_RATE, BITS_PER_SAMPLE, 1, true, false); 1.75 + DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); 1.76 + 1.77 + line = (SourceDataLine) AudioSystem.getLine(info); 1.78 + 1.79 + // RLM: set to 1 and see what happens! 1.80 + line.open(format, SAMPLE_BUFFER_SIZE); 1.81 + //line.open(format, SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE); 1.82 + 1.83 + // the internal buffer is a fraction of the actual buffer size, this choice is arbitrary 1.84 + // it gets divided because we can't expect the buffered data to line up exactly with when 1.85 + // the sound card decides to push out its samples. 1.86 + buffer = new byte[SAMPLE_BUFFER_SIZE * BYTES_PER_SAMPLE/3]; 1.87 + } catch (Exception e) { 1.88 + System.out.println(e.getMessage()); 1.89 + System.exit(1); 1.90 + } 1.91 + 1.92 + // no sound gets made before this call 1.93 + line.start(); 1.94 + } 1.95 + */ 1.96 + 1.97 + 1.98 + /** 1.99 + * Close standard audio. 1.100 + */ 1.101 + public static void close() { 1.102 + line.drain(); 1.103 + line.stop(); 1.104 + } 1.105 + 1.106 + /** 1.107 + * Write one sample (between -1.0 and +1.0) to standard audio. If the sample 1.108 + * is outside the range, it will be clipped. 1.109 + */ 1.110 + public static void play(double in) { 1.111 + 1.112 + // clip if outside [-1, +1] 1.113 + if (in < -1.0) in = -1.0; 1.114 + if (in > +1.0) in = +1.0; 1.115 + 1.116 + // convert to bytes 1.117 + short s = (short) (MAX_16_BIT * in); 1.118 + buffer[bufferSize++] = (byte) s; 1.119 + buffer[bufferSize++] = (byte) (s >> 8); // little Endian 1.120 + 1.121 + // send to sound card if buffer is full 1.122 + if (bufferSize >= buffer.length) { 1.123 + line.write(buffer, 0, buffer.length); 1.124 + bufferSize = 0; 1.125 + } 1.126 + } 1.127 + 1.128 + /** 1.129 + * Write an array of samples (between -1.0 and +1.0) to standard audio. If a sample 1.130 + * is outside the range, it will be clipped. 1.131 + */ 1.132 + public static void play(double[] input) { 1.133 + for (int i = 0; i < input.length; i++) { 1.134 + play(input[i]); 1.135 + } 1.136 + } 1.137 + 1.138 + /** 1.139 + * Read audio samples from a file (in .wav or .au format) and return them as a double array 1.140 + * with values between -1.0 and +1.0. 1.141 + */ 1.142 + public static double[] read(String filename) { 1.143 + byte[] data = readByte(filename); 1.144 + int N = data.length; 1.145 + double[] d = new double[N/2]; 1.146 + for (int i = 0; i < N/2; i++) { 1.147 + d[i] = ((short) (((data[2*i+1] & 0xFF) << 8) + (data[2*i] & 0xFF))) / ((double) MAX_16_BIT); 1.148 + } 1.149 + return d; 1.150 + } 1.151 + 1.152 + 1.153 + 1.154 + 1.155 + /** 1.156 + * Play a sound file (in .wav or .au format) in a background thread. 1.157 + */ 1.158 + public static void play(String filename) { 1.159 + URL url = null; 1.160 + try { 1.161 + File file = new File(filename); 1.162 + if (file.canRead()) url = file.toURI().toURL(); 1.163 + } 1.164 + catch (MalformedURLException e) { e.printStackTrace(); } 1.165 + // URL url = StdAudio.class.getResource(filename); 1.166 + if (url == null) throw new RuntimeException("audio " + filename + " not found"); 1.167 + AudioClip clip = Applet.newAudioClip(url); 1.168 + clip.play(); 1.169 + } 1.170 + 1.171 + /** 1.172 + * Loop a sound file (in .wav or .au format) in a background thread. 1.173 + */ 1.174 + public static void loop(String filename) { 1.175 + URL url = null; 1.176 + try { 1.177 + File file = new File(filename); 1.178 + if (file.canRead()) url = file.toURI().toURL(); 1.179 + } 1.180 + catch (MalformedURLException e) { e.printStackTrace(); } 1.181 + // URL url = StdAudio.class.getResource(filename); 1.182 + if (url == null) throw new RuntimeException("audio " + filename + " not found"); 1.183 + AudioClip clip = Applet.newAudioClip(url); 1.184 + clip.loop(); 1.185 + } 1.186 + 1.187 + 1.188 + // return data as a byte array 1.189 + private static byte[] readByte(String filename) { 1.190 + byte[] data = null; 1.191 + AudioInputStream ais = null; 1.192 + try { 1.193 + URL url = StdAudio.class.getResource(filename); 1.194 + ais = AudioSystem.getAudioInputStream(url); 1.195 + data = new byte[ais.available()]; 1.196 + ais.read(data); 1.197 + } 1.198 + catch (Exception e) { 1.199 + System.out.println(e.getMessage()); 1.200 + throw new RuntimeException("Could not read " + filename); 1.201 + } 1.202 + 1.203 + return data; 1.204 + } 1.205 + 1.206 + 1.207 + 1.208 + /** 1.209 + * Save the double array as a sound file (using .wav or .au format). 1.210 + */ 1.211 + public static void save(String filename, double[] input) { 1.212 + 1.213 + // assumes 44,100 samples per second 1.214 + // use 16-bit audio, mono, signed PCM, little Endian 1.215 + AudioFormat format = new AudioFormat(SAMPLE_RATE, 16, 1, true, false); 1.216 + byte[] data = new byte[2 * input.length]; 1.217 + for (int i = 0; i < input.length; i++) { 1.218 + int temp = (short) (input[i] * MAX_16_BIT); 1.219 + data[2*i + 0] = (byte) temp; 1.220 + data[2*i + 1] = (byte) (temp >> 8); 1.221 + } 1.222 + 1.223 + // now save the file 1.224 + try { 1.225 + ByteArrayInputStream bais = new ByteArrayInputStream(data); 1.226 + AudioInputStream ais = new AudioInputStream(bais, format, input.length); 1.227 + if (filename.endsWith(".wav") || filename.endsWith(".WAV")) { 1.228 + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename)); 1.229 + } 1.230 + else if (filename.endsWith(".au") || filename.endsWith(".AU")) { 1.231 + AudioSystem.write(ais, AudioFileFormat.Type.AU, new File(filename)); 1.232 + } 1.233 + else { 1.234 + throw new RuntimeException("File format not supported: " + filename); 1.235 + } 1.236 + } 1.237 + catch (Exception e) { 1.238 + System.out.println(e); 1.239 + System.exit(1); 1.240 + } 1.241 + } 1.242 + 1.243 + public static void save(String filename, byte[] data){ 1.244 + // now save the file 1.245 + AudioFormat format = new AudioFormat(SAMPLE_RATE, 32, 1, true, false); 1.246 + 1.247 + try { 1.248 + ByteArrayInputStream bais = new ByteArrayInputStream(data); 1.249 + AudioInputStream ais = new AudioInputStream(bais, format, data.length/2); 1.250 + if (filename.endsWith(".wav") || filename.endsWith(".WAV")) { 1.251 + AudioSystem.write(ais, AudioFileFormat.Type.WAVE, new File(filename)); 1.252 + } 1.253 + else if (filename.endsWith(".au") || filename.endsWith(".AU")) { 1.254 + AudioSystem.write(ais, AudioFileFormat.Type.AU, new File(filename)); 1.255 + } 1.256 + else { 1.257 + throw new RuntimeException("File format not supported: " + filename); 1.258 + } 1.259 + } 1.260 + catch (Exception e) { 1.261 + System.out.println(e); 1.262 + System.exit(1); 1.263 + } 1.264 + } 1.265 + 1.266 + /* 1.267 + public static void save(String filename, Byte[] data){ 1.268 + // now save the file 1.269 + save(filename, ArrayUtils.toPrimitive(data)); 1.270 + 1.271 + } 1.272 + */ 1.273 + 1.274 + /*********************************************************************** 1.275 + * sample test client 1.276 + ***********************************************************************/ 1.277 + 1.278 + // create a note (sine wave) of the given frequency (Hz), for the given 1.279 + // duration (seconds) scaled to the given volume (amplitude) 1.280 + private static double[] note(double hz, double duration, double amplitude) { 1.281 + int N = (int) (StdAudio.SAMPLE_RATE * duration); 1.282 + double[] a = new double[N+1]; 1.283 + for (int i = 0; i <= N; i++) 1.284 + a[i] = amplitude * Math.sin(2 * Math.PI * i * hz / StdAudio.SAMPLE_RATE); 1.285 + return a; 1.286 + } 1.287 + 1.288 + /** 1.289 + * Test client - play an A major scale to standard audio. 1.290 + */ 1.291 + public static void main(String[] args) { 1.292 + 1.293 + // 440 Hz for 1 sec 1.294 + double freq = 440.0; 1.295 + for (int i = 0; i <= StdAudio.SAMPLE_RATE; i++) { 1.296 + StdAudio.play(0.5 * Math.sin(2*Math.PI * freq * i / StdAudio.SAMPLE_RATE)); 1.297 + } 1.298 + 1.299 + // scale increments 1.300 + int[] steps = { 0, 2, 4, 5, 7, 9, 11, 12 }; 1.301 + for (int i = 0; i < steps.length; i++) { 1.302 + double hz = 440.0 * Math.pow(2, steps[i] / 12.0); 1.303 + StdAudio.play(note(hz, 1.0, 0.5)); 1.304 + } 1.305 + 1.306 + 1.307 + // need to call this in non-interactive stuff so the program doesn't terminate 1.308 + // until all the sound leaves the speaker. 1.309 + StdAudio.close(); 1.310 + 1.311 + // need to terminate a Java program with sound 1.312 + System.exit(0); 1.313 + } 1.314 +} 1.315 +