package edu.msoe.taylor.audio; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.File; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.AudioFileFormat; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.DataLine; import java.util.List; /** * A facade class that simplifies interaction with the Java sound * library. The class provides functionality for creating and playing * sounds and saving the sounds as .wav files. * @author t a y l o r@msoe.edu * @version 2006.04.25 */ public class SimpleAudio { /** * BITS_PER_SAMPLE number of bits used to represent an audio sample. */ private static final int BITS_PER_SAMPLE = 16; /** * CHANNELS number of audio channels. */ private static final int CHANNELS = 1; /** * SIGNED_BYTES are the audio samples signed. */ private static final boolean SIGNED_BYTES = true; /** * BIGENDIAN byte order format for audio samples. */ private static final boolean BIGENDIAN = true; /** * BUFFER_SIZE size of the buffer used by SimpleAudio objects. */ private static final int BUFFER_SIZE = 64000; /** * BUFFER_SIZE size of the buffer used by SimpleAudio objects. */ private static final int DEFAULT_SAMPLE_RATE = 8000; /** * format audio format information. */ private AudioFormat format; /** * Constructor * @param sampleRate number of audio samples per second */ public SimpleAudio(float sampleRate) { format = new AudioFormat(sampleRate, BITS_PER_SAMPLE, CHANNELS, SIGNED_BYTES, BIGENDIAN); } /** * Default constructor which uses the default sample rate. */ public SimpleAudio() { this(DEFAULT_SAMPLE_RATE); } /** * Send audio samples to the the speakers. * @param samples Audio samples to be sent to the speakers. * @throws javax.sound.sampled.LineUnavailableException * @throws java.io.IOException */ public void play(List samples) throws LineUnavailableException, IOException { AudioInputStream audioIS = toAudioInputStream(samples); DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, format); if(AudioSystem.isLineSupported(dataLineInfo)) { SourceDataLine line = null; try { line = (SourceDataLine)AudioSystem.getLine(dataLineInfo); line.open(format); line.start(); byte[] buffer = new byte[BUFFER_SIZE]; int bytesRead = 0; int offset = 0; while(bytesRead != -1) { bytesRead = audioIS.read(buffer); if(bytesRead>0) { line.write(buffer, offset, bytesRead); } } } finally { // Drain and close the line even if an exception was thrown. line.drain(); line.close(); } } } /** * Save audio samples to a file using the .wav format. * @param file File to receive the audio samples. * @param samples Audio samples to be written to the file. * @throws java.io.IOException */ public void saveWAV(File file, List samples) throws IOException { AudioInputStream audioIS = toAudioInputStream(samples); AudioSystem.write(audioIS, AudioFileFormat.Type.WAVE, file); } /** * Convert the audio samples into an audio input stream. * @param samples Audio samples to be used. * @return An audio input stream containing the audio samples. */ private AudioInputStream toAudioInputStream(List samples) { byte[] data = toBytes(samples); ByteArrayInputStream byteStream = new ByteArrayInputStream(data); long numOfFrames = data.length / format.getFrameSize(); return new AudioInputStream(byteStream, format, numOfFrames); } /** * Convert the audio samples into a byte array that can be used * to populate an AudioInputStream. * @param samples Audio samples to be used. * @return A byte array representation of the audio samples. */ private byte[] toBytes(List samples) { byte[] byteArray = new byte[samples.size()*2]; int index = 0; for(float sample : samples) { sample = Math.min(1.0f, Math.max(-1.0f, sample)); int quantized = Math.round(sample * 32767.0f); byte high = (byte)((quantized>>8) & 0xff); byte low = (byte)(quantized & 0xff); byteArray[index++] = high; byteArray[index++] = low; } return byteArray; } }