如何实时接收电子钢琴的Live MIDI输入事件?
问题分析
你添加的ControllerEventListener仅监听MIDI控制器变更事件(CC消息,比如编号127的控制器消息),但电子钢琴按键触发的是Note On/Note Off这类MIDI短消息,这是监听器未生效的核心原因。
有效解决方案
以下提供两种实时捕获MIDI乐器事件的可行方法:
方案1:直接监听MidiDevice输入(最直接的实时处理)
跳过Sequencer,直接给输入设备绑定自定义Receiver,可捕获所有从电子钢琴发来的MIDI消息,适合实时对比需求:
import java.io.File; import java.io.IOException; import javax.sound.midi.*; import javax.swing.Timer; public class Main { public static void main(String[] args) throws Exception { // 列出所有MIDI设备,确认目标输入设备索引 Info[] infos = MidiSystem.getMidiDeviceInfo(); for (int i = 0; i < infos.length; i++) { System.out.println(i + ": " + infos[i].getName() + " - " + infos[i].getDescription()); } // 打开电子钢琴输入设备(根据实际列表调整索引) MidiDevice inputDevice = MidiSystem.getMidiDevice(infos[1]); inputDevice.open(); // 自定义Receiver,实时处理所有MIDI消息 Receiver customReceiver = new Receiver() { @Override public void send(MidiMessage message, long timeStamp) { if (message instanceof ShortMessage) { ShortMessage sm = (ShortMessage) message; switch (sm.getCommand()) { case ShortMessage.NOTE_ON: System.out.printf("按键按下: 通道%d, 音符%d, 力度%d%n", sm.getChannel(), sm.getData1(), sm.getData2()); // 在这里添加与乐谱MIDI文件的实时对比逻辑 break; case ShortMessage.NOTE_OFF: System.out.printf("按键松开: 通道%d, 音符%d%n", sm.getChannel(), sm.getData1()); break; case ShortMessage.CONTROL_CHANGE: System.out.printf("控制器变更: 通道%d, 编号%d, 值%d%n", sm.getChannel(), sm.getData1(), sm.getData2()); break; // 可扩展处理其他MIDI消息类型 } } } @Override public void close() {} }; // 将输入设备的Transmitter绑定到自定义Receiver Transmitter transmitter = inputDevice.getTransmitter(); transmitter.setReceiver(customReceiver); // 保留原有录制功能(如果需要) Sequencer sequencer = MidiSystem.getSequencer(); sequencer.open(); Sequence seq = new Sequence(Sequence.PPQ, 24); Track track = seq.createTrack(); sequencer.setSequence(seq); sequencer.recordEnable(track, -1); sequencer.startRecording(); // 10秒后停止录制并清理资源 new Timer(10000, e -> { sequencer.stopRecording(); try { MidiSystem.write(sequencer.getSequence(), 0, new File("midi.mid")); } catch (IOException e1) { e1.printStackTrace(); } sequencer.close(); inputDevice.close(); System.exit(0); }).start(); } }
方案2:结合Sequencer的事件监听(适合需同步录制的场景)
如果必须通过Sequencer处理,可通过监听Track的事件或使用MetaEventListener,但需注意:Sequencer默认仅向ControllerEventListener传递CC消息,若要捕获Note事件,需额外处理:
// 在原有录制代码基础上,添加Track事件监听 Track currentTrack = seq.createTrack(); // 手动监听Track的新增事件(通过定时扫描或自定义Track实现) new Timer(10, e -> { for (int i = currentTrack.size() - 1; i >= 0; i--) { MidiEvent event = currentTrack.get(i); MidiMessage msg = event.getMessage(); if (msg instanceof ShortMessage) { ShortMessage sm = (ShortMessage) msg; // 处理Note On/Off等消息 if (sm.getCommand() == ShortMessage.NOTE_ON) { System.out.println("录制到按键按下: " + sm.getData1()); } } } }).start();
注意事项
- 确认
infos[1]是正确的电子钢琴输入设备,可根据控制台打印的设备列表调整索引。 - 树莓派运行时,需确保ALSA MIDI驱动正常,若遇设备识别问题,可添加系统属性配置:
-Djavax.sound.midi.Receiver=com.sun.media.sound.MidiOutDeviceProvider。 - 程序结束时务必关闭MidiDevice和Sequencer,避免资源泄漏。
内容的提问来源于stack exchange,提问作者Crih.exe
相关产品推荐
相关产品推荐

