
本文详解 Mido 库中 MIDI 输入端口的正确监听方式,指出阻塞式 for msg in port: 循环在交互式场景中的局限性,并提供基于回调函数的可靠解决方案,附完整可运行代码与关键注意事项。
本文详解 mido 库中 midi 输入端口的正确监听方式,指出阻塞式 `for msg in port:` 循环在交互式场景中的局限性,并提供基于回调函数的可靠解决方案,附完整可运行代码与关键注意事项。
在使用 Mido 库监听物理 MIDI 键盘时,初学者常误用 for msg in port: 这一阻塞迭代方式——它会永久挂起当前线程,等待首个消息到达,而后续调用(如 collect_note_events(port) 在 while True 中反复执行)不仅逻辑冗余,更会导致程序“假死”:既不响应按键,也无法及时捕获 KeyboardInterrupt(Ctrl+C),因此看不到 "Exiting..." 输出。
根本原因在于:mido.Input 对象的迭代器是单次、阻塞且不可重入的。一旦进入 for msg in port:,它将持续等待,直到端口关闭或异常终止;将其置于循环内会导致每次调用都卡住,无法实现持续监听。
✅ 正确做法是启用异步回调机制:通过 callback= 参数注册一个函数,Mido 会在后台线程中自动将每条 MIDI 消息推送给该函数。这种方式非阻塞、响应及时,且天然兼容主程序流程控制。
以下是修正后的完整示例代码:
import mido
import time
# 全局列表用于累积音符事件(注意线程安全,此处为简化示例)
note_events = []
def on_midi_message(message):
"""MIDI 消息回调函数"""
if message.type == 'note_on' and message.velocity > 0: # 忽略 note_off 和静音音符
note_events.append({
'note': message.note,
'velocity': message.velocity,
'time': time.time()
})
print(f"→ Note ON: {message.note} (vel={message.velocity})")
def main():
print("Available MIDI input ports:")
for i, name in enumerate(mido.get_input_names()):
print(f" {i}: {name}")
if not mido.get_input_names():
print("❌ No MIDI input ports found. Please check your keyboard connection and drivers.")
return
selected_port = input("\nEnter the exact name of your MIDI input port: ").strip()
try:
# ✅ 关键:使用 callback 启动监听
with mido.open_input(selected_port, callback=on_midi_message) as port:
print(f"\n✅ Listening on '{selected_port}'. Play notes now.")
print("Press Ctrl+C to stop and display summary.\n")
# 主线程保持活跃,仅用于响应中断
while True:
time.sleep(0.1) # 轻量休眠,避免空转占用 CPU
except KeyboardInterrupt:
print("\n\n⏹️ Stopped listening.")
print(f"? Collected {len(note_events)} note-on events:")
for evt in note_events[:10]: # 仅显示前10条,避免刷屏
print(f" Note {evt['note']} at {evt['time']:.3f}s (vel {evt['velocity']})")
if len(note_events) > 10:
print(f" ... and {len(note_events) - 10} more")
except OSError as e:
print(f"❌ Port error: {e}. Verify the port name matches exactly (case-sensitive).")
if __name__ == "__main__":
main()? 关键注意事项:
- 端口名称必须完全匹配:调用 mido.get_input_names() 获取的名称是精确且区分大小写的,复制粘贴最安全;Windows 常见如 'MPK249 MIDI In',macOS 可能含 'IAC Driver',Linux 多为 'USB MIDI Interface:0'。
- 回调函数需轻量:避免在 on_midi_message 中执行耗时操作(如文件写入、复杂计算),否则可能丢消息;如需持久化,建议用线程安全队列(如 queue.Queue)中转。
- note_on 与 note_off 区分:多数键盘发送 note_on(velocity=0)模拟关音,代码中已过滤 velocity=0 的情况,确保只记录真实触发音符。
- 权限与驱动:macOS/Linux 用户需确保有 USB 设备访问权限;Windows 用户若无设备,请安装对应 MIDI 驱动(如 Roland/Novation 官方驱动或通用 LoopBe1/VirtualMIDISynth)。
掌握这一回调模式后,你即可稳定接入实时 MIDI 流,为后续与 .mid 文件音符序列比对、错音检测、实时反馈等高级功能打下坚实基础。

















