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
相关产品推荐
相关产品推荐

