Working with MIDI in Java is easy. The standard API have classes covering MIDI file I/O, device I/O, sequencing and sound synthesis. This comprhensive tutorial covers most aspects. It also helps to know something about the MIDI format. Juan Bello’s slide deck is an excellent introduction. midi.org is also a good source, including their full message reference table.

To play a single C note through the default included “Gervill” soft synthezier, the following snippet will do. As commented in the code, there’s come discrepancy on pitch or octave labelling, but the 60th note is still on the save octave as A at 440 Hz.

  public void testPlayNote() throws InvalidMidiDataException, MidiUnavailableException {
    // 60 = middle C, C4 (or C3 or C5)
    // https://en.wikipedia.org/wiki/Scientific_pitch_notation#Similar_systems
    int channel = 0;
    int note = 60;
    int velocity = 127; // velocity (i.e. volume); 127 = high
    ShortMessage msg = new ShortMessage();
    msg.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);

    long timeStamp = -1;
    MidiDevice synthesizer = getSynthesizer("Gervill");
    synthesizer.open();
    Receiver receiver = synthesizer.getReceiver();
    receiver.send(msg, timeStamp);
    receiver.close();
  }

This gives a brief overview of the MIDI devices on the system, both software based and hardware devices. Depending on drivers and OS, the various devices might show up under different names and types.

  public void testGetMidiDeviceInfo() throws MidiUnavailableException {
    Info[] infos = MidiSystem.getMidiDeviceInfo();
    for (Info info : infos) {
      MidiDevice device = MidiSystem.getMidiDevice(info);
      boolean isSequencer = device instanceof Sequencer;
      boolean isSynthesizer = device instanceof Synthesizer;

      System.out.println(
          "Info: " + device + ", '" + 
              info.getName() + "',  '" + 
              info.getVendor() + "', '" + 
              info.getVersion() + "', '" + 
              info.getDescription() + "', " + 
              "sequencer=" + isSequencer + 
              ", synthesizer=" + isSynthesizer);
    }
  }

Finally, the following methods demonstream MIDI file I/O.

  public void testWriteFile() throws InvalidMidiDataException, IOException {
    int volume = 0x70;
    int tick = 0;

    Sequence sequence = new Sequence(Sequence.PPQ, 24);
    Track track = sequence.createTrack();

    MetaMessage meta = new MetaMessage();
    String trackName = "First track";
    meta.setMessage(0x03, trackName.getBytes(), trackName.length());
    track.add(new MidiEvent(meta, tick));

    int notes[] = IntStream.range(60, 73).toArray();

    for (int note : notes) {
      ShortMessage on = new ShortMessage(ShortMessage.NOTE_ON, note, volume);
      track.add(new MidiEvent(on, tick));
      tick += 4;

      ShortMessage off = new ShortMessage(ShortMessage.NOTE_OFF, note, 0);
      track.add(new MidiEvent(off, tick));
      tick += 4;
    }

    MidiSystem.write(sequence, 1, MIDI_FILE);
  }

  public void testReadFile() throws InvalidMidiDataException, IOException {
    Sequence sequence = MidiSystem.getSequence(MIDI_FILE);
    
    System.out.println("tracks: " + sequence.getTracks().length);
    System.out.println("patches: " + sequence.getPatchList().length);
    
    for (Track track : sequence.getTracks()) {
      int size = track.size();
      System.out.println("track size: "+size);
      
      IntStream.range(0, size).boxed()
        .map(track::get)
        .map(RawMessage::new)
        .map(AbstractMessage::getNoteOctave)
        .map(str -> str + " ")
        .forEach(System.out::print);
      System.out.println();
    }
  }

  public void testMidiFileFormat() throws InvalidMidiDataException, IOException {
    MidiFileFormat format = MidiSystem.getMidiFileFormat(MIDI_FILE);

    System.out.println("bytes: " + format.getByteLength());
    System.out.println("length ms: " + format.getMicrosecondLength());
    System.out.println("divisionType: " + format.getDivisionType());
    System.out.println("resolution: " + format.getResolution());
    System.out.println("type: " + format.getType());
    System.out.println("properties: " + format.properties().size());

    format.properties().entrySet().stream()
      .map(e -> e.getValue() + "=" + e.getKey())
      .forEach(System.out::println);
  }

The full test case can be seen below. There’s a few more helper classes and details in the same package here. Then there’s some special implementation and details for the Roland TB-03 and TR-8 devices here.

MidiApiTest.java
GitHub Raw
/* Copyright rememberjava.com. Licensed under GPL 3. See http://rememberjava.com/license */
package com.rememberjava.midi;

import static com.rememberjava.midi.MidiUtils.filteredDeviceStream;
import static com.rememberjava.midi.MidiUtils.onClassnameEquals;

import java.io.File;
import java.io.IOException;
import java.util.stream.IntStream;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MetaMessage;
import javax.sound.midi.MidiDevice;
import javax.sound.midi.MidiDevice.Info;
import javax.sound.midi.MidiEvent;
import javax.sound.midi.MidiFileFormat;
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.ShortMessage;
import javax.sound.midi.Synthesizer;
import javax.sound.midi.Track;
import javax.sound.midi.Transmitter;

import org.junit.Test;


/**
 * See also: https://docs.oracle.com/javase/tutorial/sound/overview-MIDI.html
 */
public class MidiApiTest {

  private static final String SOFT_SYNTHESIZER = "SoftSynthesizer";

  private static final String MIDI_FILE_PATH = "com/rememberjava/midi/test.mid";

  private static final File MIDI_FILE = new File(MIDI_FILE_PATH);

  @Test
  public void testGetMidiDeviceInfo() throws MidiUnavailableException {
    Info[] infos = MidiSystem.getMidiDeviceInfo();
    for (Info info : infos) {
      MidiDevice device = MidiSystem.getMidiDevice(info);
      boolean isSequencer = device instanceof Sequencer;
      boolean isSynthesizer = device instanceof Synthesizer;

      System.out.println(
          "Info: " + device + ", '" + 
              info.getName() + "',  '" + 
              info.getVendor() + "', '" + 
              info.getVersion() + "', '" + 
              info.getDescription() + "', " + 
              "sequencer=" + isSequencer + 
              ", synthesizer=" + isSynthesizer);
    }
  }

  @Test
  public void testGetSequencer() throws MidiUnavailableException {
    Sequencer sequencer = MidiSystem.getSequencer();
    Transmitter transmitter = sequencer.getTransmitter();

    System.out.println("Sequencer: " + sequencer.getDeviceInfo());
    System.out.println("Transmitter: " + transmitter);
  }

  @Test
  public void testGetSynthesizer() throws MidiUnavailableException {
    Synthesizer synthesizer = MidiSystem.getSynthesizer();
    Receiver receiver = synthesizer.getReceiver();

    System.out.println("Synthesizer: " + synthesizer.getDeviceInfo());
    System.out.println("Receiver: " + receiver);
  }

  @Test
  public void testPlayNote() throws InvalidMidiDataException, MidiUnavailableException {
    // 60 = middle C, C4 (or C3 or C5)
    // https://en.wikipedia.org/wiki/Scientific_pitch_notation#Similar_systems
    int channel = 0;
    int note = 60;
    int velocity = 127; // velocity (i.e. volume); 127 = high
    ShortMessage msg = new ShortMessage();
    msg.setMessage(ShortMessage.NOTE_ON, channel, note, velocity);

    long timeStamp = -1;
    MidiDevice synthesizer = getSynthesizer("Gervill");
    synthesizer.open();
    Receiver receiver = synthesizer.getReceiver();
    receiver.send(msg, timeStamp);
    receiver.close();
  }
  
  private MidiDevice getSynthesizer(String deviceName) throws MidiUnavailableException {
     return filteredDeviceStream(deviceName)
         .filter(onClassnameEquals(SOFT_SYNTHESIZER))
         .findFirst().get();
  }

  @Test
  public void testWriteFile() throws InvalidMidiDataException, IOException {
    int volume = 0x70;
    int tick = 0;

    Sequence sequence = new Sequence(Sequence.PPQ, 24);
    Track track = sequence.createTrack();

    MetaMessage meta = new MetaMessage();
    String trackName = "First track";
    meta.setMessage(0x03, trackName.getBytes(), trackName.length());
    track.add(new MidiEvent(meta, tick));

    int notes[] = IntStream.range(60, 73).toArray();

    for (int note : notes) {
      ShortMessage on = new ShortMessage(ShortMessage.NOTE_ON, note, volume);
      track.add(new MidiEvent(on, tick));
      tick += 4;

      ShortMessage off = new ShortMessage(ShortMessage.NOTE_OFF, note, 0);
      track.add(new MidiEvent(off, tick));
      tick += 4;
    }

    MidiSystem.write(sequence, 1, MIDI_FILE);
  }

  @Test
  public void testReadFile() throws InvalidMidiDataException, IOException {
    Sequence sequence = MidiSystem.getSequence(MIDI_FILE);
    
    System.out.println("tracks: " + sequence.getTracks().length);
    System.out.println("patches: " + sequence.getPatchList().length);
    
    for (Track track : sequence.getTracks()) {
      int size = track.size();
      System.out.println("track size: "+size);
      
      IntStream.range(0, size).boxed()
        .map(track::get)
        .map(RawMessage::new)
        .map(AbstractMessage::getNoteOctave)
        .map(str -> str + " ")
        .forEach(System.out::print);
      System.out.println();
    }
  }

  @Test
  public void testMidiFileFormat() throws InvalidMidiDataException, IOException {
    MidiFileFormat format = MidiSystem.getMidiFileFormat(MIDI_FILE);

    System.out.println("bytes: " + format.getByteLength());
    System.out.println("length ms: " + format.getMicrosecondLength());
    System.out.println("divisionType: " + format.getDivisionType());
    System.out.println("resolution: " + format.getResolution());
    System.out.println("type: " + format.getType());
    System.out.println("properties: " + format.properties().size());

    format.properties().entrySet().stream()
      .map(e -> e.getValue() + "=" + e.getKey())
      .forEach(System.out::println);
  }
}