PyAudio 在线程中播放连续流并改变频率

PyAudio play continuous stream in a thread and let change the frequency

我完全没有线程经验。

我想做的就是播放声音并同时使用 GUI 改变音调(频率)。

此代码播放连续流,没有任何峰值或失真:


class Stream:
    def __init__(self, sample_rate):
        self.p = pyaudio.PyAudio()
        self.sample_rate = sample_rate

        # for paFloat32 sample values must be in range [-1.0, 1.0]
        self.stream = self.p.open(format=pyaudio.paFloat32,
                                  channels=1,
                                  rate=sample_rate,
                                  output=True)
        self.samples = 0.

    def create_sine_tone(self, frequency, duration):
        # generate samples, note conversion to float32 array
        self.samples = (np.sin(2 * np.pi * np.arange(self.sample_rate * duration) * frequency
                               / self.sample_rate)).astype(np.float32)

    def play_sine_tone(self, volume=1.):
        """
        :param frequency:
        :param duration:
        :param volume:
        :param sample_rate:
        :return:
        """

        # play. May repeat with different volume values (if done interactively)
        while 1:
            self.stream.write(volume * self.samples)

    def terminate(self):
        self.p.terminate()

    def finish(self):
        self.stream.stop_stream()
        self.stream.close()

此代码创建 GUI。在left_clickright_click 中,create_sine_tone() 创建了一个新的频率波。但是,据我了解,它修改了 threadingplay_sine_tone 中使用的内存,并且程序崩溃了。


def main():
    window = Tk()
    window.title("Piano reference")
    window.geometry('350x200')

    s = Stream(44100)

    lbl = Label(window, text="A4")
    lbl.grid(column=2, row=1)

    def left_click(frequency):
        s.create_sine_tone(frequency, 1.)
        t = threading.Thread(target=s.play_sine_tone, args=(1,))
        t.start()
        lbl.configure(text=frequency)

    def right_click(frequency):
        s.create_sine_tone(frequency, 1.)
        t = threading.Thread(target=s.play_sine_tone, args=(1,))
        t.start()
        lbl.configure(text=frequency)

    btn1 = Button(window, text="<<", command=lambda: left_click(100))
    btn2 = Button(window, text=">>", command=lambda: right_click(200))

    btn1.grid(column=0, row=0)
    btn2.grid(column=1, row=0)

    window.mainloop()

如何修改 wave 程序才不会崩溃?也许我可以在更改频率之前关闭线程?

我找不到答案。我知道的:

  1. 循环音中有"clicks"个。必须生成信号的完整周期
  2. 您可以将 streamcallback 一起使用,以便将线程移动到 PyAudio 的引擎盖下。使用它我能够运行程序以可接受的方式
  3. 我有一些关于创建两个线程对象的想法,它们将成为 Stream class 的字段。然后他们可以使用 2 个不同的流和线程来启动和停止。虽然我无法让它工作 - 我认为线程没有等待 finish()

如果您只想播放可以使用 GUI 控制的不同音调,则可能不需要线程。

PySimpleGUI 提供了一个超级易用的基于 Tkinter(和其他工具)的 GUI 生成器。最重要的是,它提供了基于由 GUI 组件驱动的事件的操作。

另一方面,使用 pydub 可以让我们轻松地创建不同的音调并播放它们。 pydub _play_with_simpleaudio 方法允许我们以非阻塞方式使用 simpleAudio 播放音调。

GUI 控件:

  • '>>'以 200 Hz 的倍数选择下一个频率。

  • '<<' 选择之前的频率,以 100 Hz 的倍数表示。

  • 'X' 退出图形界面。

我观察到的唯一问题是频率上有轻微的咔嗒声 shift.That 可能需要进一步改进。

以下工作代码基于上述包。

import PySimpleGUI as sg      
from pydub.generators import Sine
from pydub import AudioSegment
from pydub.playback import _play_with_simpleaudio
import time

sr = 44100  # sample rate
bd = 16     # bit depth
l  = 10000.0     # duration in millisec

sg.ChangeLookAndFeel('BluePurple')
silent = AudioSegment.silent(duration=10000)
FREQ = 200

def get_sine(freq):
  #create sine wave of given freq
  sine_wave = Sine(freq, sample_rate=sr, bit_depth=bd)

  #Convert waveform to audio_segment for playback and export
  sine_segment = sine_wave.to_audio_segment(duration=l)

  return sine_segment

# Very basic window.  Return values as a list      
layout = [
              [sg.Button('<<'), sg.Button('>>')],
              [sg.Text('Processing Freq [Hz]:'), sg.Text(size=(15,1), justification='center', key='-OUTPUT-')]
          ]

window = sg.Window('Piano reference', layout)

count = 0
play_obj = _play_with_simpleaudio(silent)

while 100 <= FREQ <= 20000 :  # Event Loop
    count += 1
    event, values = window.Read()

    if event in  (None, 'Exit'):
        break
    if event == '<<':
      if not FREQ < 100:
        FREQ -= 100
        window['-OUTPUT-'].update(FREQ)

    if event == '>>':
      if not FREQ > 20000:
        FREQ += 200
        window['-OUTPUT-'].update(FREQ)

    print(event, FREQ)

    sound = get_sine(FREQ)

    try:
      play_obj.stop()
      time.sleep(0.1)
      sound = sound.fade_in(100).fade_out(100)
      play_obj = _play_with_simpleaudio(sound)
      time.sleep(0.1)
    except KeyboardInterrupt:
      play_obj.stop_all()


window.close()

结果:

$ python3 pygui3.py 
Playing >> 400 Hz
Playing >> 600 Hz
Playing >> 800 Hz
Playing << 700 Hz
Playing << 600 Hz

图形用户界面: