Sound Manager Test : Sound « Development Class « Java






Sound Manager Test

Sound Manager Test
 
       /*
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.Color;
import java.awt.Container;
import java.awt.DisplayMode;
import java.awt.EventQueue;
import java.awt.FlowLayout;
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.Toolkit;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.FilterInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedList;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
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.Mixer;
import javax.sound.sampled.SourceDataLine;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.AbstractButton;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JToggleButton;
import javax.swing.RepaintManager;

/**
 * The SoundManagerTest demonstrates the functionality of the SoundManager
 * class. It provides the following demos:
 * <ul>
 * <li>Playing a Midi sequence.
 * <li>Toggle a track of a playing Midi sequence.
 * <li>Playing a sound.
 * <li>Playing a Sound with an Echo filter.
 * <li>Looping a sound.
 * <li>Playing the maximum number of sounds at once.
 * <li>Pausing all sounds.
 * </ul>
 * <p>
 * This class wasn't listed in the book ;)
 * 
 * @see SoundManager
 * @see Sound
 * @see SoundFilter
 */
public class SoundManagerTest extends GameCore implements ActionListener {
  public static void main(String[] args) {
    new SoundManagerTest().run();
  }

  // uncompressed, 44100Hz, 16-bit, mono, signed, little-endian
  private static final AudioFormat PLAYBACK_FORMAT = new AudioFormat(44100,
      16, 1, true, false);

  private static final int MANY_SOUNDS_COUNT = SoundManager
      .getMaxSimultaneousSounds(PLAYBACK_FORMAT);

  private static final int DRUM_TRACK = 1;

  private static final String EXIT = "Exit";

  private static final String PAUSE = "Pause";

  private static final String PLAY_MUSIC = "Play Music";

  private static final String MUSIC_DRUMS = "Toggle Drums";

  private static final String PLAY_SOUND = "Play Sound";

  private static final String PLAY_ECHO_SOUND = "Play Echoed Sound";

  private static final String PLAY_LOOPING_SOUND = "Play Looping Sound";

  private static final String PLAY_MANY_SOUNDS = "Play " + MANY_SOUNDS_COUNT
      + " Sounds";

  private SoundManager soundManager;

  private MidiPlayer midiPlayer;

  private Sequence music;

  private Sound boop;

  private Sound bzz;

  private InputStream lastloopingSound;

  public void init() {
    super.init();
    initSounds();
    initUI();
  }

  /**
   * Loads sounds and music.
   */
  public void initSounds() {
    midiPlayer = new MidiPlayer();
    soundManager = new SoundManager(PLAYBACK_FORMAT);
    music = midiPlayer.getSequence("../sounds/music.midi");
    boop = soundManager.getSound("../sounds/boop.wav");
    bzz = soundManager.getSound("../sounds/fly-bzz.wav");
  }

  /**
   * Creates the UI, which is a row of buttons.
   */
  public void initUI() {
    // make sure Swing components don't paint themselves
    NullRepaintManager.install();

    JFrame frame = super.screen.getFullScreenWindow();
    Container contentPane = frame.getContentPane();

    contentPane.setLayout(new FlowLayout());
    contentPane.add(createButton(PAUSE, true));
    contentPane.add(createButton(PLAY_MUSIC, true));
    contentPane.add(createButton(MUSIC_DRUMS, false));
    contentPane.add(createButton(PLAY_SOUND, false));
    contentPane.add(createButton(PLAY_ECHO_SOUND, false));
    contentPane.add(createButton(PLAY_LOOPING_SOUND, true));
    contentPane.add(createButton(PLAY_MANY_SOUNDS, false));
    contentPane.add(createButton(EXIT, false));

    // explicitly layout components (needed on some systems)
    frame.validate();
  }

  /**
   * Draws all Swing components
   */
  public void draw(Graphics2D g) {
    JFrame frame = super.screen.getFullScreenWindow();
    frame.getLayeredPane().paintComponents(g);
  }

