Python 3.x 后台Python多进程音频
我正在尝试编写一个带有基本用户界面的简单数字合成器。目前,它创建了一个具有所需频率和振幅的正弦波发生器,然后在发生器上无限循环,将块发送到音频流。这样做可以产生纯音,听起来不错。在我的简单UI上,是音符频率的滑块,而声音循环是无限的,显然我无法与UI交互来更改音符,因为控件是无限循环的。如果我试着在UI_更新循环中运行audiostream,而不是它自己的无限循环,它会变得很不稳定,因为它花费很短的时间来计算注释更改和UI渲染,听起来很糟糕 我的解决方案是简单地将音频渲染循环发送到多进程进程,并在后台运行无限循环。然后,当滑块移动时,终止进程并创建一个新进程来播放新音符。我制作了这个代码函数,我可以看到它创建过程来播放声音,调试我可以看到它执行声音创建功能,我可以更新UI并观察它循环过程,但是没有声音产生。我知道声音创建功能可以正常工作,因为我可以将其从进程中删除,并将其放置在主循环中,它会发出良好的声音,但由于无限循环,我无法与UI交互 我在MacCatalina10.15.6上使用Python3.8.5,pySimpleGUI是我使用的GUI库,pyaudio是我用来播放它的。有没有一种方法可以为我的电脑提供多进程的音频控制?有没有更好的设计,我可以使用,以获得平稳的声音,同时仍然能够改变声音不断 这是我代码的相关部分:Python 3.x 后台Python多进程音频,python-3.x,audio,pyaudio,multiprocess,Python 3.x,Audio,Pyaudio,Multiprocess,我正在尝试编写一个带有基本用户界面的简单数字合成器。目前,它创建了一个具有所需频率和振幅的正弦波发生器,然后在发生器上无限循环,将块发送到音频流。这样做可以产生纯音,听起来不错。在我的简单UI上,是音符频率的滑块,而声音循环是无限的,显然我无法与UI交互来更改音符,因为控件是无限循环的。如果我试着在UI_更新循环中运行audiostream,而不是它自己的无限循环,它会变得很不稳定,因为它花费很短的时间来计算注释更改和UI渲染,听起来很糟糕 我的解决方案是简单地将音频渲染循环发送到多进程进程,并
def sin_wave(frequency=440.0, samplerate=44100, amplitude=0.5):
period = int(samplerate / frequency)
amplitude = clip_amplitude(amplitude)
lookup = [float(amplitude) * math.sin(2.0*math.pi*float(frequency)*(float(i%period)/float(samplerate))) for i in range(period)]
return (lookup[i%period] for i in count(0))
def grouper(n, iterable, fillvalue=None):
# "grouper(3, 'ABCDEFG', 'x') --> zip_longest((A,B,C), (D,E,F), (G,x,x))"
args = [iter(iterable)] * n
return zip_longest(fillvalue=fillvalue, *args)
def compute_samples(channels, nsamples=None):
return islice(zip(*(map(sum, zip(*channel)) for channel in channels)), nsamples)
def update_samples(values):
channel = ()
for i in range(50):
if f'Osc{i}Freq' in values and f'Osc{i}Vol' in values:
# construct sound, then convert tuple to list, append our sound, back to tuple
sound = sin_wave(frequency=values[f'Osc{i}Freq'], amplitude=values[f'Osc{i}Vol'])
# operations cannot be combined or NoneType error
channel = list(channel)
channel.append(sound)
channel = tuple(channel)
# mono for now
channels = (
channel,
channel
)
return compute_samples(channels)
def write_audiostream(stream, samples, nchannels=2, sampwidth=2, framerate=44100, bufsize=2048):
max_amplitude = float(int((2 ** (sampwidth * 8)) / 2) - 1) / 100
logging.info('test')
for chunk in grouper(bufsize, samples):
frames = b''.join(b''.join(struct.pack('h', int(max_amplitude * sample)) for sample in channels) for channels in chunk if channels is not None)
stream.write(frames)
def main():
num_channels = 2
sampwidth = 2
framerate = 44100
pya = pyaudio.PyAudio()
stream = pya.open(format = pya.get_format_from_width(width=sampwidth), channels=num_channels, rate=framerate, output=True)
### GUI
layout = [
[
sg.Text('Oscilator 1'),
sg.Slider(
key="Osc1Freq",
range=(20.0,500.0),
default_value=440.0,
orientation='h'),
sg.Slider(
key="Osc1Vol",
range=(0.0,1.0),
default_value=0.5,
resolution=0.01,
orientation='h')
],
[
sg.Text('Oscilator 2'),
sg.Slider(
key="Osc2Freq",
range=(20.0,500.0),
default_value=440.0,
orientation='h'),
sg.Slider(
key="Osc2Vol",
range=(0.0,1.0),
default_value=0.5,
resolution=0.01,
orientation='h')
],
[sg.Output(size=(250,50))],
[sg.Submit(), sg.Cancel()]
]
window = sg.Window('Shit', layout)
event_old, values_old = None, None
while True:
event, values = window.read(timeout = 50)
if event in (None, 'Exit', 'Cancel'):
break
if values_old != values:
print(event, values)
event_old, values_old = event, values
samples = update_samples(values)
# sound functions here, it just takes control away while in infinite loop
# write_audiostream(stream, samples)
# no sound plays if executed in a Process
if not 'audio_proc' in locals():
audio_proc = multiprocess.Process(target=write_audiostream, args=(stream, samples), daemon=True)
audio_proc.start()
else:
audio_proc.terminate()
audio_proc = multiprocess.Process(target=write_audiostream, args=(stream, samples), daemon=True)
audio_proc.start()