Search code examples
javaaudiomidi

Is there any way to know what instruments Java Sequencer's "magic numbers" are mapped to?


Recently, I discovered the magic of Java Sequencer framework. I experimented a little, and now I have this code

public class MidiPlayer {
    private static final int KEYBOARD_CHANNEL = 1;
    private static final int PERCUSSION_CHANNEL = 9;
    private static final int MAGIC_NUMBER_THAT_MEANS_SEQUENCE_IS_FINISHED = 47;
    @SneakyThrows
    public void playRegularPiano() {
        playInstrument(KEYBOARD_CHANNEL, Keyboards.REGULAR_PIANO);
    }

    @SneakyThrows
    public void playWeirdPiano() {
        playInstrument(KEYBOARD_CHANNEL, Keyboards.WEIRD_PIANO);
    }

    @SneakyThrows
    public void playEerieXylo() {
        playInstrument(KEYBOARD_CHANNEL, Keyboards.EERIE_XYLO);
    }

    @SneakyThrows
    public void playSomeUFOStuff() {
        playInstrument(KEYBOARD_CHANNEL, Keyboards.SOME_UFO_STUFF);
    }

    @SneakyThrows
    public void playDrums() {
        playInstrument(PERCUSSION_CHANNEL, 1);
    }

    private void playInstrument(int channel, int instrument) throws MidiUnavailableException, InvalidMidiDataException {
        Sequencer sequencer = MidiSystem.getSequencer();
        sequencer.open();

        Sequence sequence = getSequence(channel, instrument);
        sequencer.setSequence(sequence);

        MetaEventListener listener = getClosingListener(sequencer);
        sequencer.addMetaEventListener(listener);

        sequencer.start();

    }

    private Sequence getSequence(int channel, int instrument) throws InvalidMidiDataException {
        Sequence sequence = new Sequence(Sequence.PPQ, 4);
        Track track = sequence.createTrack();
        ShortMessage firstMessage, secondMessage;
        MidiEvent noteOn, noteOff;

        firstMessage = new ShortMessage();
        firstMessage.setMessage(PROGRAM_CHANGE, channel, instrument, 80); // this is important
        MidiEvent instrumentPick = new MidiEvent(firstMessage, 0);
        track.add(instrumentPick);

        for (int i = 0; i < 8; i++) {
            firstMessage = new ShortMessage();
            firstMessage.setMessage(NOTE_ON, channel, 54 - i, 80);
            noteOn = new MidiEvent(firstMessage, i + 1);
            track.add(noteOn);

            secondMessage = new ShortMessage();
            secondMessage.setMessage(NOTE_OFF, channel, 54 - i, 80);
            noteOff = new MidiEvent(secondMessage, i + 1 + 20);
            track.add(noteOff);
        }

        for (int i = 8; i < 25; i++) {
            firstMessage = new ShortMessage();
            firstMessage.setMessage(NOTE_ON, channel, 54 - (7 - (i - 7)), 100);
            noteOn = new MidiEvent(firstMessage, i + 1);
            track.add(noteOn);

            secondMessage = new ShortMessage();
            secondMessage.setMessage(NOTE_OFF, channel, 54 - (8 - (i - 8)), 100);
            noteOff = new MidiEvent(secondMessage, i + 1 + 20);
            track.add(noteOff);
        }
        return sequence;
    }

    private MetaEventListener getClosingListener(Sequencer sequencer) {
        return (metaMessage) -> {
            if (metaMessage.getType() == MAGIC_NUMBER_THAT_MEANS_SEQUENCE_IS_FINISHED) {
                sequencer.close();
            }
        };
    }

    private static class Keyboards {
        private static final int REGULAR_PIANO = 1;
        private static final int WEIRD_PIANO = 2;
        private static final int EERIE_XYLO = 4;
        private static final int SOME_UFO_STUFF = 7;
    }
}
// a humble main class
public class App {
    public static void main(String[] args) {
        MidiPlayer player = new MidiPlayer();
        player.playRegularPiano();
    }
}

Notice the names of my int constants. I don't have a good ear for musical instruments so you see things like "weird piano", "some UFO stuff", etc. Clearly unprofessional! Is there some official or unofficial table (or something to that effect) that describes what concrete instrument one numeric value for that argument or the other is mapped to? Is there a way I can know for sure what instruments those numbers (and sounds) represent?

/*
if channel is 1, then instrument that equals 1 gives you "regular piano", 4 gives you "eerie xylo", etc. 
What instruments are actually represented? There's no such instrument as "eerie xylo"
*/
        firstMessage = new ShortMessage();
        firstMessage.setMessage(PROGRAM_CHANGE, channel, instrument, 80);
        MidiEvent instrumentPick = new MidiEvent(firstMessage, 0);
        track.add(noteOn);

Solution

  • The MIDI specification itself does not define program numbers. But almost all synthesizers support the sound set defined by the General MIDI Specification; the list on Wikipedia has useful links and descriptions.

    Most values in the MIDI protocol have 7 bits, so they are in the range from 0 to 127. To make counting easier for humans, they are often (but not always) documented starting at 1, i.e., in the range from 1 to 128.

    So your instruments are:
    1(Java) = 2(human) = Bright Piano
    2(Java) = 3(human) = Electric Grand Piano
    4(Java) = 5(human) = Electric Piano 1
    7(Java) = 8(human) = Clavinet

    I do not know what synthesizer your unknown JVM uses; the quality of instrument samples can vary.