javaswinganimationmidijslider

Problem animating Java Swing slider in time with midi sequencer


I'm building a simple drum machine app and wanted to try animating a slider to move in rhythm with the beat to show which 16th note of the bar the drum machine is on.

I've set up a sequencer that runs in a 16 beat loop with the javax.sound.midi api. The sound aspect of the program is working fine, but I want my slider at the bottom of the screen to cycle though the beats with what I'm hearing.

When I implemented a changeListener to the code the slider's position only updates when I click the slider with my mouse.

I've tried using the both slider's and the JPanels's updateUI, repaint, revalidate methods but nothing changes.

Is it actually possible to animate GUI elements in this way?

Im using the swing API

        tickDisplaySlider.addChangeListener(e -> {
            tickDisplaySlider.setValue((int)sequencer.getTickPosition());
            tickDisplaySlider.repaint();
            System.out.println(Thread.currentThread() + " is running");
        });

Solution

  • To make sure UI moves in sync with the music make the Java sequencer fire events every 16th note. To achieve that add your own Midi Controller messages every 16th note in your sequence.

    Register the sequencer to get notified of these controller messages during playback. Then the event handler can update the slider on the Event Dispatching Thread.

    Here is the code:

    // Create the sequence
    // PPQ = ticks per quarter
    // 100 ticks per quarter means 25 ticks for a 16th note
    Sequence sequence = new Sequence(Sequence.PPQ, 100);
    
    // .........
    
    // Fill the track with your MidiEvents
    Track track = sequence.createTrack();
    // track.add(MidiEvent(....));
    
    // .........
    
    // Then add your special controller messages every 16th note
    // Let's assume sequence length is 1 measure = 4 beats = 16 x 16th notes
    for (int i = 0; i < 16; i++)
    {
        ShortMessage msg = new ShortMessage();
        // 176=control change type
        // 0=Midi channel 0
        // 110=an undefined control change code I choose to use for 16th notes
        // 0=unused in this case
        msg.setMessage(176, 0, 110, 0);
    
        // Create the event at a 16th note position
        long tickPosition = i * 25;
        MidiEvent me = new MidiEvent(msg, tickPosition);
    
        // Add the event to the track
        track.add(me);
    }
    
    
    // Register the sequencer so that when you start playback you get notified 
    // of controller messages 110 
    int[] controllers = new int[]  { 110 };
    sequencer.addControllerEventListener(shortMessage -> updateSlider(shortMessage), controllers);
    
    // .........    
    
    // Process event to update the slider on the Swing Event Dispatching Thread
    void updateSlider(ShortMessage sm)
    {
        // Convert tick position to slider value
        double percentage = sequencer.getTickPosition() / (double) sequencer.getTickLength();
        int sliderPosition = (int) (slider.getMinimum() + (slider.getMaximum() - slider.getMinimum()) * percentage);
    
        // Important: sequencer fire events outside of the EDT
        SwingUtilities.invokeLater(() -> slider.setValue(sliderPosition));
    }