/*
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.
*
* 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.
*
* 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.
*
* 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);
}
}