Java/Development Class/MIDI

Материал из Java эксперт
Версия от 18:01, 31 мая 2010; (обсуждение)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

A Swing component that can load and play a sound clip, displaying progress and controls

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.ru/javaexamples3.
 */
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.io.IOException;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Receiver;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.Clip;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.LineUnavailableException;
import javax.sound.sampled.UnsupportedAudioFileException;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JCheckBox;
import javax.swing.JComponent;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JSlider;
import javax.swing.Timer;
import javax.swing.border.TitledBorder;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
/**
 * This class is a Swing component that can load and play a sound clip,
 * displaying progress and controls. The main() method is a test program. This
 * component can play sampled audio or MIDI files, but handles them differently.
 * For sampled audio, time is reported in microseconds, tracked in milliseconds
 * and displayed in seconds and tenths of seconds. For midi files time is
 * reported, tracked, and displayed in MIDI "ticks". This program does no
 * transcoding, so it can only play sound files that use the PCM encoding.
 */
public class SoundPlayer extends JComponent {
  boolean midi; // Are we playing a midi file or a sampled one?
  Sequence sequence; // The contents of a MIDI file
  Sequencer sequencer; // We play MIDI Sequences with a Sequencer
  Clip clip; // Contents of a sampled audio file
  boolean playing = false; // whether the sound is current playing
  // Length and position of the sound are measured in milliseconds for
  // sampled sounds and MIDI "ticks" for MIDI sounds
  int audioLength; // Length of the sound.
  int audioPosition = 0; // Current position within the sound
  // The following fields are for the GUI
  JButton play; // The Play/Stop button
  JSlider progress; // Shows and sets current position in sound
  JLabel time; // Displays audioPosition as a number
  Timer timer; // Updates slider every 100 milliseconds
  // The main method just creates an SoundPlayer in a Frame and displays it
  public static void main(String[] args) throws IOException, UnsupportedAudioFileException,
      LineUnavailableException, MidiUnavailableException, InvalidMidiDataException {
    SoundPlayer player;
    File file = new File(args[0]); // This is the file we"ll be playing
    // Determine whether it is midi or sampled audio
    boolean ismidi;
    try {
      // We discard the return value of this method; we just need to know
      // whether it returns successfully or throws an exception
      MidiSystem.getMidiFileFormat(file);
      ismidi = true;
    } catch (InvalidMidiDataException e) {
      ismidi = false;
    }
    // Create a SoundPlayer object to play the sound.
    player = new SoundPlayer(file, ismidi);
    // Put it in a window and play it
    JFrame f = new JFrame("SoundPlayer");
    f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    f.getContentPane().add(player, "Center");
    f.pack();
    f.setVisible(true);
  }
  // Create an SoundPlayer component for the specified file.
  public SoundPlayer(File f, boolean isMidi) throws IOException, UnsupportedAudioFileException,
      LineUnavailableException, MidiUnavailableException, InvalidMidiDataException {
    if (isMidi) { // The file is a MIDI file
      midi = true;
      // First, get a Sequencer to play sequences of MIDI events
      // That is, to send events to a Synthesizer at the right time.
      sequencer = MidiSystem.getSequencer(); // Used to play sequences
      sequencer.open(); // Turn it on.
      // Get a Synthesizer for the Sequencer to send notes to
      Synthesizer synth = MidiSystem.getSynthesizer();
      synth.open(); // acquire whatever resources it needs
      // The Sequencer obtained above may be connected to a Synthesizer
      // by default, or it may not. Therefore, we explicitly connect it.
      Transmitter transmitter = sequencer.getTransmitter();
      Receiver receiver = synth.getReceiver();
      transmitter.setReceiver(receiver);
      // Read the sequence from the file and tell the sequencer about it
      sequence = MidiSystem.getSequence(f);
      sequencer.setSequence(sequence);
      audioLength = (int) sequence.getTickLength(); // Get sequence length
    } else { // The file is sampled audio
      midi = false;
      // Getting a Clip object for a file of sampled audio data is kind
      // of cumbersome. The following lines do what we need.
      AudioInputStream ain = AudioSystem.getAudioInputStream(f);
      try {
        DataLine.Info info = new DataLine.Info(Clip.class, ain.getFormat());
        clip = (Clip) AudioSystem.getLine(info);
        clip.open(ain);
      } finally { // We"re done with the input stream.
        ain.close();
      }
      // Get the clip length in microseconds and convert to milliseconds
      audioLength = (int) (clip.getMicrosecondLength() / 1000);
    }
    // Now create the basic GUI
    play = new JButton("Play"); // Play/stop button
    progress = new JSlider(0, audioLength, 0); // Shows position in sound
    time = new JLabel("0"); // Shows position as a #
    // When clicked, start or stop playing the sound
    play.addActionListener(new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        if (playing)
          stop();
        else
          play();
      }
    });
    // Whenever the slider value changes, first update the time label.
    // Next, if we"re not already at the new position, skip to it.
    progress.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        int value = progress.getValue();
        // Update the time label
        if (midi)
          time.setText(value + "");
        else
          time.setText(value / 1000 + "." + (value % 1000) / 100);
        // If we"re not already there, skip there.
        if (value != audioPosition)
          skip(value);
      }
    });
    // This timer calls the tick() method 10 times a second to keep
    // our slider in sync with the music.
    timer = new javax.swing.Timer(100, new ActionListener() {
      public void actionPerformed(ActionEvent e) {
        tick();
      }
    });
    // put those controls in a row
    Box row = Box.createHorizontalBox();
    row.add(play);
    row.add(progress);
    row.add(time);
    // And add them to this component.
    setLayout(new BoxLayout(this, BoxLayout.Y_AXIS));
    this.add(row);
    // Now add additional controls based on the type of the sound
    if (midi)
      addMidiControls();
    else
      addSampledControls();
  }
  /** Start playing the sound at the current position */
  public void play() {
    if (midi)
      sequencer.start();
    else
      clip.start();
    timer.start();
    play.setText("Stop");
    playing = true;
  }
  /** Stop playing the sound, but retain the current position */
  public void stop() {
    timer.stop();
    if (midi)
      sequencer.stop();
    else
      clip.stop();
    play.setText("Play");
    playing = false;
  }
  /** Stop playing the sound and reset the position to 0 */
  public void reset() {
    stop();
    if (midi)
      sequencer.setTickPosition(0);
    else
      clip.setMicrosecondPosition(0);
    audioPosition = 0;
    progress.setValue(0);
  }
  /** Skip to the specified position */
  public void skip(int position) { // Called when user drags the slider
    if (position < 0 || position > audioLength)
      return;
    audioPosition = position;
    if (midi)
      sequencer.setTickPosition(position);
    else
      clip.setMicrosecondPosition(position * 1000);
    progress.setValue(position); // in case skip() is called from outside
  }
  /** Return the length of the sound in ms or ticks */
  public int getLength() {
    return audioLength;
  }
  // An internal method that updates the progress bar.
  // The Timer object calls it 10 times a second.
  // If the sound has finished, it resets to the beginning
  void tick() {
    if (midi && sequencer.isRunning()) {
      audioPosition = (int) sequencer.getTickPosition();
      progress.setValue(audioPosition);
    } else if (!midi && clip.isActive()) {
      audioPosition = (int) (clip.getMicrosecondPosition() / 1000);
      progress.setValue(audioPosition);
    } else
      reset();
  }
  // For sampled sounds, add sliders to control volume and balance
  void addSampledControls() {
    try {
      FloatControl gainControl = (FloatControl) clip.getControl(FloatControl.Type.MASTER_GAIN);
      if (gainControl != null)
        this.add(createSlider(gainControl));
    } catch (IllegalArgumentException e) {
      // If MASTER_GAIN volume control is unsupported, just skip it
    }
    try {
      // FloatControl.Type.BALANCE is probably the correct control to
      // use here, but it doesn"t work for me, so I use PAN instead.
      FloatControl panControl = (FloatControl) clip.getControl(FloatControl.Type.PAN);
      if (panControl != null)
        this.add(createSlider(panControl));
    } catch (IllegalArgumentException e) {
    }
  }
  // Return a JSlider component to manipulate the supplied FloatControl
  // for sampled audio.
  JSlider createSlider(final FloatControl c) {
    if (c == null)
      return null;
    final JSlider s = new JSlider(0, 1000);
    final float min = c.getMinimum();
    final float max = c.getMaximum();
    final float width = max - min;
    float fval = c.getValue();
    s.setValue((int) ((fval - min) / width * 1000));
    java.util.Hashtable labels = new java.util.Hashtable(3);
    labels.put(new Integer(0), new JLabel(c.getMinLabel()));
    labels.put(new Integer(500), new JLabel(c.getMidLabel()));
    labels.put(new Integer(1000), new JLabel(c.getMaxLabel()));
    s.setLabelTable(labels);
    s.setPaintLabels(true);
    s.setBorder(new TitledBorder(c.getType().toString() + " " + c.getUnits()));
    s.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        int i = s.getValue();
        float f = min + (i * width / 1000.0f);
        c.setValue(f);
      }
    });
    return s;
  }
  // For Midi files, create a JSlider to control the tempo,
  // and create JCheckBoxes to mute or solo each MIDI track.
  void addMidiControls() {
    // Add a slider to control the tempo
    final JSlider tempo = new JSlider(50, 200);
    tempo.setValue((int) (sequencer.getTempoFactor() * 100));
    tempo.setBorder(new TitledBorder("Tempo Adjustment (%)"));
    java.util.Hashtable labels = new java.util.Hashtable();
    labels.put(new Integer(50), new JLabel("50%"));
    labels.put(new Integer(100), new JLabel("100%"));
    labels.put(new Integer(200), new JLabel("200%"));
    tempo.setLabelTable(labels);
    tempo.setPaintLabels(true);
    // The event listener actually changes the tmpo
    tempo.addChangeListener(new ChangeListener() {
      public void stateChanged(ChangeEvent e) {
        sequencer.setTempoFactor(tempo.getValue() / 100.0f);
      }
    });
    this.add(tempo);
    // Create rows of solo and checkboxes for each track
    Track[] tracks = sequence.getTracks();
    for (int i = 0; i < tracks.length; i++) {
      final int tracknum = i;
      // Two checkboxes per track
      final JCheckBox solo = new JCheckBox("solo");
      final JCheckBox mute = new JCheckBox("mute");
      // The listeners solo or mute the track
      solo.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          sequencer.setTrackSolo(tracknum, solo.isSelected());
        }
      });
      mute.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
          sequencer.setTrackMute(tracknum, mute.isSelected());
        }
      });
      // Build up a row
      Box box = Box.createHorizontalBox();
      box.add(new JLabel("Track " + tracknum));
      box.add(Box.createHorizontalStrut(10));
      box.add(solo);
      box.add(Box.createHorizontalStrut(10));
      box.add(mute);
      box.add(Box.createHorizontalGlue());
      // And add it to this component
      this.add(box);
    }
  }
}





