Generate simple sinusoidal sounds and plot signal/fft 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()
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.