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?

Asked By: Sam

||

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')
Answered By: Kevin

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()
Answered By: America
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.