0

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");
        });
NMard
  • 25
  • 7
  • 3
    For better help sooner, [edit] to add a [MCVE] or [Short, Self Contained, Correct Example](http://www.sscce.org/). First try and animate a slider without any involvement of MIDI / Java Sound. BTW - I used a `JProgressBar` in a Java based MP3 player app. and added a `MouseListener` to allow the user to jump to different parts of the track. – Andrew Thompson Jun 10 '20 at 10:24
  • 2
    Where in your code do you call `setValue(...)` on the slider? That is how you change its value, but without calling this explicitly, no change will magically happen on its own. Likely you will need a SwingWorker that plays the music, and that itself has a PropertyChangeListener attached that will change your slider's value based on the music's location. I agree with @AndrewThompson that you will want to simplify your code and your problem and create your [mre] to go with your question. – DontKnowMuchBut Getting Better Jun 10 '20 at 10:36

2 Answers2

1

the slider's position only updates when I click the slider with my mouse.

   tickDisplay.addChangeListener(e -> {
        tickDisplay.setValue((int)sequencer.getTickPosition());

The point of using a ChangeListener is that it will generate an event when the user changes the slider.

You don't want to listen for a change event on the slider, you need to listen for a change event on the "sequencer". Then when the sequencer generates an event you update the slider. I don't know the sound API so you will need to read the API documentation to see what listeners can be used.

If the sequencer does not generate an event then you could use a Swing Timer to poll the sequencer. You set up the Timer to generate an event at a specified interval. When the Timer fires you get the sequencer tick position and then update your slider.

Read the section from the Swing tutorial on How to Use Timers for more information.

camickr
  • 321,443
  • 19
  • 166
  • 288
1

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));
}
jjazzboss
  • 1,261
  • 8
  • 14