MIDI控制器触发音频时非440Hz频率声音断续问题求助
问题分析与解决思路
你的代码里非440Hz频率声音断续,核心原因有两个:
1. 固定片段循环的相位断层
你在sound_play里预先生成1秒时长的波形样本,然后循环写入音频流。440Hz时,这段样本的结尾相位和开头几乎完全重合(计算出的结尾相位接近0),循环衔接无明显断层;但其他频率的样本首尾相位存在跳变,每次循环写入都会产生咔哒/断续感。
2. 多线程冲突风险
按下新MIDI按键时直接启动新线程,未停止之前可能还在运行的线程,多个线程同时向音频流写数据会导致声音混乱或断续。
具体修复步骤
步骤1:生成相位连续的循环波形
修改sound_play,生成整数个完整周期的波形样本,确保循环写入时首尾相位完全连续:
def sound_play(target_freq): # 计算单个周期的样本数,取最近整数 cycle_samples = round(fs / target_freq) # 生成单个周期的三角波(原代码写了三角波逻辑但用了正弦,这里修正) t = np.linspace(0, 2*np.pi, cycle_samples, endpoint=False) triangle = 2/np.pi * np.arcsin(np.sin(t)) # 转为带音量的float32格式样本 samples = (volume * triangle).astype(np.float32) while not stop_event.is_set(): stream.write(samples)
步骤2:确保单线程运行
处理MIDI按键按下事件时,先停止之前的线程再启动新线程,避免多线程冲突:
def play_sound(target_freq): global sound # 停止正在运行的旧线程 if 'sound' in globals() and sound.is_alive(): stop_event.set() sound.join() stop_event.clear() # 启动新线程并传入目标频率 sound = threading.Thread(target=sound_play, args=(target_freq,)) sound.start()
步骤3:修正MIDI消息处理逻辑
更新主循环,直接传递目标频率给play_sound,去掉依赖全局变量的逻辑:
while True: msg = midiin.get_message() if msg: message = msg[0] msg_type, note = message[0], message[1] print(message) if msg_type == 144: # 处理按键按下(Note On) if (msg_type, note) in frekvence_seznam: target_freq = frekvence_seznam[(msg_type, note)] play_sound(target_freq) elif note == 81: exit() elif msg_type == 128: # 处理按键松开(Note Off) if 'sound' in globals() and sound.is_alive(): stop_event.set() sound.join() stop_event.clear() print("konec zvuku")
步骤4:移除冗余全局变量
删除原代码中的frekvence全局变量,改用参数传递目标频率,避免全局变量引发的线程同步问题。
额外优化建议
- 改用PyAudio的回调模式替代线程循环写入,音频时序会更稳定,进一步降低断续风险。
- 给MIDI消息处理加入防抖逻辑,避免误触发多次线程启停。
内容的提问来源于stack exchange,提问作者Mr.novak
相关产品推荐
相关产品推荐

