Java tutorial
/* DEVELOPING GAME IN JAVA Caracteristiques Editeur : NEW RIDERS Auteur : BRACKEEN Parution : 09 2003 Pages : 972 Isbn : 1-59273-005-1 Reliure : Paperback Disponibilite : Disponible a la librairie */ import java.io.ByteArrayInputStream; import java.io.DataInputStream; import java.io.File; import java.io.FilterInputStream; import java.io.IOException; import java.io.InputStream; import javax.sound.sampled.AudioFormat; import javax.sound.sampled.AudioInputStream; import javax.sound.sampled.AudioSystem; import javax.sound.sampled.DataLine; import javax.sound.sampled.LineUnavailableException; import javax.sound.sampled.SourceDataLine; import javax.sound.sampled.UnsupportedAudioFileException; /** * An example of playing a sound with an echo filter. * * @see EchoFilter * @see SimpleSoundPlayer */ public class EchoFilterTest { public static void main(String[] args) { // load the sound SimpleSoundPlayer sound = new SimpleSoundPlayer("../sounds/voice.wav"); // create the sound stream InputStream is = new ByteArrayInputStream(sound.getSamples()); // create an echo with a 11025-sample buffer // (1/4 sec for 44100Hz sound) and a 60% decay EchoFilter filter = new EchoFilter(11025, .6f); // create the filtered sound stream is = new FilteredSoundStream(is, filter); // play the sound sound.play(is); // due to bug in Java Sound, explicitly exit the VM. System.exit(0); } } /** * The SimpleSoundPlayer encapsulates a sound that can be opened from the file * system and later played. */ class SimpleSoundPlayer { public static void main(String[] args) { // load a sound SimpleSoundPlayer sound = new SimpleSoundPlayer("../sounds/voice.wav"); // create the stream to play InputStream stream = new ByteArrayInputStream(sound.getSamples()); // play the sound sound.play(stream); // exit System.exit(0); } private AudioFormat format; private byte[] samples; /** * Opens a sound from a file. */ public SimpleSoundPlayer(String filename) { try { // open the audio input stream AudioInputStream stream = AudioSystem.getAudioInputStream(new File(filename)); format = stream.getFormat(); // get the audio samples samples = getSamples(stream); } catch (UnsupportedAudioFileException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } } /** * Gets the samples of this sound as a byte array. */ public byte[] getSamples() { return samples; } /** * Gets the samples from an AudioInputStream as an array of bytes. */ private byte[] getSamples(AudioInputStream audioStream) { // get the number of bytes to read int length = (int) (audioStream.getFrameLength() * format.getFrameSize()); // read the entire stream byte[] samples = new byte[length]; DataInputStream is = new DataInputStream(audioStream); try { is.readFully(samples); } catch (IOException ex) { ex.printStackTrace(); } // return the samples return samples; } /** * Plays a stream. This method blocks (doesn't return) until the sound is * finished playing. */ public void play(InputStream source) { // use a short, 100ms (1/10th sec) buffer for real-time // change to the sound stream int bufferSize = format.getFrameSize() * Math.round(format.getSampleRate() / 10); byte[] buffer = new byte[bufferSize]; // create a line to play to SourceDataLine line; try { DataLine.Info info = new DataLine.Info(SourceDataLine.class, format); line = (SourceDataLine) AudioSystem.getLine(info); line.open(format, bufferSize); } catch (LineUnavailableException ex) { ex.printStackTrace(); return; } // start the line line.start(); // copy data to the line try { int numBytesRead = 0; while (numBytesRead != -1) { numBytesRead = source.read(buffer, 0, buffer.length); if (numBytesRead != -1) { line.write(buffer, 0, numBytesRead); } } } catch (IOException ex) { ex.printStackTrace(); } // wait until all data is played, then close the line line.drain(); line.close(); } } /** * The EchoFilter class is a SoundFilter that emulates an echo. * * @see FilteredSoundStream */ class EchoFilter extends SoundFilter { private short[] delayBuffer; private int delayBufferPos; private float decay; /** * Creates an EchoFilter with the specified number of delay samples and the * specified decay rate. * <p> * The number of delay samples specifies how long before the echo is * initially heard. For a 1 second echo with mono, 44100Hz sound, use 44100 * delay samples. * <p> * The decay value is how much the echo has decayed from the source. A decay * value of .5 means the echo heard is half as loud as the source. */ public EchoFilter(int numDelaySamples, float decay) { delayBuffer = new short[numDelaySamples]; this.decay = decay; } /** * Gets the remaining size, in bytes, of samples that this filter can echo * after the sound is done playing. Ensures that the sound will have decayed * to below 1% of maximum volume (amplitude). */ public int getRemainingSize() { float finalDecay = 0.01f; // derived from Math.pow(decay,x) <= finalDecay int numRemainingBuffers = (int) Math.ceil(Math.log(finalDecay) / Math.log(decay)); int bufferSize = delayBuffer.length * 2; return bufferSize * numRemainingBuffers; } /** * Clears this EchoFilter's internal delay buffer. */ public void reset() { for (int i = 0; i < delayBuffer.length; i++) { delayBuffer[i] = 0; } delayBufferPos = 0; } /** * Filters the sound samples to add an echo. The samples played are added to * the sound in the delay buffer multipied by the decay rate. The result is * then stored in the delay buffer, so multiple echoes are heard. */ public void filter(byte[] samples, int offset, int length) { for (int i = offset; i < offset + length; i += 2) { // update the sample short oldSample = getSample(samples, i); short newSample = (short) (oldSample + decay * delayBuffer[delayBufferPos]); setSample(samples, i, newSample); // update the delay buffer delayBuffer[delayBufferPos] = newSample; delayBufferPos++; if (delayBufferPos == delayBuffer.length) { delayBufferPos = 0; } } } } /** * The FilteredSoundStream class is a FilterInputStream that applies a * SoundFilter to the underlying input stream. * * @see SoundFilter */ class FilteredSoundStream extends FilterInputStream { private static final int REMAINING_SIZE_UNKNOWN = -1; private SoundFilter soundFilter; private int remainingSize; /** * Creates a new FilteredSoundStream object with the specified InputStream * and SoundFilter. */ public FilteredSoundStream(InputStream in, SoundFilter soundFilter) { super(in); this.soundFilter = soundFilter; remainingSize = REMAINING_SIZE_UNKNOWN; } /** * Overrides the FilterInputStream method to apply this filter whenever * bytes are read */ public int read(byte[] samples, int offset, int length) throws IOException { // read and filter the sound samples in the stream int bytesRead = super.read(samples, offset, length); if (bytesRead > 0) { soundFilter.filter(samples, offset, bytesRead); return bytesRead; } // if there are no remaining bytes in the sound stream, // check if the filter has any remaining bytes ("echoes"). if (remainingSize == REMAINING_SIZE_UNKNOWN) { remainingSize = soundFilter.getRemainingSize(); // round down to nearest multiple of 4 // (typical frame size) remainingSize = remainingSize / 4 * 4; } if (remainingSize > 0) { length = Math.min(length, remainingSize); // clear the buffer for (int i = offset; i < offset + length; i++) { samples[i] = 0; } // filter the remaining bytes soundFilter.filter(samples, offset, length); remainingSize -= length; // return return length; } else { // end of stream return -1; } } } /** * A abstract class designed to filter sound samples. Since SoundFilters may use * internal buffering of samples, a new SoundFilter object should be created for * every sound played. However, SoundFilters can be reused after they are * finished by called the reset() method. * <p> * Assumes all samples are 16-bit, signed, little-endian format. * * @see FilteredSoundStream */ abstract class SoundFilter { /** * Resets this SoundFilter. Does nothing by default. */ public void reset() { // do nothing } /** * Gets the remaining size, in bytes, that this filter plays after the sound * is finished. An example would be an echo that plays longer than it's * original sound. This method returns 0 by default. */ public int getRemainingSize() { return 0; } /** * Filters an array of samples. Samples should be in 16-bit, signed, * little-endian format. */ public void filter(byte[] samples) { filter(samples, 0, samples.length); } /** * Filters an array of samples. Samples should be in 16-bit, signed, * little-endian format. This method should be implemented by subclasses. */ public abstract void filter(byte[] samples, int offset, int length); /** * Convenience method for getting a 16-bit sample from a byte array. Samples * should be in 16-bit, signed, little-endian format. */ public static short getSample(byte[] buffer, int position) { return (short) (((buffer[position + 1] & 0xff) << 8) | (buffer[position] & 0xff)); } /** * Convenience method for setting a 16-bit sample in a byte array. Samples * should be in 16-bit, signed, little-endian format. */ public static void setSample(byte[] buffer, int position, short sample) { buffer[position] = (byte) (sample & 0xff); buffer[position + 1] = (byte) ((sample >> 8) & 0xff); } }