2
\$\begingroup\$

I made a small python script to create an audio sound used as stimuli. It's one of my first uses of super(), and I do not know if I did it right. I'm looking for any improvement in both optimization and style you could suggest.

import pyaudio
import numpy as np
class SoundStimuli:
 def __init__(self, fs=44100, duration=1.0):
 self.fs = fs
 self.duration = duration
 self.t = np.linspace(0, duration, int(duration*fs), endpoint=False)
 self.signal = None
 
 def play(self):
 if self.signal is None:
 print ('WARNING: No audio signal to play.')
 return
 
 p = pyaudio.PyAudio()
 # for paFloat32 sample values must be in range [-1.0, 1.0]
 stream = p.open(format=pyaudio.paFloat32,
 channels=1,
 rate=self.fs,
 output=True)
 
 # play. May repeat with different volume values (if done interactively)
 stream.write(self.signal)
 
 stream.stop_stream()
 stream.close()
 p.terminate()
 
 def convert2float32(self):
 self.signal = self.signal.astype(np.float32)
 
 def plot_signal(self, ax, **plt_kwargs):
 if self.signal is None:
 print ('WARNING: No audio signal to plot.')
 return
 
 ax.plot(self.t, self.signal, **plt_kwargs)
 
 def plot_signal_fft(self, ax, **plt_kwargs):
 fft_freq = np.fft.rfftfreq(self.t.shape[0], 1.0/self.fs)
 fft = np.abs(np.fft.rfft(self.signal))
 ax.plot(fft_freq, fft, **plt_kwargs)
 
class ASSR(SoundStimuli):
 def __init__(self, fc, fm, fs=44100, duration=1.0):
 super().__init__(fs, duration)
 self.fc = fc
 self.fm = fm
 
 def classical_AM(self):
 self.assr_amplitude = (1-np.sin(2*np.pi*self.fm*self.t))
 self.signal = self.assr_amplitude * np.sin(2*np.pi*self.fc*self.t).astype(np.float32)
 
 def DSBSC_AM(self):
 self.assr_amplitude = np.sin(2*np.pi*self.fm*self.t)
 self.signal = self.assr_amplitude * np.sin(2*np.pi*self.fc*self.t).astype(np.float32)
 
 def plot_signal(self, ax, amplitude_linestyle='--', amplitude_color='crimson', **plt_kwargs):
 super().plot_signal(ax, **plt_kwargs)
 ax.plot(self.t, self.assr_amplitude, linestyle=amplitude_linestyle, color=amplitude_color)

The class ASSR is imported in a second script where the stimuli is created:

sound = ASSR(fs=44100, duration=1.0, fc=1000, fm=40)
sound.classical_AM()

However, I do not play it using the .play() method because it looks like pyaudio doesn't accept the PyAudio and stream to be open/close outside the scope of .write(). And since I do not want to open a new stream every time I play a sound, I ended up using .play() to test the sound; and then create in the main program where ASSR is imported a new audio stream with different .write(sound.signal) calls.

# Initialize audio stream
p = pyaudio.PyAudio()
stream = p.open(format=pyaudio.paFloat32,
 channels=1,
 rate=sound.fs,
 output=True)
# Do stuff
stream.write(sound.signal)
# Do stuff
stream.write(sound.signal)
stream.stop_stream()
stream.close()
p.terminate()
Reinderien
71k5 gold badges76 silver badges256 bronze badges
asked Nov 11, 2020 at 9:01
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

A lot of things here are already good, like:

  • fs, duration and t being members on SoundStimuli
  • The choice of which things to initialize in the first __init__ (mostly)

Things that could be improved:

  • SoundStimuli is arguably SoundStimulus
  • self.signal should not be an optional member variable; the base class should be made effectively abstract and the signal should be returned from a getter or property whose base implementation raises NotImplementedError.
  • In play, the absence of a signal should be fatal, so you should naively access self.signal, and let any exceptions fall through to the caller
  • After p.open, wrap the rest in a try, and put the close() in a finally
  • convert2float is not a great idea - it replaces a member with a different version of the same member. That means that before this is called, the instance is by definition in an invalid state, which should be avoided.
answered Nov 11, 2020 at 15:10
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.