Play Piano

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.ru/javaexamples3.
 */
import java.io.File;
import java.io.IOException;
import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaEventListener;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Sequence;
import javax.sound.midi.Sequencer;
import javax.sound.midi.ShortMessage;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Track;
public class PlayerPiano {
  // These are some MIDI constants from the spec. They aren"t defined
  // for us in javax.sound.midi.
  public static final int DAMPER_PEDAL = 64;
  public static final int DAMPER_ON = 127;
  public static final int DAMPER_OFF = 0;
  public static final int END_OF_TRACK = 47;
  public static void main(String[] args) throws MidiUnavailableException, InvalidMidiDataException,
      IOException {
    int instrument = 0;
    int tempo = 120;
    String filename = null;
    // Parse the options
    // -i <instrument number> default 0, a piano. Allowed values: 0-127
    // -t <beats per minute> default tempo is 120 quarter notes per minute
    // -o <filename> save to a midi file instead of playing
    int a = 0;
    while (a < args.length) {
      if (args[a].equals("-i")) {
        instrument = Integer.parseInt(args[a + 1]);
        a += 2;
      } else if (args[a].equals("-t")) {
        tempo = Integer.parseInt(args[a + 1]);
        a += 2;
      } else if (args[a].equals("-o")) {
        filename = args[a + 1];
        a += 2;
      } else
        break;
    }
    char[] notes = args[a].toCharArray();
    // 16 ticks per quarter note.
    Sequence sequence = new Sequence(Sequence.PPQ, 16);
    // Add the specified notes to the track
    addTrack(sequence, instrument, tempo, notes);
    if (filename == null) { // no filename, so play the notes
      // Set up the Sequencer and Synthesizer objects
      Sequencer sequencer = MidiSystem.getSequencer();
      sequencer.open();
      Synthesizer synthesizer = MidiSystem.getSynthesizer();
      synthesizer.open();
      sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
      // Specify the sequence to play, and the tempo to play it at
      sequencer.setSequence(sequence);
      sequencer.setTempoInBPM(tempo);
      // Let us know when it is done playing
      sequencer.addMetaEventListener(new MetaEventListener() {
        public void meta(MetaMessage m) {
          // A message of this type is automatically sent
          // when we reach the end of the track
          if (m.getType() == END_OF_TRACK)
            System.exit(0);
        }
      });
      // And start playing now.
      sequencer.start();
    } else { // A file name was specified, so save the notes
      int[] allowedTypes = MidiSystem.getMidiFileTypes(sequence);
      if (allowedTypes.length == 0) {
        System.err.println("No supported MIDI file types.");
      } else {
        MidiSystem.write(sequence, allowedTypes[0], new File(filename));
        System.exit(0);
      }
    }
  }
  static final int[] offsets = { // add these amounts to the base value
  // A B C D E F G
      -4, -2, 0, 1, 3, 5, 7 };
  /*
   * This method parses the specified char[] of notes into a Track. The musical
   * notation is the following: A-G: A named note; Add b for flat and # for
   * sharp. +: Move up one octave. Persists. -: Move down one octave. Persists.
   * /1: Notes are whole notes. Persists "till changed /2: Half notes /4:
   * Quarter notes /n: N can also be, 8, 16, 32, 64. s: Toggle sustain pedal on
   * or off (initially off)
   *  >: Louder. Persists <: Softer. Persists .: Rest. Length depends on current
   * length setting Space: Play the previous note or notes; notes not separated
   * by spaces are played at the same time
   */
  public static void addTrack(Sequence s, int instrument, int tempo, char[] notes)
      throws InvalidMidiDataException {
    Track track = s.createTrack(); // Begin with a new track
    // Set the instrument on channel 0
    ShortMessage sm = new ShortMessage();
    sm.setMessage(ShortMessage.PROGRAM_CHANGE, 0, instrument, 0);
    track.add(new MidiEvent(sm, 0));
    int n = 0; // current character in notes[] array
    int t = 0; // time in ticks for the composition
    // These values persist and apply to all notes "till changed
    int notelength = 16; // default to quarter notes
    int velocity = 64; // default to middle volume
    int basekey = 60; // 60 is middle C. Adjusted up and down by octave
    boolean sustain = false; // is the sustain pedal depressed?
    int numnotes = 0; // How many notes in current chord?
    while (n < notes.length) {
      char c = notes[n++];
      if (c == "+")
        basekey += 12; // increase octave
      else if (c == "-")
        basekey -= 12; // decrease octave
      else if (c == ">")
        velocity += 16; // increase volume;
      else if (c == "<")
        velocity -= 16; // decrease volume;
      else if (c == "/") {
        char d = notes[n++];
        if (d == "2")
          notelength = 32; // half note
        else if (d == "4")
          notelength = 16; // quarter note
        else if (d == "8")
          notelength = 8; // eighth note
        else if (d == "3" && notes[n++] == "2")
          notelength = 2;
        else if (d == "6" && notes[n++] == "4")
          notelength = 1;
        else if (d == "1") {
          if (n < notes.length && notes[n] == "6")
            notelength = 4; // 1/16th note
          else
            notelength = 64; // whole note
        }
      } else if (c == "s") {
        sustain = !sustain;
        // Change the sustain setting for channel 0
        ShortMessage m = new ShortMessage();
        m
            .setMessage(ShortMessage.CONTROL_CHANGE, 0, DAMPER_PEDAL, sustain ? DAMPER_ON
                : DAMPER_OFF);
        track.add(new MidiEvent(m, t));
      } else if (c >= "A" && c <= "G") {
        int key = basekey + offsets[c - "A"];
        if (n < notes.length) {
          if (notes[n] == "b") { // flat
            key--;
            n++;
          } else if (notes[n] == "#") { // sharp
            key++;
            n++;
          }
        }
        addNote(track, t, notelength, key, velocity);
        numnotes++;
      } else if (c == " ") {
        // Spaces separate groups of notes played at the same time.
        // But we ignore them unless they follow a note or notes.
        if (numnotes > 0) {
          t += notelength;
          numnotes = 0;
        }
      } else if (c == ".") {
        // Rests are like spaces in that they force any previous
        // note to be output (since they are never part of chords)
        if (numnotes > 0) {
          t += notelength;
          numnotes = 0;
        }
        // Now add additional rest time
        t += notelength;
      }
    }
  }
  // A convenience method to add a note to the track on channel 0
  public static void addNote(Track track, int startTick, int tickLength, int key, int velocity)
      throws InvalidMidiDataException {
    ShortMessage on = new ShortMessage();
    on.setMessage(ShortMessage.NOTE_ON, 0, key, velocity);
    ShortMessage off = new ShortMessage();
    off.setMessage(ShortMessage.NOTE_OFF, 0, key, velocity);
    track.add(new MidiEvent(on, startTick));
    track.add(new MidiEvent(off, startTick + tickLength));
  }
}