  /**
   * Creates a button (either JButton or JToggleButton).
   */
  public AbstractButton createButton(String name, boolean canToggle) {
    AbstractButton button;

    if (canToggle) {
      button = new JToggleButton(name);
    } else {
      button = new JButton(name);
    }
    button.addActionListener(this);
    button.setIgnoreRepaint(true);
    button.setFocusable(false);

    return button;
  }

  /**
   * Performs actions when a button is pressed.
   */
  public void actionPerformed(ActionEvent e) {
    String command = e.getActionCommand();
    AbstractButton button = (AbstractButton) e.getSource();
    if (command == EXIT) {
      midiPlayer.close();
      soundManager.close();
      stop();
    } else if (command == PAUSE) {
      // pause the sound
      soundManager.setPaused(button.isSelected());
      midiPlayer.setPaused(button.isSelected());
    } else if (command == PLAY_MUSIC) {
      // toggle music on or off
      if (button.isSelected()) {
        midiPlayer.play(music, true);
      } else {
        midiPlayer.stop();
      }
    } else if (command == MUSIC_DRUMS) {
      // toggle drums on or off
      Sequencer sequencer = midiPlayer.getSequencer();
      if (sequencer != null) {
        boolean mute = sequencer.getTrackMute(DRUM_TRACK);
        sequencer.setTrackMute(DRUM_TRACK, !mute);
      }
    } else if (command == PLAY_SOUND) {
      // play a normal sound
      soundManager.play(boop);
    } else if (command == PLAY_ECHO_SOUND) {
      // play a sound with an echo
      EchoFilter filter = new EchoFilter(11025, .6f);
      soundManager.play(boop, filter, false);
    } else if (command == PLAY_LOOPING_SOUND) {
      // play or stop the looping sound
      if (button.isSelected()) {
        lastloopingSound = soundManager.play(bzz, null, true);
      } else if (lastloopingSound != null) {
        try {
          lastloopingSound.close();
        } catch (IOException ex) {
        }
        lastloopingSound = null;
      }
    } else if (command == PLAY_MANY_SOUNDS) {
      // play several sounds at once, to test the system
      for (int i = 0; i < MANY_SOUNDS_COUNT; i++) {
        soundManager.play(boop);
      }
    }
  }

}

/**
 * The SoundManager class manages sound playback. The SoundManager is a
 * ThreadPool, with each thread playing back one sound at a time. This allows
 * the SoundManager to easily limit the number of simultaneous sounds being
 * played.
 * <p>
 * Possible ideas to extend this class:
 * <ul>
 * <li>add a setMasterVolume() method, which uses Controls to set the volume
 * for each line.
 * <li>don't play a sound if more than, say, 500ms has passed since the request
 * to play
 * </ul>
 */

class SoundManager extends ThreadPool {

  private AudioFormat playbackFormat;

  private ThreadLocal localLine;

  private ThreadLocal localBuffer;

  private Object pausedLock;

  private boolean paused;

  /**
   * Creates a new SoundManager using the maximum number of simultaneous
   * sounds.
   */
  public SoundManager(AudioFormat playbackFormat) {
    this(playbackFormat, getMaxSimultaneousSounds(playbackFormat));
  }

  /**
   * Creates a new SoundManager with the specified maximum number of
   * simultaneous sounds.
   */
  public SoundManager(AudioFormat playbackFormat, int maxSimultaneousSounds) {
    super(Math.min(maxSimultaneousSounds,
        getMaxSimultaneousSounds(playbackFormat)));
    this.playbackFormat = playbackFormat;
    localLine = new ThreadLocal();
    localBuffer = new ThreadLocal();
    pausedLock = new Object();
    // notify threads in pool it's ok to start
    synchronized (this) {
      notifyAll();
    }
  }

