You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java使用MIDI编写播放程序仅能短暂发声问题排查求助

Java MIDI程序音符播放异常修复方案

问题根因

  • Java javax.sound.midi.Receiver 提供的send()方法第二个参数为MIDI时间戳,绝大多数本地默认MIDI合成器/接收器不支持基于该参数的延迟调度。该参数的标准默认用法是传 -1 表示立即发送消息,你传入的未来时间戳会被直接忽略,导致NOTE_ON和对应的NOTE_OFF消息被连续立即发送,因此音符只会发出极短的声响就被关闭。
  • 原有逻辑将时序调度交给不支持对应能力的MIDI接收器,因此无法按预期控制音符播放时长。

修复方案

自己实现事件时序控制,先收集所有NOTE_ON、NOTE_OFF事件按触发时间排序,再按时间间隔依次发送,所有消息的时间戳参数统一传-1。

修复后可运行代码

package application;

import javax.sound.midi.InvalidMidiDataException;
import javax.sound.midi.MidiSystem;
import javax.sound.midi.MidiUnavailableException;
import javax.sound.midi.ShortMessage;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class App {
    // 自定义MIDI事件类,存储消息和触发时间(单位:毫秒)
    static class MidiEvent {
        ShortMessage msg;
        long triggerTimeMs;

        public MidiEvent(ShortMessage msg, long triggerTimeMs) {
            this.msg = msg;
            this.triggerTimeMs = triggerTimeMs;
        }
    }

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

        List<MidiEvent> events = new ArrayList<>();
        for (int i = 0; i < notes.length; i++) {
            int note = notes[i];
            int time = times[i];
            events.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_ON, 0, note, 127), time));
            events.add(new MidiEvent(new ShortMessage(ShortMessage.NOTE_OFF, 0, note, 127), time + 1000));
        }

        // 按触发时间对所有事件排序
        events.sort(Comparator.comparingLong(e -> e.triggerTimeMs));

        long lastEventTime = 0;
        for (MidiEvent event : events) {
            long delay = event.triggerTimeMs - lastEventTime;
            if (delay > 0) {
                Thread.sleep(delay);
            }
            receiver.send(event.msg, -1);
            lastEventTime = event.triggerTimeMs;
        }

        // 等待最后一个音符播放完成后释放资源
        Thread.sleep(1000);
        receiver.close();
    }
}

注意事项

  • 操作完成后必须调用receiver.close()释放MIDI设备资源,避免部分系统出现音频设备占用无法释放的问题。
  • 如果你确实需要使用MIDI时间戳调度能力,需先调用接收器的getMicrosecondPosition()方法获取当前时间基准,所有事件的时间戳都要基于该基准计算,仅当方法返回值不为-1时才说明当前设备支持时间戳调度,否则不要使用该参数。

内容的提问来源于stack exchange,提问作者Matei Anghel

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 15:15:03