Plays sounds streaming from a URL

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.ru/javaexamples3.
 */
import java.io.IOException;
import java.net.URL;
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.Sequencer;
import javax.sound.midi.Synthesizer;
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;
/**
 * This class plays sounds streaming from a URL: it does not have to preload the
 * entire sound into memory before playing it. It is a command-line application
 * with no gui. It includes code to convert ULAW and ALAW audio formats to PCM
 * so they can be played. Use the -m command-line option before MIDI files.
 */
public class PlaySoundStream {
  // Create a URL from the command-line argument and pass it to the
  // right static method depending on the presence of the -m (MIDI) option.
  public static void main(String[] args) throws Exception {
    if (args[0].equals("-m"))
      streamMidiSequence(new URL(args[1]));
    else
      streamSampledAudio(new URL(args[0]));
    // Exit explicitly.
    // This is needed because the audio system starts background threads.
    System.exit(0);
  }
  /** Read sampled audio data from the specified URL and play it */
  public static void streamSampledAudio(URL url) throws IOException, UnsupportedAudioFileException,
      LineUnavailableException {
    AudioInputStream ain = null; // We read audio data from here
    SourceDataLine line = null; // And write it here.
    try {
      // Get an audio input stream from the URL
      ain = AudioSystem.getAudioInputStream(url);
      // Get information about the format of the stream
      AudioFormat format = ain.getFormat();
      DataLine.Info info = new DataLine.Info(SourceDataLine.class, format);
      // If the format is not supported directly (i.e. if it is not PCM
      // encoded, then try to transcode it to PCM.
      if (!AudioSystem.isLineSupported(info)) {
        // This is the PCM format we want to transcode to.
        // The parameters here are audio format details that you
        // shouldn"t need to understand for casual use.
        AudioFormat pcm = new AudioFormat(format.getSampleRate(), 16, format.getChannels(), true,
            false);
        // Get a wrapper stream around the input stream that does the
        // transcoding for us.
        ain = AudioSystem.getAudioInputStream(pcm, ain);
        // Update the format and info variables for the transcoded data
        format = ain.getFormat();
        info = new DataLine.Info(SourceDataLine.class, format);
      }
      // Open the line through which we"ll play the streaming audio.
      line = (SourceDataLine) AudioSystem.getLine(info);
      line.open(format);
      // Allocate a buffer for reading from the input stream and writing
      // to the line. Make it large enough to hold 4k audio frames.
      // Note that the SourceDataLine also has its own internal buffer.
      int framesize = format.getFrameSize();
      byte[] buffer = new byte[4 * 1024 * framesize]; // the buffer
      int numbytes = 0; // how many bytes
      // We haven"t started the line yet.
      boolean started = false;
      for (;;) { // We"ll exit the loop when we reach the end of stream
        // First, read some bytes from the input stream.
        int bytesread = ain.read(buffer, numbytes, buffer.length - numbytes);
        // If there were no more bytes to read, we"re done.
        if (bytesread == -1)
          break;
        numbytes += bytesread;
        // Now that we"ve got some audio data, to write to the line,
        // start the line, so it will play that data as we write it.
        if (!started) {
          line.start();
          started = true;
        }
        // We must write bytes to the line in an integer multiple of
        // the framesize. So figure out how many bytes we"ll write.
        int bytestowrite = (numbytes / framesize) * framesize;
        // Now write the bytes. The line will buffer them and play
        // them. This call will block until all bytes are written.
        line.write(buffer, 0, bytestowrite);
        // If we didn"t have an integer multiple of the frame size,
        // then copy the remaining bytes to the start of the buffer.
        int remaining = numbytes - bytestowrite;
        if (remaining > 0)
          System.arraycopy(buffer, bytestowrite, buffer, 0, remaining);
        numbytes = remaining;
      }
      // Now block until all buffered sound finishes playing.
      line.drain();
    } finally { // Always relinquish the resources we use
      if (line != null)
        line.close();
      if (ain != null)
        ain.close();
    }
  }
  // A MIDI protocol constant that isn"t defined by javax.sound.midi
  public static final int END_OF_TRACK = 47;
  /* MIDI or RMF data from the specified URL and play it */
  public static void streamMidiSequence(URL url) throws IOException, InvalidMidiDataException,
      MidiUnavailableException {
    Sequencer sequencer = null; // Converts a Sequence to MIDI events
    Synthesizer synthesizer = null; // Plays notes in response to MIDI events
    try {
      // Create, open, and connect a Sequencer and Synthesizer
      // They are closed in the finally block at the end of this method.
      sequencer = MidiSystem.getSequencer();
      sequencer.open();
      synthesizer = MidiSystem.getSynthesizer();
      synthesizer.open();
      sequencer.getTransmitter().setReceiver(synthesizer.getReceiver());
      // Specify the InputStream to stream the sequence from
      sequencer.setSequence(url.openStream());
      // This is an arbitrary object used with wait and notify to
      // prevent the method from returning before the music finishes
      final Object lock = new Object();
      // Register a listener to make the method exit when the stream is
      // done. See Object.wait() and Object.notify()
      sequencer.addMetaEventListener(new MetaEventListener() {
        public void meta(MetaMessage e) {
          if (e.getType() == END_OF_TRACK) {
            synchronized (lock) {
              lock.notify();
            }
          }
        }
      });
      // Start playing the music
      sequencer.start();
      // Now block until the listener above notifies us that we"re done.
      synchronized (lock) {
        while (sequencer.isRunning()) {
          try {
            lock.wait();
          } catch (InterruptedException e) {
          }
        }
      }
    } finally {
      // Always relinquish the sequencer, so others can use it.
      if (sequencer != null)
        sequencer.close();
      if (synthesizer != null)
        synthesizer.close();
    }
  }
}





