动画问题 Java 使用 MIDI 音序器及时摆动滑块

Problem animating Java Swing slider in time with midi sequencer

我正在构建一个简单的鼓机应用程序,并想尝试为滑块设置动画以随着节拍的节奏移动以显示鼓机在小节的第 16 个音符上。

我设置了一个音序器,它使用 javax.sound.midi api 在 16 拍循环中运行。该程序的声音方面工作正常,但我希望屏幕底部的滑块能够随着我听到的节拍循环。

当我对代码实施 changeListener 时,滑块的位置仅在我用鼠标单击滑块时更新。

我试过使用滑块和 JPanel 的 updateUI、重绘、重新验证方法,但没有任何变化。

是否真的可以用这种方式为 GUI 元素设置动画?

我在用秋千API

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

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

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

使用 ChangeListener 的要点是当用户改变滑块时它会产生一个事件。

您不想监听滑块上的更改事件,您需要监听 "sequencer" 上的更改事件。然后,当音序器生成一个事件时,您更新滑块。我不知道 API 的声音,因此您需要阅读 API 文档以了解可以使用哪些侦听器。

如果定序器不生成事件,那么您可以使用 Swing 定时器轮询定序器。您设置计时器以在指定的时间间隔生成事件。当计时器触发时,您将获得音序器节拍位置,然后更新您的滑块。

阅读 How to Use Timers 上的 Swing 教程部分了解更多信息。

为了确保 UI 移动与音乐同步,使 Java 音序器每 16 个音符触发一次事件。要实现这一点,请在序列中每第 16 个音符添加您自己的 Midi 控制器消息。

注册音序器以在播放期间收到这些控制器消息的通知。然后事件处理程序可以更新事件调度线程上的滑块。

代码如下:

// 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));
}