Python实现键盘按键触发多正弦波同时播放的问题求助
多音调同时播放的Python按键发声程序解决方案
问题背景
编程基础薄弱,为大学研讨课开发Python按键触发发声程序,使用PyCharm实现,但当前按下"A"或"S"键只能依次播放对应音调,必须等待当前播放结束才能触发下一个,无法实现双音调同时叠加播放,需要修改代码实现多音同步输出。
原代码
import pyaudio import numpy as np import math import struct import keyboard FS = 44100 # frames per second, samples per second or sample rate def play_sound(type, frequency, volume, duration): generate_sound(type, frequency, volume, duration) def generate_sound(type, frequency, volume, duration): outbuf = np.random.normal(loc=0, scale=1, size=int(float(duration / 1000.0)*FS)) if type == "sine": dur = int(FS * float(duration / 1000.0)) theta = 0.0 incr_theta = frequency * 2 * math.pi / FS # frequency increment normalized for sample rate for i in range(dur): outbuf[i] = volume * math.sin(theta) theta += incr_theta p = pyaudio.PyAudio() stream = p.open(format=pyaudio.paFloat32, channels=1, rate=FS, output=True) data = b''.join(struct.pack('f', samp) for samp in outbuf) # must pack the binary data stream.write(data) stream.stop_stream() stream.close() p.terminate() #thread here maybe? while True: if keyboard.is_pressed("a"): play_sound("sine",200, 0.5, 3000) if keyboard.is_pressed("s"): play_sound("sine",300,0.5,3000) elif keyboard.is_pressed("esc"): break
解决方案
原代码的核心问题是每次播放都创建独立的PyAudio实例和流,且stream.write()是阻塞操作,导致无法同时输出多音调。要实现多音叠加,需要改为单流持续输出+实时波形叠加的模式,具体修改如下:
修改后完整代码
import pyaudio import math import struct import keyboard import threading import time FS = 44100 # 采样率 VOLUME = 0.3 # 全局音量,避免叠加后爆音 CHUNK_SIZE = 1024 # 每次输出的音频块大小 # 保存当前激活的音符(频率) active_notes = set() # 每个音符的相位跟踪,避免切换时波形断层 note_phases = {} def generate_sine_sample(frequency, phase): """生成单个采样点的正弦波值""" sample = VOLUME * math.sin(phase) # 更新相位 new_phase = phase + (frequency * 2 * math.pi / FS) # 相位取模,防止数值过大 return sample, new_phase % (2 * math.pi) def audio_loop(): """持续输出叠加后的音频流""" p = pyaudio.PyAudio() # 打开持续输出的流 stream = p.open( format=pyaudio.paFloat32, channels=1, rate=FS, output=True, frames_per_buffer=CHUNK_SIZE ) while True: chunk = [] for _ in range(CHUNK_SIZE): sample = 0.0 # 叠加所有激活音符的采样 for freq in list(active_notes): # 初始化相位(如果是新音符) if freq not in note_phases: note_phases[freq] = 0.0 s, new_phase = generate_sine_sample(freq, note_phases[freq]) sample += s note_phases[freq] = new_phase # 叠加后做限幅,防止音量过大失真 sample = max(min(sample, 1.0), -1.0) chunk.append(sample) # 打包成二进制数据写入流 data = b''.join(struct.pack('f', samp) for samp in chunk) stream.write(data) # 短暂休眠,降低CPU占用 time.sleep(0.001) def main(): # 启动音频输出线程 audio_thread = threading.Thread(target=audio_loop, daemon=True) audio_thread.start() # 按键状态跟踪,避免重复触发 key_states = {"a": False, "s": False} print("按下A/S键播放音调,ESC键退出") while True: # 检测A键:200Hz if keyboard.is_pressed("a"): if not key_states["a"]: active_notes.add(200) key_states["a"] = True else: if key_states["a"]: active_notes.discard(200) note_phases.pop(200, None) key_states["a"] = False # 检测S键:300Hz if keyboard.is_pressed("s"): if not key_states["s"]: active_notes.add(300) key_states["s"] = True else: if key_states["s"]: active_notes.discard(300) note_phases.pop(300, None) key_states["s"] = False # 退出程序 if keyboard.is_pressed("esc"): break if __name__ == "__main__": main()
关键修改说明
- 线程分离:用
threading把音频输出循环放到后台线程,主线程专注于按键检测,避免阻塞。 - 实时波形叠加:维护
active_notes集合保存当前按下的音符,每次生成音频块时叠加所有激活音符的采样值。 - 相位跟踪:用
note_phases字典记录每个音符的当前相位,避免按键松开再按下时波形出现断层。 - 限幅处理:叠加后的采样值做
max(min(sample, 1.0), -1.0)处理,防止多个音调叠加导致音量过大失真。 - 按键状态跟踪:用
key_states避免按键按住时重复添加音符到集合中。
内容的提问来源于stack exchange,提问作者BeaNatural
相关产品推荐
相关产品推荐