Program the MIDI percussion channel with a Swing window

/*
 * Copyright (c) 2004 David Flanagan.  All rights reserved.
 * This code is from the book Java Examples in a Nutshell, 3nd Edition.
 * It is provided AS-IS, WITHOUT ANY WARRANTY either expressed or implied.
 * You may study, use, and modify it for any non-commercial purpose,
 * including teaching and use in open-source projects.
 * You may distribute it non-commercially as long as you retain this notice.
 * For a commercial use license, or to purchase the book, 
 * please visit http://www.davidflanagan.ru/javaexamples3.
 */
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import javax.sound.midi.MidiChannel;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.Synthesizer;
import javax.swing.JFrame;
/**
 * This program the MIDI percussion channel with a Swing window. It monitors
 * keystrokes and mouse motion in the window and uses them to create music.
 * Keycodes between 35 and 81, inclusive, generate different percussive sounds.
 * See the VK_ constants in java.awt.event.KeyEvent, or just experiment. Mouse
 * position controls volume: move the mouse to the right of the window to
 * increase the volume.
 */
public class Drums extends JFrame {
  MidiChannel channel; // The channel we play on: 10 is for percussion
  int velocity = 64; // Default volume is 50%
  public static void main(String[] args) throws MidiUnavailableException {
    // We don"t need a Sequencer in this example, since we send MIDI
    // events directly to the Synthesizer instead.
    Synthesizer synthesizer = MidiSystem.getSynthesizer();
    synthesizer.open();
    JFrame frame = new Drums(synthesizer);
    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    frame.setSize(50, 128); // We use window width as volume control
    frame.setVisible(true);
  }
  public Drums(Synthesizer synth) {
    super("Drums");
    // Channel 10 is the GeneralMidi percussion channel. In Java code, we
    // number channels from 0 and use channel 9 instead.
    channel = synth.getChannels()[9];
    addKeyListener(new KeyAdapter() {
      public void keyPressed(KeyEvent e) {
        int key = e.getKeyCode();
        if (key >= 35 && key <= 81) {
          channel.noteOn(key, velocity);
        }
      }
      public void keyReleased(KeyEvent e) {
        int key = e.getKeyCode();
        if (key >= 35 && key <= 81)
          channel.noteOff(key);
      }
    });
    addMouseMotionListener(new MouseMotionAdapter() {
      public void mouseMoved(MouseEvent e) {
        velocity = e.getX();
      }
    });
  }
}