Search code examples
javajava-16javax.sound.midi

Using MIDI in Java


I was experimenting trying to write a Java program that uses MIDI, so the program has to array: notes (which contains all the notes that I want to play) and another array times (which specify when the note should be play) the notes and times are grouped three at a time so I can have multiple chords, the problem is that the program only plays a very brief note and then it stops, what am I doing wrong? Bellow is the code, I am using Java 16.

package application;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.ShortMessage;

public class App {

    public static void main(String[] args)
            throws MidiUnavailableException, InvalidMidiDataException, InterruptedException {

        var receiver = MidiSystem.getReceiver();

        int[] notes = { 60, 64, 67, 60, 65, 67, 55, 59, 62, 55, 60, 62, 53, 57, 60, 53, 58, 60 };
        int[] times = { 0, 0, 0, 1000, 1000, 1000, 2000, 2000, 2000, 3000, 3000, 3000, 4000, 4000, 4000, 5000, 5000,
                5000 };

        for (int i = 0; i < notes.length; i++) {

            int note = notes[i];
            int time = times[i];
            System.out.println(note + ":" + time);
            receiver.send(new ShortMessage(ShortMessage.NOTE_ON, 0, note, 127), time * 1000);
            receiver.send(new ShortMessage(ShortMessage.NOTE_OFF, 0, note, 127), (time + 1000) * 1000);
        }

        Thread.sleep(7000);
    }

}

I think it has something to do with ShortMessage.NOTE_OFF but I am not sure and I can't figure it out.


Solution

  • The problem has most likely to do with your times array.

    Taken from the oracle java documentation on sequancers

    As previously mentioned, the program can include a time stamp with each MIDI message it sends to the device's receiver. However, such time stamps are used only for fine-tuning the timing, to correct for processing latency. The caller can't generally set arbitrary time stamps; the time value passed to Receiver.send must be close to the present time, or the receiving device might not be able to schedule the message correctly. This means that if an application program wanted to create a queue of MIDI messages for an entire piece of music ahead of time (instead of creating each message in response to a real-time event), it would have to be very careful to schedule each invocation of Receiver.send for nearly the right time.

    Fortunately, most application programs don't have to be concerned with such scheduling. Instead of invoking Receiver.send itself, a program can use a Sequencer object to manage the queue of MIDI messages for it.

    You are blindly assuming that the numbers (1000, 2000, 3000... etc.) is milliseconds, but defined in the api this is defined as ticks.

    Like in any music you need to define the subdivisions, and the pulse per ticks (PPQ) for instance quarter notes, 16th notes etc. etc.

    In java this is usually done by defining a sequencer, and with these settings, and then creating a track on said sequencer, and then playing notes on that track.

    Here is an example i found on the internet.

    public class MyMidiPlayer {
     
        public static void main(String[] args) {
     
            System.out.println("Enter the number of notes to be played: ");
            Scanner in = new Scanner(System.in);
            int numOfNotes = in.nextInt();
     
            MyMidiPlayer player = new MyMidiPlayer();
            player.setUpPlayer(numOfNotes);
        }
     
        public void setUpPlayer(int numOfNotes) {
     
            try {
     
                // A static method of MidiSystem that returns
                // a sequencer instance.
                Sequencer sequencer = MidiSystem.getSequencer();
                sequencer.open();
     
                // Creating a sequence.
                Sequence sequence = new Sequence(Sequence.PPQ, 4);
     
                // PPQ(Pulse per ticks) is used to specify timing
                // type and 4 is the timing resolution.
     
                // Creating a track on our sequence upon which
                // MIDI events would be placed
                Track track = sequence.createTrack();
     
                    // Adding some events to the track
                for (int i = 5; i < (4 * numOfNotes) + 5; i += 4){
     
                    // Add Note On event
                    track.add(makeEvent(144, 1, i, 100, i));
     
                    // Add Note Off event
                    track.add(makeEvent(128, 1, i, 100, i + 2));
                }
     
                // Setting our sequence so that the sequencer can
                // run it on synthesizer
                sequencer.setSequence(sequence);
     
                // Specifies the beat rate in beats per minute.
                sequencer.setTempoInBPM(220);
     
                // Sequencer starts to play notes
                sequencer.start();
     
                while (true) {
     
                    // Exit the program when sequencer has stopped playing.
                    if (!sequencer.isRunning()) {
                        sequencer.close();
                        System.exit(1);
                    }
                }
            }
            catch (Exception ex) {
     
                ex.printStackTrace();
            }
        }
     
        public MidiEvent makeEvent(int command, int channel,
                                   int note, int velocity, int tick) {
     
            MidiEvent event = null;
     
            try {
     
                // ShortMessage stores a note as command type, channel,
                // instrument it has to be played on and its speed.
                ShortMessage a = new ShortMessage();
                a.setMessage(command, channel, note, velocity);
     
                // A midi event is comprised of a short message(representing
                // a note) and the tick at which that note has to be played
                event = new MidiEvent(a, tick);
            }
            catch (Exception ex) {
     
                ex.printStackTrace();
            }
            return event;
        }
    }