Reputation: 915
I don't have the experience with threading at all.
All I want to do is to play a sound and be able to change the tone (frequency) in the meantime, using GUI.
This code plays a continuous stream without any peaks or distortions:
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()
This code creates GUI. Inleft_click
and right_click
the create_sine_tone()
creates a new frequency wave. However, as I understand, it modifies the memory that is used by threading
in play_sine_tone
and the program crashes.
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()
How can I modify the wave so the program won't crash? Maybe I could close the thread before changing the frequency?
Upvotes: 2
Views: 2873
Reputation: 11453
If all you are trying to do is play different tones that can be controlled using GUI, you may not need threads.
PySimpleGUI provides an super easy to use GUI builder based on Tkinter (and other tools). Best of all it provides actions based on event that are driven by GUI components.
On the other hand use of pydub gives us easy way to create different tones and play them.
pydub _play_with_simpleaudio
method allows us to play tones using simpleAudio in a non-blocking way.
GUI Controls:
'>>' chooses next frequency in multiples of 200 Hz.
'<<' chooses previous frequency in multiples of 100 Hz.
The only issue I observed was slight clicking sounds on frequency shift.That may need further work.
Following working code is based on above packages.
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()
Result:
$ python3 pygui3.py
Playing >> 400 Hz
Playing >> 600 Hz
Playing >> 800 Hz
Playing << 700 Hz
Playing << 600 Hz
GUI:
Upvotes: 1
Reputation: 915
I can't find the answer. What I know:
stream
with callback
so the threading is moved under the hood of PyAudio
. Using it I am able to run the program in the accepted wayStream
class. Then they can use 2 different streams and threads to start and stop. I wasn't able to make it work though - I think the threads are not waiting for the finish()
Upvotes: 0