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

无需声明时长的音频播放方案:实现MIDI按键启停音频

实现MIDI按键松开停止音频播放的解决方案

核心问题拆解

你当前的代码有几个关键问题导致没法实现按键松开停止:

  • 只处理了MIDI的**Note On(144)按键按下消息,完全没处理Note Off(128)**按键松开消息,根本没触发停止的入口
  • 预先生成了固定时长的完整音频样本,一次性写入PyAudio流,写完才会停止,中途没法打断
  • 每次按键都新建一个音频流,资源管理混乱,也不利于实时控制

修复后的完整代码

import time
from rtmidi.midiutil import open_midiinput
import numpy as np
import pyaudio

# 初始化全局变量
p = pyaudio.PyAudio()
is_playing = False
current_freq = 440.0
stream = None

# 音频参数
volume = 0.5  # 音量范围 [0.0, 1.0]
fs = 44100    # 采样率,Hz
block_size = 1024  # 每次生成的音频块大小,越小响应越快

# 音符频率映射
freq_map = {
    32: 440.0,    # A
    33: 493.88,   # B
    34: 523.25,   # C
    35: 587.33    # D
}

# 初始化MIDI输入
midiin, port_name = open_midiinput(0)
print(f"已连接MIDI端口: {port_name}")

def generate_audio_block(freq, block_size, fs):
    """实时生成单块音频样本(三角波)"""
    t = np.arange(block_size) / fs
    x = 2 * np.pi * freq * t
    # 生成三角波,你可以替换成其他波形(正弦/square/saw等)
    triangle = 2/np.pi * np.arcsin(np.sin(x))
    return (volume * triangle).astype(np.float32)

try:
    while True:
        msg = midiin.get_message()
        if msg:
            message, _ = msg
            status, note, velocity = message
            
            # 处理Note On(按键按下)
            if status == 144 and velocity > 0:
                if note in freq_map and not is_playing:
                    current_freq = freq_map[note]
                    is_playing = True
                    # 创建音频流
                    stream = p.open(
                        format=pyaudio.paFloat32,
                        channels=1,
                        rate=fs,
                        output=True
                    )
                    print(f"开始播放音符: {note}, 频率: {current_freq}")
            
            # 处理Note Off(按键松开)
            elif (status == 128) or (status == 144 and velocity == 0):
                if note in freq_map and is_playing:
                    is_playing = False
                    print(f"停止播放音符: {note}")
            
            # 退出指令(原代码的81号键)
            elif status == 144 and note == 81:
                break
        
        # 实时播放逻辑:如果正在播放,持续生成并写入音频块
        if is_playing and stream is not None:
            audio_block = generate_audio_block(current_freq, block_size, fs)
            stream.write(audio_block.tobytes())
            
        time.sleep(0.001)  # 降低CPU占用

finally:
    # 清理资源
    if stream is not None:
        stream.stop_stream()
        stream.close()
    p.terminate()
    midiin.close_port()
    print("资源已释放")

关键改动说明

  1. 新增Note Off处理:

    • 监听MIDI的128状态(标准Note Off),以及144且velocity=0的情况(部分设备用这个表示松开)
    • 触发时直接把is_playing设为False,终止播放循环
  2. 实时生成音频块:

    • 不再预先生成固定时长的完整样本,而是每次生成block_size个样本(这里设为1024,可根据响应速度调整)
    • 循环写入音频流,只要is_playing为True就持续生成,一旦变为False立刻停止
  3. 全局状态管理:

    • 用is_playing跟踪播放状态,current_freq记录当前播放的频率
    • 避免重复创建音频流,只有在开始播放时创建,停止时关闭
  4. 资源清理:

    • 用try-finally确保程序退出时一定会释放PyAudio和MIDI资源,避免内存泄漏

内容的提问来源于stack exchange,提问作者Tom hala

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 04:10:26