Search code examples
javaswingmidijavasound

Using the MIDI tick position to control movement of a graphic marker in a sequence


I'm trying to assemble a graphical representation of a MIDI sequence, and I'd like a vertical line to move across the panel horizontally as the sequence plays, reflecting the actual position in the sequence. I know that I should use something like getTickPosition() to provide the location of the vertical line.

But how do I fire off these events so that the vertical line knows to redraw itself? Do I create a special listener that the ticks somehow trigger?


Solution

  • Establish a Swing Timer to check and update the tick position every NN milliseconds.

    So, there's not any kind of built-in timer in the MidiSystem?

    Sure there is. But the point is that all GUI updates should happen on the EDT. By invoking them from a Swing Timer, they are. Also, the MIDI timer is for the use of MIDI API/System, let it do what it does undisturbed, and report back the relevant information when checked from the Swing Timer.

    Also, given the nature of the UI component, look to a JProgressBar as seen in the upper right of this GUI.

    DukeBox GUI

    Update

    I adapted the source seen on the Java Sound WIKI into an SSCCE of this approach.

    Play MIDI showing progress

    import javax.sound.midi.*;
    import javax.swing.*;
    import java.awt.event.*;
    import java.net.URL;
    
    class PlayMidi {
    
        public static void main(String[] args) throws Exception {
            URL url = new URL("http://pscode.org/media/EverLove.mid");
            Sequence sequence = MidiSystem.getSequence(url);
            final Sequencer sequencer = MidiSystem.getSequencer();
            sequencer.open();
            sequencer.setSequence(sequence);
            Runnable r = new Runnable() {
                public void run() {
                    final JProgressBar progress = new JProgressBar(0,(int)sequencer.getMicrosecondLength()); 
                    ActionListener updateListener = new ActionListener(){
                        public void actionPerformed(ActionEvent arg0) {
                            progress.setValue((int)sequencer.getMicrosecondPosition());
                        }
                    };
                    Timer timer = new Timer(40,updateListener); 
                    sequencer.start();
                    timer.start();
                    JOptionPane.showMessageDialog(null, progress);
                    sequencer.close();
                    timer.stop();
                }
            };
            SwingUtilities.invokeLater(r);
        }
    }