无需声明时长的音频播放方案:实现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("资源已释放")
关键改动说明
新增Note Off处理:
- 监听MIDI的
128状态(标准Note Off),以及144且velocity=0的情况(部分设备用这个表示松开) - 触发时直接把
is_playing设为False,终止播放循环
- 监听MIDI的
实时生成音频块:
- 不再预先生成固定时长的完整样本,而是每次生成
block_size个样本(这里设为1024,可根据响应速度调整) - 循环写入音频流,只要
is_playing为True就持续生成,一旦变为False立刻停止
- 不再预先生成固定时长的完整样本,而是每次生成
全局状态管理:
- 用
is_playing跟踪播放状态,current_freq记录当前播放的频率 - 避免重复创建音频流,只有在开始播放时创建,停止时关闭
- 用
资源清理:
- 用
try-finally确保程序退出时一定会释放PyAudio和MIDI资源,避免内存泄漏
- 用
内容的提问来源于stack exchange,提问作者Tom hala
相关产品推荐
相关产品推荐

