.. DO NOT EDIT. .. THIS FILE WAS AUTOMATICALLY GENERATED BY SPHINX-GALLERY. .. TO MAKE CHANGES, EDIT THE SOURCE PYTHON FILE: .. "gallery/stimulus-generation.py" .. LINE NUMBERS ARE GIVEN BELOW. .. only:: html .. note:: :class: sphx-glr-download-link-note :ref:`Go to the end ` to download the full example code. .. rst-class:: sphx-glr-example-title .. _sphx_glr_gallery_stimulus-generation.py: Generating calibrated stimuli using factories ============================================= This demonstrates how to combine the factories to create calibrated stimuli. The calibrated stimuli are then generated in blocks. This block-based approach allows us to create infinite-duration stimuli that are "fed" into the speaker buffer incrementially. .. GENERATED FROM PYTHON SOURCE LINES 10-14 .. code-block:: Python import matplotlib.pylab as plt .. GENERATED FROM PYTHON SOURCE LINES 15-23 Psiaudio supports several types of calibration. The simplest calibration assumes that frequency response is "flat". In other words, if you send a 1V RMS tone to the speaker, it will always produce the same output level regardless of frequency. You can also have calibrations that compensate for variations in speaker output as a function of frequency. For simplicity, let's assume our speaker's response is flat. First, import the calibration class that supports flat calibrations. .. GENERATED FROM PYTHON SOURCE LINES 23-26 .. code-block:: Python from psiaudio.calibration import FlatCalibration .. GENERATED FROM PYTHON SOURCE LINES 27-35 Now, let's assume that when we play a 1V RMS tone through the speaker, it produces an output of 114 dB SPL. Sensitivity of acoustic systems are often reported in millivolts per Pascal. 114 dB SPL is 10 Pascals. This means that the sensitivity of the speaker is 0.1 volt per Pascal or 100 millivolts per Pascal. We have a convenience method, `FlatCalibration.from_mv_pa` to create the calibration. .. GENERATED FROM PYTHON SOURCE LINES 35-38 .. code-block:: Python calibration = FlatCalibration.from_mv_pa(0.1e3) .. GENERATED FROM PYTHON SOURCE LINES 39-43 One Pascal is 94 dB. Let's see if this works. The method, `Calibration.get_sf` gives us the RMS amplitude of the waveform needed to generate a tone at the given frequency and level. We would expect the RMS value to be 0.1. .. GENERATED FROM PYTHON SOURCE LINES 43-45 .. code-block:: Python calibration.get_sf(frequency=1000, level=94) .. rst-class:: sphx-glr-script-out .. code-block:: none np.float64(0.10023744672545445) .. GENERATED FROM PYTHON SOURCE LINES 46-48 Remember that 6 dB translates to half on a linear scale. Let's confirm this works (we expect the RMS value to be 0.05). .. GENERATED FROM PYTHON SOURCE LINES 48-50 .. code-block:: Python calibration.get_sf(frequency=1000, level=94-6) .. rst-class:: sphx-glr-script-out .. code-block:: none np.float64(0.050237728630191596) .. GENERATED FROM PYTHON SOURCE LINES 51-54 Now that we've defined our calibration, we can generate a stimulus waveform. Let's start with the simplest possible type of stimulus, a tone. First, we import the ToneFactory class and create an instance. .. GENERATED FROM PYTHON SOURCE LINES 54-59 .. code-block:: Python from psiaudio.stim import ToneFactory fs = 100000 tone = ToneFactory(fs=fs, frequency=1000, level=80, calibration=calibration) .. GENERATED FROM PYTHON SOURCE LINES 60-66 Note that we had to provide the sampling frequency (`fs`) the tone must be generated at along with other stimulus parameters. The instance supports several methods that are used by psiexperiment to properly handle the tone. For example, we need to know how long the stimulus is. .. GENERATED FROM PYTHON SOURCE LINES 66-68 .. code-block:: Python tone.get_duration() .. rst-class:: sphx-glr-script-out .. code-block:: none inf .. GENERATED FROM PYTHON SOURCE LINES 69-74 This means the tone can run continuously for the full duration of the experiment. You may use a continuous waveform (e.g., bandlimited noise) for generating a background masker. Let's get the first 5000 samples of the tone. .. GENERATED FROM PYTHON SOURCE LINES 74-78 .. code-block:: Python waveform = tone.next(5000) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_001.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_001.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 79-80 Let's get the next 1000 samples. .. GENERATED FROM PYTHON SOURCE LINES 80-84 .. code-block:: Python waveform = tone.next(1000) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_002.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_002.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 85-93 As you can see, a factory supports *incremential* generation of waveforms. This enables us to generate infinitely long waveforms (such as maskers) that never repeat. Tones are boring. Let's look at a more interesting type of stimulus. Sinusoidally-amplitude modulated noise with a cosine-squared onset/offset ramp. The `Cos2EnvelopeFactory` is a modulator, which means that it takes, as it's input, another factory (e.g., a tone) and applies a transform to it. .. GENERATED FROM PYTHON SOURCE LINES 93-111 .. code-block:: Python from psiaudio.stim import Cos2EnvelopeFactory tone = ToneFactory(fs=fs, frequency=16000, level=94, calibration=calibration) envelope = Cos2EnvelopeFactory(fs=100000, start_time=0, rise_time=5e-3, duration=10, input_factory=tone) waveform = envelope.next(1000) plt.figure() plt.plot(waveform) waveform = envelope.next(1000) plt.figure() plt.plot(waveform) plt.figure() plt.specgram(waveform, Fs=fs); plt.show() .. rst-class:: sphx-glr-horizontal * .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_003.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_003.png :class: sphx-glr-multi-img * .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_004.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_004.png :class: sphx-glr-multi-img * .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_005.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_005.png :class: sphx-glr-multi-img .. GENERATED FROM PYTHON SOURCE LINES 112-113 The Cos2EnvelopeFactory has a finite duration .. GENERATED FROM PYTHON SOURCE LINES 113-115 .. code-block:: Python envelope.get_duration() .. rst-class:: sphx-glr-script-out .. code-block:: none 10 .. GENERATED FROM PYTHON SOURCE LINES 116-118 Let's take a look at bandlimited noise, which is a more commonly used background masker. .. GENERATED FROM PYTHON SOURCE LINES 118-129 .. code-block:: Python from psiaudio.stim import BandlimitedNoiseFactory noise = BandlimitedNoiseFactory(fs=fs, seed=0, level=94, fl=2000, fh=8000, filter_rolloff=1, passband_attenuation=1, stopband_attenuation=80, equalize=False, calibration=calibration) waveform = noise.next(5000) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_006.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_006.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 130-132 Like tone factories, the bandlimited noise factory can run forever if you want it to. .. GENERATED FROM PYTHON SOURCE LINES 132-134 .. code-block:: Python noise.get_duration() .. rst-class:: sphx-glr-script-out .. code-block:: none inf .. GENERATED FROM PYTHON SOURCE LINES 135-138 Now, let's embed the noise in a sinusoidally amplitude-modulated (SAM) envelope. Note that when we create this factory, we provide the noise we created as an argument to the parameter `input_waveform`. .. GENERATED FROM PYTHON SOURCE LINES 138-147 .. code-block:: Python from psiaudio.stim import SAMEnvelopeFactory sam_envelope = SAMEnvelopeFactory(fs=fs, depth=1, fm=5, delay=1, direction='positive', input_factory=noise) waveform = sam_envelope.next(fs*2) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_007.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_007.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 148-149 Unlike the Cos2EnvelopeFactory, the SAMEnvelopeFactory has a finite duration. .. GENERATED FROM PYTHON SOURCE LINES 149-151 .. code-block:: Python sam_envelope.get_duration() .. rst-class:: sphx-glr-script-out .. code-block:: none inf .. GENERATED FROM PYTHON SOURCE LINES 152-153 Now, embed the SAM noise inside a cosine-squared envelope. .. GENERATED FROM PYTHON SOURCE LINES 153-157 .. code-block:: Python cos_envelope = Cos2EnvelopeFactory(fs=fs, start_time=0, rise_time=0.25, duration=4, input_factory=sam_envelope) .. GENERATED FROM PYTHON SOURCE LINES 158-160 By definition, a cosine-squared envelope has a finite duration. Let's plot the first two seconds. .. GENERATED FROM PYTHON SOURCE LINES 160-164 .. code-block:: Python waveform = cos_envelope.next(fs*2) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_008.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_008.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 165-166 Now, the next two seconds. .. GENERATED FROM PYTHON SOURCE LINES 166-170 .. code-block:: Python waveform = cos_envelope.next(fs*2) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_009.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_009.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 171-173 What happens if we keep going? Remember the duration of the stimulus is only 4 seconds. .. GENERATED FROM PYTHON SOURCE LINES 173-177 .. code-block:: Python waveform = cos_envelope.next(fs*2) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_010.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_010.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 178-179 That's because the stimulus is over. We can check that this is the case. .. GENERATED FROM PYTHON SOURCE LINES 179-181 .. code-block:: Python cos_envelope.is_complete() .. rst-class:: sphx-glr-script-out .. code-block:: none True .. GENERATED FROM PYTHON SOURCE LINES 182-183 What if we want to start over at the beginning? Reset it. .. GENERATED FROM PYTHON SOURCE LINES 183-188 .. code-block:: Python cos_envelope.reset() waveform = cos_envelope.next(100000*4) plt.plot(waveform) plt.show() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_011.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_011.png :class: sphx-glr-single-img .. GENERATED FROM PYTHON SOURCE LINES 189-192 Not all stimuli have to be composed of individual building blocks (e.g., envelopes, modulators and carriers). We can also define discrete waveform factories that can be used as-is. For example, chirps. .. GENERATED FROM PYTHON SOURCE LINES 192-201 .. code-block:: Python from psiaudio.stim import ChirpFactory chirp = ChirpFactory(fs=100000, start_frequency=50, end_frequency=5000, duration=1, level=94, calibration=calibration) waveform = chirp.next(5000) plt.plot(waveform) chirp.get_duration() .. image-sg:: /gallery/images/sphx_glr_stimulus-generation_012.png :alt: stimulus generation :srcset: /gallery/images/sphx_glr_stimulus-generation_012.png :class: sphx-glr-single-img .. rst-class:: sphx-glr-script-out .. code-block:: none 1.0 .. GENERATED FROM PYTHON SOURCE LINES 202-216 To create your own, you would subclass `psiaudio.stim.Waveform` and implement the following methods: * ``__init_``: Where you perform potentially expensive computations (such as the filter coefficients for bandlimited noise). * ``reset``: Where you reset any settings that are releavant to incremential generation of the waveform (e.g., the initial state of the filter and the random number generator for bandlimited noise). * ``next``: Where you actually generate the waveform. * ``get_duration``: The duration of the waveform. Return `np.inf` if continuous. See the ``psiaudio.stim`` module for examples (e.g., ``BandlimitedNoiseFactory`` and ``ChirpFactory``). .. rst-class:: sphx-glr-timing **Total running time of the script:** (0 minutes 1.216 seconds) .. _sphx_glr_download_gallery_stimulus-generation.py: .. only:: html .. container:: sphx-glr-footer sphx-glr-footer-example .. container:: sphx-glr-download sphx-glr-download-jupyter :download:`Download Jupyter notebook: stimulus-generation.ipynb ` .. container:: sphx-glr-download sphx-glr-download-python :download:`Download Python source code: stimulus-generation.py ` .. container:: sphx-glr-download sphx-glr-download-zip :download:`Download zipped: stimulus-generation.zip ` .. only:: html .. rst-class:: sphx-glr-signature `Gallery generated by Sphinx-Gallery `_