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_click
和right_click
中,create_sine_tone()
创建了一个新的频率波。但是,据我了解,它修改了 threading
在 play_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 程序才不会崩溃?也许我可以在更改频率之前关闭线程?
我找不到答案。我知道的:
- 循环音中有"clicks"个。必须生成信号的完整周期
- 您可以将
stream
与 callback
一起使用,以便将线程移动到 PyAudio
的引擎盖下。使用它我能够运行程序以可接受的方式
- 我有一些关于创建两个线程对象的想法,它们将成为
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
图形用户界面:
我完全没有线程经验。
我想做的就是播放声音并同时使用 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_click
和right_click
中,create_sine_tone()
创建了一个新的频率波。但是,据我了解,它修改了 threading
在 play_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 程序才不会崩溃?也许我可以在更改频率之前关闭线程?
我找不到答案。我知道的:
- 循环音中有"clicks"个。必须生成信号的完整周期
- 您可以将
stream
与callback
一起使用,以便将线程移动到PyAudio
的引擎盖下。使用它我能够运行程序以可接受的方式 - 我有一些关于创建两个线程对象的想法,它们将成为
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
图形用户界面: