Mega Code Archive

 
Categories / Java / Development Class
 

The Filter3dTest class demonstrates the Filter3d functionality

/* 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.awt.AWTException; import java.awt.Color; import java.awt.Component; import java.awt.Cursor; import java.awt.DisplayMode; import java.awt.EventQueue; import java.awt.Font; import java.awt.Graphics2D; import java.awt.GraphicsConfiguration; import java.awt.GraphicsDevice; import java.awt.GraphicsEnvironment; import java.awt.Image; import java.awt.Point; import java.awt.Robot; import java.awt.Toolkit; import java.awt.Window; import java.awt.event.KeyEvent; import java.awt.event.KeyListener; import java.awt.event.MouseEvent; import java.awt.event.MouseListener; import java.awt.event.MouseMotionListener; import java.awt.event.MouseWheelEvent; import java.awt.event.MouseWheelListener; import java.awt.image.BufferStrategy; import java.awt.image.BufferedImage; 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 java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.List; 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; import javax.swing.ImageIcon; import javax.swing.JFrame; import javax.swing.SwingUtilities; /**  * The Filter3dTest class demonstrates the Filter3d functionality. A fly buzzes  * around the listener, and the closer the fly is, the louder it's heard.  *   * @see Filter3d  * @see SimpleSoundPlayer  */ public class Filter3dTest extends GameCore {   public static void main(String[] args) {     new Filter3dTest().run();   }   private Sprite fly;   private Sprite listener;   private InputManager inputManager;   private GameAction exit;   private SimpleSoundPlayer bzzSound;   private InputStream bzzSoundStream;   public void init() {     super.init();     // set up input manager     exit = new GameAction("exit", GameAction.DETECT_INITAL_PRESS_ONLY);     inputManager = new InputManager(screen.getFullScreenWindow());     inputManager.mapToKey(exit, KeyEvent.VK_ESCAPE);     inputManager.setCursor(InputManager.INVISIBLE_CURSOR);     createSprites();     // load the sound     bzzSound = new SimpleSoundPlayer("../sounds/fly-bzz.wav");     // create the 3d filter     Filter3d filter = new Filter3d(fly, listener, screen.getHeight());     // create the filtered sound stream     bzzSoundStream = new FilteredSoundStream(new LoopingByteInputStream(         bzzSound.getSamples()), filter);     // play the sound in a separate thread     new Thread() {       public void run() {         bzzSound.play(bzzSoundStream);       }     }.start();   }   /**    * Loads images and creates sprites.    */   private void createSprites() {     // load images     Image fly1 = loadImage("../images/fly1.png");     Image fly2 = loadImage("../images/fly2.png");     Image fly3 = loadImage("../images/fly3.png");     Image ear = loadImage("../images/ear.png");     // create "fly" sprite     Animation anim = new Animation();     anim.addFrame(fly1, 50);     anim.addFrame(fly2, 50);     anim.addFrame(fly3, 50);     anim.addFrame(fly2, 50);     fly = new Sprite(anim);     // create the listener sprite     anim = new Animation();     anim.addFrame(ear, 0);     listener = new Sprite(anim);     listener.setX((screen.getWidth() - listener.getWidth()) / 2);     listener.setY((screen.getHeight() - listener.getHeight()) / 2);   }   public void update(long elapsedTime) {     if (exit.isPressed()) {       stop();     } else {       listener.update(elapsedTime);       fly.update(elapsedTime);       fly.setX(inputManager.getMouseX());       fly.setY(inputManager.getMouseY());     }   }   public void stop() {     super.stop();     // stop the bzz sound     try {       bzzSoundStream.close();     } catch (IOException ex) {     }   }   public void draw(Graphics2D g) {     // draw background     g.setColor(new Color(0x33cc33));     g.fillRect(0, 0, screen.getWidth(), screen.getHeight());     // draw listener     g.drawImage(listener.getImage(), Math.round(listener.getX()), Math         .round(listener.getY()), null);     // draw fly     g.drawImage(fly.getImage(), Math.round(fly.getX()), Math.round(fly         .getY()), null);   } } /**  * 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 InputManager manages input of key and mouse events. Events are mapped to  * GameActions.  */ class InputManager implements KeyListener, MouseListener, MouseMotionListener,     MouseWheelListener {   /**    * An invisible cursor.    */   public static final Cursor INVISIBLE_CURSOR = Toolkit.getDefaultToolkit()       .createCustomCursor(Toolkit.getDefaultToolkit().getImage(""),           new Point(0, 0), "invisible");   // mouse codes   public static final int MOUSE_MOVE_LEFT = 0;   public static final int MOUSE_MOVE_RIGHT = 1;   public static final int MOUSE_MOVE_UP = 2;   public static final int MOUSE_MOVE_DOWN = 3;   public static final int MOUSE_WHEEL_UP = 4;   public static final int MOUSE_WHEEL_DOWN = 5;   public static final int MOUSE_BUTTON_1 = 6;   public static final int MOUSE_BUTTON_2 = 7;   public static final int MOUSE_BUTTON_3 = 8;   private static final int NUM_MOUSE_CODES = 9;   // key codes are defined in java.awt.KeyEvent.   // most of the codes (except for some rare ones like   // "alt graph") are less than 600.   private static final int NUM_KEY_CODES = 600;   private GameAction[] keyActions = new GameAction[NUM_KEY_CODES];   private GameAction[] mouseActions = new GameAction[NUM_MOUSE_CODES];   private Point mouseLocation;   private Point centerLocation;   private Component comp;   private Robot robot;   private boolean isRecentering;   /**    * Creates a new InputManager that listens to input from the specified    * component.    */   public InputManager(Component comp) {     this.comp = comp;     mouseLocation = new Point();     centerLocation = new Point();     // register key and mouse listeners     comp.addKeyListener(this);     comp.addMouseListener(this);     comp.addMouseMotionListener(this);     comp.addMouseWheelListener(this);     // allow input of the TAB key and other keys normally     // used for focus traversal     comp.setFocusTraversalKeysEnabled(false);   }   /**    * Sets the cursor on this InputManager's input component.    */   public void setCursor(Cursor cursor) {     comp.setCursor(cursor);   }   /**    * Sets whether realtive mouse mode is on or not. For relative mouse mode,    * the mouse is "locked" in the center of the screen, and only the changed    * in mouse movement is measured. In normal mode, the mouse is free to move    * about the screen.    */   public void setRelativeMouseMode(boolean mode) {     if (mode == isRelativeMouseMode()) {       return;     }     if (mode) {       try {         robot = new Robot();         recenterMouse();       } catch (AWTException ex) {         // couldn't create robot!         robot = null;       }     } else {       robot = null;     }   }   /**    * Returns whether or not relative mouse mode is on.    */   public boolean isRelativeMouseMode() {     return (robot != null);   }   /**    * Maps a GameAction to a specific key. The key codes are defined in    * java.awt.KeyEvent. If the key already has a GameAction mapped to it, the    * new GameAction overwrites it.    */   public void mapToKey(GameAction gameAction, int keyCode) {     keyActions[keyCode] = gameAction;   }   /**    * Maps a GameAction to a specific mouse action. The mouse codes are defined    * herer in InputManager (MOUSE_MOVE_LEFT, MOUSE_BUTTON_1, etc). If the    * mouse action already has a GameAction mapped to it, the new GameAction    * overwrites it.    */   public void mapToMouse(GameAction gameAction, int mouseCode) {     mouseActions[mouseCode] = gameAction;   }   /**    * Clears all mapped keys and mouse actions to this GameAction.    */   public void clearMap(GameAction gameAction) {     for (int i = 0; i < keyActions.length; i++) {       if (keyActions[i] == gameAction) {         keyActions[i] = null;       }     }     for (int i = 0; i < mouseActions.length; i++) {       if (mouseActions[i] == gameAction) {         mouseActions[i] = null;       }     }     gameAction.reset();   }   /**    * Gets a List of names of the keys and mouse actions mapped to this    * GameAction. Each entry in the List is a String.    */   public List getMaps(GameAction gameCode) {     ArrayList list = new ArrayList();     for (int i = 0; i < keyActions.length; i++) {       if (keyActions[i] == gameCode) {         list.add(getKeyName(i));       }     }     for (int i = 0; i < mouseActions.length; i++) {       if (mouseActions[i] == gameCode) {         list.add(getMouseName(i));       }     }     return list;   }   /**    * Resets all GameActions so they appear like they haven't been pressed.    */   public void resetAllGameActions() {     for (int i = 0; i < keyActions.length; i++) {       if (keyActions[i] != null) {         keyActions[i].reset();       }     }     for (int i = 0; i < mouseActions.length; i++) {       if (mouseActions[i] != null) {         mouseActions[i].reset();       }     }   }   /**    * Gets the name of a key code.    */   public static String getKeyName(int keyCode) {     return KeyEvent.getKeyText(keyCode);   }   /**    * Gets the name of a mouse code.    */   public static String getMouseName(int mouseCode) {     switch (mouseCode) {     case MOUSE_MOVE_LEFT:       return "Mouse Left";     case MOUSE_MOVE_RIGHT:       return "Mouse Right";     case MOUSE_MOVE_UP:       return "Mouse Up";     case MOUSE_MOVE_DOWN:       return "Mouse Down";     case MOUSE_WHEEL_UP:       return "Mouse Wheel Up";     case MOUSE_WHEEL_DOWN:       return "Mouse Wheel Down";     case MOUSE_BUTTON_1:       return "Mouse Button 1";     case MOUSE_BUTTON_2:       return "Mouse Button 2";     case MOUSE_BUTTON_3:       return "Mouse Button 3";     default:       return "Unknown mouse code " + mouseCode;     }   }   /**    * Gets the x position of the mouse.    */   public int getMouseX() {     return mouseLocation.x;   }   /**    * Gets the y position of the mouse.    */   public int getMouseY() {     return mouseLocation.y;   }   /**    * Uses the Robot class to try to postion the mouse in the center of the    * screen.    * <p>    * Note that use of the Robot class may not be available on all platforms.    */   private synchronized void recenterMouse() {     if (robot != null && comp.isShowing()) {       centerLocation.x = comp.getWidth() / 2;       centerLocation.y = comp.getHeight() / 2;       SwingUtilities.convertPointToScreen(centerLocation, comp);       isRecentering = true;       robot.mouseMove(centerLocation.x, centerLocation.y);     }   }   private GameAction getKeyAction(KeyEvent e) {     int keyCode = e.getKeyCode();     if (keyCode < keyActions.length) {       return keyActions[keyCode];     } else {       return null;     }   }   /**    * Gets the mouse code for the button specified in this MouseEvent.    */   public static int getMouseButtonCode(MouseEvent e) {     switch (e.getButton()) {     case MouseEvent.BUTTON1:       return MOUSE_BUTTON_1;     case MouseEvent.BUTTON2:       return MOUSE_BUTTON_2;     case MouseEvent.BUTTON3:       return MOUSE_BUTTON_3;     default:       return -1;     }   }   private GameAction getMouseButtonAction(MouseEvent e) {     int mouseCode = getMouseButtonCode(e);     if (mouseCode != -1) {       return mouseActions[mouseCode];     } else {       return null;     }   }   // from the KeyListener interface   public void keyPressed(KeyEvent e) {     GameAction gameAction = getKeyAction(e);     if (gameAction != null) {       gameAction.press();     }     // make sure the key isn't processed for anything else     e.consume();   }   // from the KeyListener interface   public void keyReleased(KeyEvent e) {     GameAction gameAction = getKeyAction(e);     if (gameAction != null) {       gameAction.release();     }     // make sure the key isn't processed for anything else     e.consume();   }   // from the KeyListener interface   public void keyTyped(KeyEvent e) {     // make sure the key isn't processed for anything else     e.consume();   }   // from the MouseListener interface   public void mousePressed(MouseEvent e) {     GameAction gameAction = getMouseButtonAction(e);     if (gameAction != null) {       gameAction.press();     }   }   // from the MouseListener interface   public void mouseReleased(MouseEvent e) {     GameAction gameAction = getMouseButtonAction(e);     if (gameAction != null) {       gameAction.release();     }   }   // from the MouseListener interface   public void mouseClicked(MouseEvent e) {     // do nothing   }   // from the MouseListener interface   public void mouseEntered(MouseEvent e) {     mouseMoved(e);   }   // from the MouseListener interface   public void mouseExited(MouseEvent e) {     mouseMoved(e);   }   // from the MouseMotionListener interface   public void mouseDragged(MouseEvent e) {     mouseMoved(e);   }   // from the MouseMotionListener interface   public synchronized void mouseMoved(MouseEvent e) {     // this event is from re-centering the mouse - ignore it     if (isRecentering && centerLocation.x == e.getX()         && centerLocation.y == e.getY()) {       isRecentering = false;     } else {       int dx = e.getX() - mouseLocation.x;       int dy = e.getY() - mouseLocation.y;       mouseHelper(MOUSE_MOVE_LEFT, MOUSE_MOVE_RIGHT, dx);       mouseHelper(MOUSE_MOVE_UP, MOUSE_MOVE_DOWN, dy);       if (isRelativeMouseMode()) {         recenterMouse();       }     }     mouseLocation.x = e.getX();     mouseLocation.y = e.getY();   }   // from the MouseWheelListener interface   public void mouseWheelMoved(MouseWheelEvent e) {     mouseHelper(MOUSE_WHEEL_UP, MOUSE_WHEEL_DOWN, e.getWheelRotation());   }   private void mouseHelper(int codeNeg, int codePos, int amount) {     GameAction gameAction;     if (amount < 0) {       gameAction = mouseActions[codeNeg];     } else {       gameAction = mouseActions[codePos];     }     if (gameAction != null) {       gameAction.press(Math.abs(amount));       gameAction.release();     }   } } /**  * Simple abstract class used for testing. Subclasses should implement the  * draw() method.  */ abstract class GameCore {   protected static final int FONT_SIZE = 24;   private static final DisplayMode POSSIBLE_MODES[] = {       new DisplayMode(800, 600, 32, 0), new DisplayMode(800, 600, 24, 0),       new DisplayMode(800, 600, 16, 0), new DisplayMode(640, 480, 32, 0),       new DisplayMode(640, 480, 24, 0), new DisplayMode(640, 480, 16, 0) };   private boolean isRunning;   protected ScreenManager screen;   /**    * Signals the game loop that it's time to quit    */   public void stop() {     isRunning = false;   }   /**    * Calls init() and gameLoop()    */   public void run() {     try {       init();       gameLoop();     } finally {       screen.restoreScreen();       lazilyExit();     }   }   /**    * Exits the VM from a daemon thread. The daemon thread waits 2 seconds then    * calls System.exit(0). Since the VM should exit when only daemon threads    * are running, this makes sure System.exit(0) is only called if neccesary.    * It's neccesary if the Java Sound system is running.    */   public void lazilyExit() {     Thread thread = new Thread() {       public void run() {         // first, wait for the VM exit on its own.         try {           Thread.sleep(2000);         } catch (InterruptedException ex) {         }         // system is still running, so force an exit         System.exit(0);       }     };     thread.setDaemon(true);     thread.start();   }   /**    * Sets full screen mode and initiates and objects.    */   public void init() {     screen = new ScreenManager();     DisplayMode displayMode = screen         .findFirstCompatibleMode(POSSIBLE_MODES);     screen.setFullScreen(displayMode);     Window window = screen.getFullScreenWindow();     window.setFont(new Font("Dialog", Font.PLAIN, FONT_SIZE));     window.setBackground(Color.blue);     window.setForeground(Color.white);     isRunning = true;   }   public Image loadImage(String fileName) {     return new ImageIcon(fileName).getImage();   }   /**    * Runs through the game loop until stop() is called.    */   public void gameLoop() {     long startTime = System.currentTimeMillis();     long currTime = startTime;     while (isRunning) {       long elapsedTime = System.currentTimeMillis() - currTime;       currTime += elapsedTime;       // update       update(elapsedTime);       // draw the screen       Graphics2D g = screen.getGraphics();       draw(g);       g.dispose();       screen.update();       // take a nap       try {         Thread.sleep(20);       } catch (InterruptedException ex) {       }     }   }   /**    * Updates the state of the game/animation based on the amount of elapsed    * time that has passed.    */   public void update(long elapsedTime) {     // do nothing   }   /**    * Draws to the screen. Subclasses must override this method.    */   public abstract void draw(Graphics2D g); } /**  * The GameAction class is an abstract to a user-initiated action, like jumping  * or moving. GameActions can be mapped to keys or the mouse with the  * InputManager.  */ class GameAction {   /**    * Normal behavior. The isPressed() method returns true as long as the key    * is held down.    */   public static final int NORMAL = 0;   /**    * Initial press behavior. The isPressed() method returns true only after    * the key is first pressed, and not again until the key is released and    * pressed again.    */   public static final int DETECT_INITAL_PRESS_ONLY = 1;   private static final int STATE_RELEASED = 0;   private static final int STATE_PRESSED = 1;   private static final int STATE_WAITING_FOR_RELEASE = 2;   private String name;   private int behavior;   private int amount;   private int state;   /**    * Create a new GameAction with the NORMAL behavior.    */   public GameAction(String name) {     this(name, NORMAL);   }   /**    * Create a new GameAction with the specified behavior.    */   public GameAction(String name, int behavior) {     this.name = name;     this.behavior = behavior;     reset();   }   /**    * Gets the name of this GameAction.    */   public String getName() {     return name;   }   /**    * Resets this GameAction so that it appears like it hasn't been pressed.    */   public void reset() {     state = STATE_RELEASED;     amount = 0;   }   /**    * Taps this GameAction. Same as calling press() followed by release().    */   public synchronized void tap() {     press();     release();   }   /**    * Signals that the key was pressed.    */   public synchronized void press() {     press(1);   }   /**    * Signals that the key was pressed a specified number of times, or that the    * mouse move a spcified distance.    */   public synchronized void press(int amount) {     if (state != STATE_WAITING_FOR_RELEASE) {       this.amount += amount;       state = STATE_PRESSED;     }   }   /**    * Signals that the key was released    */   public synchronized void release() {     state = STATE_RELEASED;   }   /**    * Returns whether the key was pressed or not since last checked.    */   public synchronized boolean isPressed() {     return (getAmount() != 0);   }   /**    * For keys, this is the number of times the key was pressed since it was    * last checked. For mouse movement, this is the distance moved.    */   public synchronized int getAmount() {     int retVal = amount;     if (retVal != 0) {       if (state == STATE_RELEASED) {         amount = 0;       } else if (behavior == DETECT_INITAL_PRESS_ONLY) {         state = STATE_WAITING_FOR_RELEASE;         amount = 0;       }     }     return retVal;   } } class Sprite {   private Animation anim;   // position (pixels)   private float x;   private float y;   // velocity (pixels per millisecond)   private float dx;   private float dy;   /**    * Creates a new Sprite object with the specified Animation.    */   public Sprite(Animation anim) {     this.anim = anim;   }   /**    * Updates this Sprite's Animation and its position based on the velocity.    */   public void update(long elapsedTime) {     x += dx * elapsedTime;     y += dy * elapsedTime;     anim.update(elapsedTime);   }   /**    * Gets this Sprite's current x position.    */   public float getX() {     return x;   }   /**    * Gets this Sprite's current y position.    */   public float getY() {     return y;   }   /**    * Sets this Sprite's current x position.    */   public void setX(float x) {     this.x = x;   }   /**    * Sets this Sprite's current y position.    */   public void setY(float y) {     this.y = y;   }   /**    * Gets this Sprite's width, based on the size of the current image.    */   public int getWidth() {     return anim.getImage().getWidth(null);   }   /**    * Gets this Sprite's height, based on the size of the current image.    */   public int getHeight() {     return anim.getImage().getHeight(null);   }   /**    * Gets the horizontal velocity of this Sprite in pixels per millisecond.    */   public float getVelocityX() {     return dx;   }   /**    * Gets the vertical velocity of this Sprite in pixels per millisecond.    */   public float getVelocityY() {     return dy;   }   /**    * Sets the horizontal velocity of this Sprite in pixels per millisecond.    */   public void setVelocityX(float dx) {     this.dx = dx;   }   /**    * Sets the vertical velocity of this Sprite in pixels per millisecond.    */   public void setVelocityY(float dy) {     this.dy = dy;   }   /**    * Gets this Sprite's current image.    */   public Image getImage() {     return anim.getImage();   } } /**  * The Filter3d class is a SoundFilter that creates a 3d sound effect. The sound  * is filtered so that it is quiter the farther away the sound source is from  * the listener.  * <p>  * Possible ideas to extend this class:  * <ul>  * <li>pan the sound to the left and right speakers  * </ul>  *   * @see FilteredSoundStream  */ class Filter3d extends SoundFilter {   // number of samples to shift when changing the volume.   private static final int NUM_SHIFTING_SAMPLES = 500;   private Sprite source;   private Sprite listener;   private int maxDistance;   private float lastVolume;   /**    * Creates a new Filter3d object with the specified source and listener    * Sprites. The Sprite's position can be changed while this filter is    * running.    * <p>    * The maxDistance parameter is the maximum distance that the sound can be    * heard.    */   public Filter3d(Sprite source, Sprite listener, int maxDistance) {     this.source = source;     this.listener = listener;     this.maxDistance = maxDistance;     this.lastVolume = 0.0f;   }   /**    * Filters the sound so that it gets more quiet with distance.    */   public void filter(byte[] samples, int offset, int length) {     if (source == null || listener == null) {       // nothing to filter - return       return;     }     // calculate the listener's distance from the sound source     float dx = (source.getX() - listener.getX());     float dy = (source.getY() - listener.getY());     float distance = (float) Math.sqrt(dx * dx + dy * dy);     // set volume from 0 (no sound) to 1     float newVolume = (maxDistance - distance) / maxDistance;     if (newVolume <= 0) {       newVolume = 0;     }     // set the volume of the sample     int shift = 0;     for (int i = offset; i < offset + length; i += 2) {       float volume = newVolume;       // shift from the last volume to the new volume       if (shift < NUM_SHIFTING_SAMPLES) {         volume = lastVolume + (newVolume - lastVolume) * shift             / NUM_SHIFTING_SAMPLES;         shift++;       }       // change the volume of the sample       short oldSample = getSample(samples, i);       short newSample = (short) (oldSample * volume);       setSample(samples, i, newSample);     }     lastVolume = newVolume;   } } /**  * 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;     }   } } /**  * The Animation class manages a series of images (frames) and the amount of  * time to display each frame.  */ class Animation {   private ArrayList frames;   private int currFrameIndex;   private long animTime;   private long totalDuration;   /**    * Creates a new, empty Animation.    */   public Animation() {     frames = new ArrayList();     totalDuration = 0;     start();   }   /**    * Adds an image to the animation with the specified duration (time to    * display the image).    */   public synchronized void addFrame(Image image, long duration) {     totalDuration += duration;     frames.add(new AnimFrame(image, totalDuration));   }   /**    * Starts this animation over from the beginning.    */   public synchronized void start() {     animTime = 0;     currFrameIndex = 0;   }   /**    * Updates this animation's current image (frame), if neccesary.    */   public synchronized void update(long elapsedTime) {     if (frames.size() > 1) {       animTime += elapsedTime;       if (animTime >= totalDuration) {         animTime = animTime % totalDuration;         currFrameIndex = 0;       }       while (animTime > getFrame(currFrameIndex).endTime) {         currFrameIndex++;       }     }   }   /**    * Gets this Animation's current image. Returns null if this animation has    * no images.    */   public synchronized Image getImage() {     if (frames.size() == 0) {       return null;     } else {       return getFrame(currFrameIndex).image;     }   }   private AnimFrame getFrame(int i) {     return (AnimFrame) frames.get(i);   }   private class AnimFrame {     Image image;     long endTime;     public AnimFrame(Image image, long endTime) {       this.image = image;       this.endTime = endTime;     }   } } /**  * The LoopingByteInputStream is a ByteArrayInputStream that loops indefinitly.  * The looping stops when the close() method is called.  * <p>  * Possible ideas to extend this class:  * <ul>  * <li>Add an option to only loop a certain number of times.  * </ul>  */ class LoopingByteInputStream extends ByteArrayInputStream {   private boolean closed;   /**    * Creates a new LoopingByteInputStream with the specified byte array. The    * array is not copied.    */   public LoopingByteInputStream(byte[] buffer) {     super(buffer);     closed = false;   }   /**    * Reads <code>length</code> bytes from the array. If the end of the array    * is reached, the reading starts over from the beginning of the array.    * Returns -1 if the array has been closed.    */   public int read(byte[] buffer, int offset, int length) {     if (closed) {       return -1;     }     int totalBytesRead = 0;     while (totalBytesRead < length) {       int numBytesRead = super.read(buffer, offset + totalBytesRead,           length - totalBytesRead);       if (numBytesRead > 0) {         totalBytesRead += numBytesRead;       } else {         reset();       }     }     return totalBytesRead;   }   /**    * Closes the stream. Future calls to the read() methods will return 1.    */   public void close() throws IOException {     super.close();     closed = true;   } } /**  * 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);   } } /**  * The ScreenManager class manages initializing and displaying full screen  * graphics modes.  */ class ScreenManager {   private GraphicsDevice device;   /**    * Creates a new ScreenManager object.    */   public ScreenManager() {     GraphicsEnvironment environment = GraphicsEnvironment         .getLocalGraphicsEnvironment();     device = environment.getDefaultScreenDevice();   }   /**    * Returns a list of compatible display modes for the default device on the    * system.    */   public DisplayMode[] getCompatibleDisplayModes() {     return device.getDisplayModes();   }   /**    * Returns the first compatible mode in a list of modes. Returns null if no    * modes are compatible.    */   public DisplayMode findFirstCompatibleMode(DisplayMode modes[]) {     DisplayMode goodModes[] = device.getDisplayModes();     for (int i = 0; i < modes.length; i++) {       for (int j = 0; j < goodModes.length; j++) {         if (displayModesMatch(modes[i], goodModes[j])) {           return modes[i];         }       }     }     return null;   }   /**    * Returns the current display mode.    */   public DisplayMode getCurrentDisplayMode() {     return device.getDisplayMode();   }   /**    * Determines if two display modes "match". Two display modes match if they    * have the same resolution, bit depth, and refresh rate. The bit depth is    * ignored if one of the modes has a bit depth of    * DisplayMode.BIT_DEPTH_MULTI. Likewise, the refresh rate is ignored if one    * of the modes has a refresh rate of DisplayMode.REFRESH_RATE_UNKNOWN.    */   public boolean displayModesMatch(DisplayMode mode1, DisplayMode mode2)   {     if (mode1.getWidth() != mode2.getWidth()         || mode1.getHeight() != mode2.getHeight()) {       return false;     }     if (mode1.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI         && mode2.getBitDepth() != DisplayMode.BIT_DEPTH_MULTI         && mode1.getBitDepth() != mode2.getBitDepth()) {       return false;     }     if (mode1.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN         && mode2.getRefreshRate() != DisplayMode.REFRESH_RATE_UNKNOWN         && mode1.getRefreshRate() != mode2.getRefreshRate()) {       return false;     }     return true;   }   /**    * Enters full screen mode and changes the display mode. If the specified    * display mode is null or not compatible with this device, or if the    * display mode cannot be changed on this system, the current display mode    * is used.    * <p>    * The display uses a BufferStrategy with 2 buffers.    */   public void setFullScreen(DisplayMode displayMode) {     final JFrame frame = new JFrame();     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);     frame.setUndecorated(true);     frame.setIgnoreRepaint(true);     frame.setResizable(false);     device.setFullScreenWindow(frame);     if (displayMode != null && device.isDisplayChangeSupported()) {       try {         device.setDisplayMode(displayMode);       } catch (IllegalArgumentException ex) {       }       // fix for mac os x       frame.setSize(displayMode.getWidth(), displayMode.getHeight());     }     // avoid potential deadlock in 1.4.1_02     try {       EventQueue.invokeAndWait(new Runnable() {         public void run() {           frame.createBufferStrategy(2);         }       });     } catch (InterruptedException ex) {       // ignore     } catch (InvocationTargetException ex) {       // ignore     }   }   /**    * Gets the graphics context for the display. The ScreenManager uses double    * buffering, so applications must call update() to show any graphics drawn.    * <p>    * The application must dispose of the graphics object.    */   public Graphics2D getGraphics() {     Window window = device.getFullScreenWindow();     if (window != null) {       BufferStrategy strategy = window.getBufferStrategy();       return (Graphics2D) strategy.getDrawGraphics();     } else {       return null;     }   }   /**    * Updates the display.    */   public void update() {     Window window = device.getFullScreenWindow();     if (window != null) {       BufferStrategy strategy = window.getBufferStrategy();       if (!strategy.contentsLost()) {         strategy.show();       }     }     // Sync the display on some systems.     // (on Linux, this fixes event queue problems)     Toolkit.getDefaultToolkit().sync();   }   /**    * Returns the window currently used in full screen mode. Returns null if    * the device is not in full screen mode.    */   public JFrame getFullScreenWindow() {     return (JFrame) device.getFullScreenWindow();   }   /**    * Returns the width of the window currently used in full screen mode.    * Returns 0 if the device is not in full screen mode.    */   public int getWidth() {     Window window = device.getFullScreenWindow();     if (window != null) {       return window.getWidth();     } else {       return 0;     }   }   /**    * Returns the height of the window currently used in full screen mode.    * Returns 0 if the device is not in full screen mode.    */   public int getHeight() {     Window window = device.getFullScreenWindow();     if (window != null) {       return window.getHeight();     } else {       return 0;     }   }   /**    * Restores the screen's display mode.    */   public void restoreScreen() {     Window window = device.getFullScreenWindow();     if (window != null) {       window.dispose();     }     device.setFullScreenWindow(null);   }   /**    * Creates an image compatible with the current display.    */   public BufferedImage createCompatibleImage(int w, int h, int transparancy) {     Window window = device.getFullScreenWindow();     if (window != null) {       GraphicsConfiguration gc = window.getGraphicsConfiguration();       return gc.createCompatibleImage(w, h, transparancy);     }     return null;   } }