  /**
   * Gets the maximum number of simultaneous sounds with the specified
   * AudioFormat that the default mixer can play.
   */
  public static int getMaxSimultaneousSounds(AudioFormat playbackFormat) {
    DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
        playbackFormat);
    Mixer mixer = AudioSystem.getMixer(null);
    return mixer.getMaxLines(lineInfo);
  }

  /**
   * Does any clean up before closing.
   */
  protected void cleanUp() {
    // signal to unpause
    setPaused(false);

    // close the mixer (stops any running sounds)
    Mixer mixer = AudioSystem.getMixer(null);
    if (mixer.isOpen()) {
      mixer.close();
    }
  }

  public void close() {
    cleanUp();
    super.close();
  }

  public void join() {
    cleanUp();
    super.join();
  }

  /**
   * Sets the paused state. Sounds may not pause immediately.
   */
  public void setPaused(boolean paused) {
    if (this.paused != paused) {
      synchronized (pausedLock) {
        this.paused = paused;
        if (!paused) {
          // restart sounds
          pausedLock.notifyAll();
        }
      }
    }
  }

  /**
   * Returns the paused state.
   */
  public boolean isPaused() {
    return paused;
  }

  /**
   * Loads a Sound from the file system. Returns null if an error occurs.
   */
  public Sound getSound(String filename) {
    return getSound(getAudioInputStream(filename));
  }

  /**
   * Loads a Sound from an input stream. Returns null if an error occurs.
   */
  public Sound getSound(InputStream is) {
    return getSound(getAudioInputStream(is));
  }

  /**
   * Loads a Sound from an AudioInputStream.
   */
  public Sound getSound(AudioInputStream audioStream) {
    if (audioStream == null) {
      return null;
    }

    // get the number of bytes to read
    int length = (int) (audioStream.getFrameLength() * audioStream
        .getFormat().getFrameSize());

    // read the entire stream
    byte[] samples = new byte[length];
    DataInputStream is = new DataInputStream(audioStream);
    try {
      is.readFully(samples);
      is.close();
    } catch (IOException ex) {
      ex.printStackTrace();
    }

    // return the samples
    return new Sound(samples);
  }

  /**
   * Creates an AudioInputStream from a sound from the file system.
   */
  public AudioInputStream getAudioInputStream(String filename) {
    try {
      return getAudioInputStream(new FileInputStream(filename));
    } catch (IOException ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * Creates an AudioInputStream from a sound from an input stream
   */
  public AudioInputStream getAudioInputStream(InputStream is) {

    try {
      if (!is.markSupported()) {
        is = new BufferedInputStream(is);
      }
      // open the source stream
      AudioInputStream source = AudioSystem.getAudioInputStream(is);

      // convert to playback format
      return AudioSystem.getAudioInputStream(playbackFormat, source);
    } catch (UnsupportedAudioFileException ex) {
      ex.printStackTrace();
    } catch (IOException ex) {
      ex.printStackTrace();
    } catch (IllegalArgumentException ex) {
      ex.printStackTrace();
    }

    return null;
  }

  /**
   * Plays a sound. This method returns immediately.
   */
  public InputStream play(Sound sound) {
    return play(sound, null, false);
  }

  /**
   * Plays a sound with an optional SoundFilter, and optionally looping. This
   * method returns immediately.
   */
  public InputStream play(Sound sound, SoundFilter filter, boolean loop) {
    InputStream is;
    if (sound != null) {
      if (loop) {
        is = new LoopingByteInputStream(sound.getSamples());
      } else {
        is = new ByteArrayInputStream(sound.getSamples());
      }

      return play(is, filter);
    }
    return null;
  }

  /**
   * Plays a sound from an InputStream. This method returns immediately.
   */
  public InputStream play(InputStream is) {
    return play(is, null);
  }

  /**
   * Plays a sound from an InputStream with an optional sound filter. This
   * method returns immediately.
   */
  public InputStream play(InputStream is, SoundFilter filter) {
    if (is != null) {
      if (filter != null) {
        is = new FilteredSoundStream(is, filter);
      }
      runTask(new SoundPlayer(is));
    }
    return is;
  }

  /**
   * Signals that a PooledThread has started. Creates the Thread's line and
   * buffer.
   */
  protected void threadStarted() {
    // wait for the SoundManager constructor to finish
    synchronized (this) {
      try {
        wait();
      } catch (InterruptedException ex) {
      }
    }

    // use a short, 100ms (1/10th sec) buffer for filters that
    // change in real-time
    int bufferSize = playbackFormat.getFrameSize()
        * Math.round(playbackFormat.getSampleRate() / 10);

    // create, open, and start the line
    SourceDataLine line;
    DataLine.Info lineInfo = new DataLine.Info(SourceDataLine.class,
        playbackFormat);
    try {
      line = (SourceDataLine) AudioSystem.getLine(lineInfo);
      line.open(playbackFormat, bufferSize);
    } catch (LineUnavailableException ex) {
      // the line is unavailable - signal to end this thread
      Thread.currentThread().interrupt();
      return;
    }

    line.start();

    // create the buffer
    byte[] buffer = new byte[bufferSize];

    // set this thread's locals
    localLine.set(line);
    localBuffer.set(buffer);
  }

  /**
   * Signals that a PooledThread has stopped. Drains and closes the Thread's
   * Line.
   */
  protected void threadStopped() {
    SourceDataLine line = (SourceDataLine) localLine.get();
    if (line != null) {
      line.drain();
      line.close();
    }
  }

  /**
   * The SoundPlayer class is a task for the PooledThreads to run. It receives
   * the threads's Line and byte buffer from the ThreadLocal variables and
   * plays a sound from an InputStream.
   * <p>
   * This class only works when called from a PooledThread.
   */
  protected class SoundPlayer implements Runnable {

    private InputStream source;

    public SoundPlayer(InputStream source) {
      this.source = source;
    }

    public void run() {
      // get line and buffer from ThreadLocals
      SourceDataLine line = (SourceDataLine) localLine.get();
      byte[] buffer = (byte[]) localBuffer.get();
      if (line == null || buffer == null) {
        // the line is unavailable
        return;
      }

      // copy data to the line
      try {
        int numBytesRead = 0;
        while (numBytesRead != -1) {
          // if paused, wait until unpaused
          synchronized (pausedLock) {
            if (paused) {
              try {
                pausedLock.wait();
              } catch (InterruptedException ex) {
                return;
              }
            }
          }
          // copy data
          numBytesRead = source.read(buffer, 0, buffer.length);
          if (numBytesRead != -1) {
            line.write(buffer, 0, numBytesRead);
          }
        }
      } catch (IOException ex) {
        ex.printStackTrace();
      }

    }
  }

}

class MidiPlayer implements MetaEventListener {

  // Midi meta event
  public static final int END_OF_TRACK_MESSAGE = 47;

  private Sequencer sequencer;

  private boolean loop;

  private boolean paused;

  /**
   * Creates a new MidiPlayer object.
   */
  public MidiPlayer() {
    try {
      sequencer = MidiSystem.getSequencer();
      sequencer.open();
      sequencer.addMetaEventListener(this);
    } catch (MidiUnavailableException ex) {
      sequencer = null;
    }
  }

  /**
   * Loads a sequence from the file system. Returns null if an error occurs.
   */
  public Sequence getSequence(String filename) {
    try {
      return getSequence(new FileInputStream(filename));
    } catch (IOException ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * Loads a sequence from an input stream. Returns null if an error occurs.
   */
  public Sequence getSequence(InputStream is) {
    try {
      if (!is.markSupported()) {
        is = new BufferedInputStream(is);
      }
      Sequence s = MidiSystem.getSequence(is);
      is.close();
      return s;
    } catch (InvalidMidiDataException ex) {
      ex.printStackTrace();
      return null;
    } catch (IOException ex) {
      ex.printStackTrace();
      return null;
    }
  }

  /**
   * Plays a sequence, optionally looping. This method returns immediately.
   * The sequence is not played if it is invalid.
   */
  public void play(Sequence sequence, boolean loop) {
    if (sequencer != null && sequence != null && sequencer.isOpen()) {
      try {
        sequencer.setSequence(sequence);
        sequencer.start();
        this.loop = loop;
      } catch (InvalidMidiDataException ex) {
        ex.printStackTrace();
      }
    }
  }

  /**
   * This method is called by the sound system when a meta event occurs. In
   * this case, when the end-of-track meta event is received, the sequence is
   * restarted if looping is on.
   */
  public void meta(MetaMessage event) {
    if (event.getType() == END_OF_TRACK_MESSAGE) {
      if (sequencer != null && sequencer.isOpen() && loop) {
        sequencer.start();
      }
    }
  }

  /**
   * Stops the sequencer and resets its position to 0.
   */
  public void stop() {
    if (sequencer != null && sequencer.isOpen()) {
      sequencer.stop();
      sequencer.setMicrosecondPosition(0);
    }
  }

  /**
   * Closes the sequencer.
   */
  public void close() {
    if (sequencer != null && sequencer.isOpen()) {
      sequencer.close();
    }
  }

  /**
   * Gets the sequencer.
   */
  public Sequencer getSequencer() {
    return sequencer;
  }

  /**
   * Sets the paused state. Music may not immediately pause.
   */
  public void setPaused(boolean paused) {
    if (this.paused != paused && sequencer != null && sequencer.isOpen()) {
      this.paused = paused;
      if (paused) {
        sequencer.stop();
      } else {
        sequencer.start();
      }
    }
  }

  /**
   * Returns the paused state.
   */
  public boolean isPaused() {
    return paused;
  }

}

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

  /**
   * 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 NullRepaintManager is a RepaintManager that doesn't do any repainting.
 * Useful when all the rendering is done manually by the application.
 */

class NullRepaintManager extends RepaintManager {

  /**
   * Installs the NullRepaintManager.
   */
  public static void install() {
    RepaintManager repaintManager = new NullRepaintManager();
    repaintManager.setDoubleBufferingEnabled(false);
    RepaintManager.setCurrentManager(repaintManager);
  }

  public void addInvalidComponent(JComponent c) {
    // do nothing
  }

  public void addDirtyRegion(JComponent c, int x, int y, int w, int h) {
    // do nothing
  }

  public void markCompletelyDirty(JComponent c) {
    // do nothing
  }

  public void paintDirtyRegions() {
    // do nothing
  }

}

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

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

}

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

}

/**
 * A thread pool is a group of a limited number of threads that are used to
 * execute tasks.
 */

class ThreadPool extends ThreadGroup {

  private boolean isAlive;

  private LinkedList taskQueue;

  private int threadID;

  private static int threadPoolID;

  /**
   * Creates a new ThreadPool.
   * 
   * @param numThreads
   *            The number of threads in the pool.
   */
  public ThreadPool(int numThreads) {
    super("ThreadPool-" + (threadPoolID++));
    setDaemon(true);

    isAlive = true;

    taskQueue = new LinkedList();
    for (int i = 0; i < numThreads; i++) {
      new PooledThread().start();
    }
  }

  /**
   * Requests a new task to run. This method returns immediately, and the task
   * executes on the next available idle thread in this ThreadPool.
   * <p>
   * Tasks start execution in the order they are received.
   * 
   * @param task
   *            The task to run. If null, no action is taken.
   * @throws IllegalStateException
   *             if this ThreadPool is already closed.
   */
  public synchronized void runTask(Runnable task) {
    if (!isAlive) {
      throw new IllegalStateException();
    }
    if (task != null) {
      taskQueue.add(task);
      notify();
    }

  }

  protected synchronized Runnable getTask() throws InterruptedException {
    while (taskQueue.size() == 0) {
      if (!isAlive) {
        return null;
      }
      wait();
    }
    return (Runnable) taskQueue.removeFirst();
  }

  /**
   * Closes this ThreadPool and returns immediately. All threads are stopped,
   * and any waiting tasks are not executed. Once a ThreadPool is closed, no
   * more tasks can be run on this ThreadPool.
   */
  public synchronized void close() {
    if (isAlive) {
      isAlive = false;
      taskQueue.clear();
      interrupt();
    }
  }

  /**
   * Closes this ThreadPool and waits for all running threads to finish. Any
   * waiting tasks are executed.
   */
  public void join() {
    // notify all waiting threads that this ThreadPool is no
    // longer alive
    synchronized (this) {
      isAlive = false;
      notifyAll();
    }

    // wait for all threads to finish
    Thread[] threads = new Thread[activeCount()];
    int count = enumerate(threads);
    for (int i = 0; i < count; i++) {
      try {
        threads[i].join();
      } catch (InterruptedException ex) {
      }
    }
  }

  /**
   * Signals that a PooledThread has started. This method does nothing by
   * default; subclasses should override to do any thread-specific startup
   * tasks.
   */
  protected void threadStarted() {
    // do nothing
  }

  /**
   * Signals that a PooledThread has stopped. This method does nothing by
   * default; subclasses should override to do any thread-specific cleanup
   * tasks.
   */
  protected void threadStopped() {
    // do nothing
  }

  /**
   * A PooledThread is a Thread in a ThreadPool group, designed to run tasks
   * (Runnables).
   */
  private class PooledThread extends Thread {

    public PooledThread() {
      super(ThreadPool.this, "PooledThread-" + (threadID++));
    }

    public void run() {
      // signal that this thread has started
      threadStarted();

      while (!isInterrupted()) {

        // get a task to run
        Runnable task = null;
        try {
          task = getTask();
        } catch (InterruptedException ex) {
        }

        // if getTask() returned null or was interrupted,
        // close this thread.
        if (task == null) {
          break;
        }

        // run the task, and eat any exceptions it throws
        try {
          task.run();
        } catch (Throwable t) {
          uncaughtException(this, t);
        }
      }
      // signal that this thread has stopped
      threadStopped();
    }
  }
}

/**
 * The Sound class is a container for sound samples. The sound samples are
 * format-agnostic and are stored as a byte array.
 */

class Sound {

  private byte[] samples;

  /**
   * Create a new Sound object with the specified byte array. The array is not
   * copied.
   */
  public Sound(byte[] samples) {
    this.samples = samples;
  }

  /**
   * Returns this Sound's objects samples as a byte array.
   */
  public byte[] getSamples() {
    return samples;
  }

}

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

}

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

}

           
         
  








