Source code for pyfar.signals.stochastic

"""Module for generating stochastic signals as part of pyfar.signals."""
import numpy as np
import pyfar
from pyfar.dsp import fft


[docs] def noise(n_samples, spectrum="white", rms=1, sampling_rate=44100, seed=None): """ Generate single or multi channel normally distributed white or pink noise. The pink noise is generated by applying a ``sqrt(1/f)`` filter to the spectrum. Parameters ---------- n_samples : int The length of the signal in samples spectrum : str, optional ``white`` to generate noise with constant energy across frequency. ``pink`` to generate noise with constant energy across filters with constant relative bandwidth. The default is ``white``. rms : double, array like, optional The root mean square (RMS) value of the noise signal. A multi channel noise signal is generated if an array of RMS values is passed. The default is ``1``. sampling_rate : int, optional The sampling rate in Hz. The default is ``44100``. seed : int, None, optional The seed for the random generator. Pass a seed to obtain identical results for multiple calls. The default is ``None``, which will yield different results with every call. Returns ------- signal : Signal The noise signal. The signal is in the time domain and has the ``rms`` FFT normalization (see :py:func:`~pyfar.dsp.fft.normalization`). The type of the spectrum (``white``, ``pink``) and the RMS amplitude are written to `comment`. """ # generate the noise rms = np.atleast_1d(rms) n_samples = int(n_samples) cshape = np.atleast_1d(rms).shape rng = np.random.default_rng(seed) noise = rng.standard_normal(np.prod(cshape + (n_samples, ))) noise = noise.reshape(cshape + (n_samples, )) if spectrum == "pink": # apply 1/f filter in the frequency domain noise = fft.rfft(noise, n_samples, sampling_rate, 'none') noise /= np.sqrt(np.arange(1, noise.shape[-1]+1)) noise = fft.irfft(noise, n_samples, sampling_rate, 'none') elif spectrum != "white": raise ValueError( f"spectrum is '{spectrum}' but must be 'white' or 'pink'") # level the noise rms_current = np.atleast_1d(np.sqrt(np.mean(noise**2, axis=-1))) for idx in np.ndindex(rms.shape): noise[idx] = noise[idx] / rms_current[idx] * rms[idx] # save to Signal nl = "\n" # required as variable because f-strings cannot contain "\" comment = f"{spectrum} noise signal (rms = {str(rms).replace(nl, ',')})" signal = pyfar.Signal( noise, sampling_rate, fft_norm="rms", comment=comment) return signal
[docs] def pulsed_noise(n_pulse, n_pause, n_fade=90, repetitions=5, rms=1, spectrum="pink", frozen=True, sampling_rate=44100, seed=None): """ Generate single channel normally distributed pulsed white or pink noise. The pink noise is generated by applying a ``sqrt(1/f)`` filter to the spectrum. Parameters ---------- n_pulse : int The length of the pulses in samples n_pause : int The length of the pauses between the pulses in samples. n_fade : int, optional The length of the squared sine/cosine fade-in and fade outs in samples. The default is ``90``, which equals approximately 2 ms at sampling rates of 44.1 and 48 kHz. repetitions : int, optional Specifies the number of noise pulses. The default is ``5``. rms : double, array like, optional The RMS amplitude of the white signal. The default is ``1``. spectrum: string, optional The noise spectrum, which can be ``pink`` or ``white``. The default is ``pink``. frozen : boolean, optional If ``True``, all noise pulses are identical. If ``False`` each noise pulse is a separate stochastic process. The default is ``True``. sampling_rate : int, optional The sampling rate in Hz. The default is ``44100``. seed : int, None, optional The seed for the random generator. Pass a seed to obtain identical results for multiple calls. The default is ``None``, which will yield different results with every call. Returns ------- signal : Signal The noise signal. The Signal is in the time domain and has the ``rms`` FFT normalization (see :py:func:`~pyfar.dsp.fft.normalization`). `comment` contains information about the selected parameters. """ if n_pulse < 2 * n_fade: raise ValueError( "n_fade too large. It must be smaller than n_pulse/2.") # get the noise sample n_pulse = int(n_pulse) repetitions = int(repetitions) n_samples = n_pulse if frozen else n_pulse * repetitions p_noise = noise(n_samples, spectrum, rms, sampling_rate, seed).time p_noise = np.tile(p_noise, (repetitions, 1)) if frozen else \ p_noise.reshape((repetitions, n_pulse)) # fade the noise if n_fade > 0: n_fade = int(n_fade) fade = np.sin(np.linspace(0, np.pi/2, n_fade))**2 p_noise[..., 0:n_fade] *= fade p_noise[..., -n_fade:] *= fade[::-1] # add the pause p_noise = np.concatenate(( p_noise, np.zeros((repetitions, int(n_pause)))), -1) # reshape to single channel signal and discard final pause p_noise = p_noise.reshape((1, -1))[..., :-int(n_pause)] # save to Signal frozen_str = "frozen" if frozen else "" comment = (f"{frozen_str} {spectrum} pulsed noise signal (rms = {rms}, " f"{repetitions} repetitions, {n_pulse} samples pulse duration, " f"{n_pause} samples pauses, and {n_fade} samples fades.") signal = pyfar.Signal( p_noise, sampling_rate, fft_norm="rms", comment=comment) return signal