【问题标题】:PyAudio play continuous stream in a thread and let change the frequencyPyAudio 在一个线程中播放连续流并让改变频率
【发布时间】:2019-12-02 11:01:38
【问题描述】:

我根本没有线程方面的经验。

我想要做的就是播放声音,同时能够使用 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 以使程序不会崩溃?也许我可以在更改频率之前关闭线程?

【问题讨论】:

  • 您在正确的轨道上,您可以在选择新频率后和播放新频率之前关闭现有线程播放声音。或者,根本不要使用线程并跟踪峰值,让现有频率在开始新频率之前完成其峰值。这样您就不会在频移时听到点击声。

标签: python audio python-multithreading pyaudio


【解决方案1】:

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

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

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

GUI 控件:

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

  • '

  • 'X' 退出 gui。

我观察到的唯一问题是频移时有轻微的点击声。这可能需要进一步的工作。

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

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

图形界面:

【讨论】:

  • 我不确定这是我问题的答案,但它解决了我的问题。下次我会寻找更好的工具,而不是与几乎可以工作的代码作斗争太长时间。
  • 我能够移除睡眠并且它没有任何问题。它对点击没有帮助。这些似乎来自“停止”呼叫,因为如果我删除停止呼叫,我就听不到咔嗒声。所有的音调都在自己之上播放,但它们每个都没有点击就开始了。我还尝试不使用淡入淡出和淡出淡出,这似乎可以改善点击次数。也许它会通过立即开始下一个音调来掩盖它?
  • 是的,我也观察到了。停止呼叫突然停止音调,并且重新启动延迟会导致咔嗒声。尽管它在彼此之上演奏得很漂亮,产生了谐波。也许在开始下一个后尝试停止前一个可能会起作用。可能需要两个 play_obj。
【解决方案2】:

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

  1. 循环声音中有“咔哒”声。必须生成信号的整个周期
  2. 您可以将streamcallback 一起使用,这样线程就移动到PyAudio 的底层了。使用它,我能够以公认的方式运行程序
  3. 我对创建两个线程对象有一些想法,它们将成为Stream 类的字段。然后他们可以使用 2 个不同的流和线程来启动和停止。我无法让它工作 - 我认为线程没有等待finish()

【讨论】:

  • 这不是一个答案,您可能需要将这些 cmets 添加到您的问题中,或者在您的问题下作为“cmets”单独发布。
  • 谢谢。我会留下它,因为已经有一些其他的 cmets 和答案,我不想弄得一团糟,但我下次一定会记住它
猜你喜欢
  • 1970-01-01
  • 2020-09-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多