Python multithreading – play multiple sine waves simultaneously
Question:
I would like to generate sine wave noises at a given frequency and duration. I would like this to play simultaneously while using a GUI.
I’ve created a class that makes use of threading but it doesn’t seem to work. I cannot execute code at the same time as invoking the .run() line. For example when running the below code, the print statement executes after the sound has completed.
import pyaudio
import numpy as np
import threading
class WavePlayerLoop(threading.Thread) :
"""
A simple class based on PyAudio to play sine wave at certain frequency.
It's a threading class. You can play audio while your application
continues to do stuff.
"""
def __init__(self, freq=440., length=1., volume=0.5):
threading.Thread.__init__(self)
self.p = pyaudio.PyAudio()
self.volume = volume # range [0.0, 1.0]
self.fs = 44100 # sampling rate, Hz, must be integer
self.duration = length # in seconds, may be float
self.f = freq # sine frequency, Hz, may be float
def run(self) :
"""
Just another name for self.start()
"""
# generate samples, note conversion to float32 array
self.samples = (np.sin(2*np.pi*np.arange(self.fs*self.duration)*self.f/self.fs)).astype(np.float32)
# for paFloat32 sample values must be in range [-1.0, 1.0]
self.stream = self.p.open(format=pyaudio.paFloat32,
channels=1,
rate=self.fs,
output=True)
# play. May repeat with different volume values (if done interactively)
self.stream.write(self.volume*self.samples)
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
s = WavePlayerLoop(freq=440., length=10., volume=0.5)
s.run()
print 'this should print while there is a beep sound'
What do I need to do to allow this sound to play simultaneously while other code can execute?
Answers:
You seem to have got it fully right. Following test code is working perfectly fine with your code.
objs = []
number_of_threads = 10
print 'Creating thread objects'
for i in range(number_of_threads):
objs.append(WavePlayerLoop(freq=440 * i, length=10., volume=0.1 * i))
print 'Starting thread objects'
for i in range(number_of_threads):
objs[i].start()
print 'Waiting for threads to finish'
for i in range(number_of_threads):
objs[i].join()
print ('Finishing program')
Here’s a cleaned up and Python-3-ified version that might be helpful for the contemporary seeker
import numpy as np
import pyaudio
import threading
class WavePlayerLoop(threading.Thread):
def __init__(self, freq=440., volume=0.5, duration=1.):
threading.Thread.__init__(self)
self.p = pyaudio.PyAudio()
self.volume = volume # range [0.0, 1.0]
self.fs = 44100 # sampling rate, Hz, must be integer
self.duration = duration # in seconds, may be float
self.f = freq # sine frequency, Hz, may be float
def run(self) :
self.samples = (np.sin(2*np.pi*np.arange(self.fs*self.duration)*self.f/self.fs)).astype(np.float32)
self.stream = self.p.open(format=pyaudio.paFloat32,
channels=1,
rate=self.fs,
output=True)
self.stream.write(self.volume*self.samples)
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
duration = 5
tones = [
{'frequency': 100, 'volume': .8},
{'frequency': 200, 'volume': .6},
{'frequency': 500, 'volume': .4},
]
for tone in tones:
WavePlayerLoop(tone['frequency'], tone['volume'], duration).start()
I would like to generate sine wave noises at a given frequency and duration. I would like this to play simultaneously while using a GUI.
I’ve created a class that makes use of threading but it doesn’t seem to work. I cannot execute code at the same time as invoking the .run() line. For example when running the below code, the print statement executes after the sound has completed.
import pyaudio
import numpy as np
import threading
class WavePlayerLoop(threading.Thread) :
"""
A simple class based on PyAudio to play sine wave at certain frequency.
It's a threading class. You can play audio while your application
continues to do stuff.
"""
def __init__(self, freq=440., length=1., volume=0.5):
threading.Thread.__init__(self)
self.p = pyaudio.PyAudio()
self.volume = volume # range [0.0, 1.0]
self.fs = 44100 # sampling rate, Hz, must be integer
self.duration = length # in seconds, may be float
self.f = freq # sine frequency, Hz, may be float
def run(self) :
"""
Just another name for self.start()
"""
# generate samples, note conversion to float32 array
self.samples = (np.sin(2*np.pi*np.arange(self.fs*self.duration)*self.f/self.fs)).astype(np.float32)
# for paFloat32 sample values must be in range [-1.0, 1.0]
self.stream = self.p.open(format=pyaudio.paFloat32,
channels=1,
rate=self.fs,
output=True)
# play. May repeat with different volume values (if done interactively)
self.stream.write(self.volume*self.samples)
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
s = WavePlayerLoop(freq=440., length=10., volume=0.5)
s.run()
print 'this should print while there is a beep sound'
What do I need to do to allow this sound to play simultaneously while other code can execute?
You seem to have got it fully right. Following test code is working perfectly fine with your code.
objs = []
number_of_threads = 10
print 'Creating thread objects'
for i in range(number_of_threads):
objs.append(WavePlayerLoop(freq=440 * i, length=10., volume=0.1 * i))
print 'Starting thread objects'
for i in range(number_of_threads):
objs[i].start()
print 'Waiting for threads to finish'
for i in range(number_of_threads):
objs[i].join()
print ('Finishing program')
Here’s a cleaned up and Python-3-ified version that might be helpful for the contemporary seeker
import numpy as np
import pyaudio
import threading
class WavePlayerLoop(threading.Thread):
def __init__(self, freq=440., volume=0.5, duration=1.):
threading.Thread.__init__(self)
self.p = pyaudio.PyAudio()
self.volume = volume # range [0.0, 1.0]
self.fs = 44100 # sampling rate, Hz, must be integer
self.duration = duration # in seconds, may be float
self.f = freq # sine frequency, Hz, may be float
def run(self) :
self.samples = (np.sin(2*np.pi*np.arange(self.fs*self.duration)*self.f/self.fs)).astype(np.float32)
self.stream = self.p.open(format=pyaudio.paFloat32,
channels=1,
rate=self.fs,
output=True)
self.stream.write(self.volume*self.samples)
self.stream.stop_stream()
self.stream.close()
self.p.terminate()
duration = 5
tones = [
{'frequency': 100, 'volume': .8},
{'frequency': 200, 'volume': .6},
{'frequency': 500, 'volume': .4},
]
for tone in tones:
WavePlayerLoop(tone['frequency'], tone['volume'], duration).start()