Related examples in the same category

1.Play sound
2.Duke Speaks
3.Duke Speaks Test
4.Sound Applet
5.Sound player
6.Simple program to try out the new Sound stuff in JDK1.2
7.Sound Application Sound Application
8.An example of loading and playing a sound using a ClipAn example of loading and playing a sound using a Clip
9.An example of playing a sound with an echo filter
10.The Filter3dTest class demonstrates the Filter3d functionality
11.An example that plays a Midi sequence
12.Determining When a Sampled Audio Player Has Finished Playing
13.Setting the Volume of a Sampled Audio Player
14.Determining the Position of a Sampled Audio Player
15.Load audio file From URL
16.Playing Streaming Sampled Audio
17.Determining the File Format of a Sampled Audio File
18.Loading and Playing Sampled Audio
19.Determining the Position of a Midi Sequencer
20.Continuously Playing a Sampled Audio File
21.Load image and sound from Jar file
22.Determine the duration of a Midi audio file
23.Playing Streaming Midi Audio
24.Capturing Audio with Java Sound API
25.A simple player for sampled sound files.
26.Load and play Midi audio
27.Play a streaming Midi audio
28.Determining When a Midi Audio Player Has Finished Playing
29.Float Control Component
30.This is a simple program to record sounds and play them back
31.Determining the Duration of a Midi Audio File
32.Loading and Playing Midi Audio
33.Determining the File Format of a Midi Audio File
34.Play an audio file from a JAR file
35.Setting the Volume of Playing Midi Audio
36.Make your own Java Media Player to play media files
37.Determining the Encoding of a Sampled Audio File
38.Determining the Duration of a Sampled Audio File
39.audio sound: implements java.applet.AudioClip