Source code for acoustics.generator

"""
Generator
=========

The generator module provides signal generators.

The following functions calculate ``N`` samples and return an array containing the samples.

For indefinitely long iteration over the samples, consider using the output of these functions
in :func:`itertools.cycle`.

Noise
*****

Different types of noise are available. The following table lists the color
of noise and how the power and power density change per octave.

====== ===== =============
Color  Power Power density
====== ===== =============
White  +3 dB  0 dB
Pink    0 dB -3 dB
Blue   +6 dB +3 dB
Brown  -3 dB -6 dB
Violet +9 dB +6 dB
====== ===== =============

The colored noise is created by generating pseudo-random numbers using
:func:`np.random.randn` and then multiplying these with a curve tyical for the color.
Afterwards, an inverse DFT is performed using :func:`np.fft.irfft`.
Finally, the noise is normalized using :func:`acoustics.signal.normalize`.

All colors
----------

.. autofunction:: noise
.. autofunction:: noise_generator

Per color
---------

.. autofunction:: white
.. autofunction:: pink
.. autofunction:: blue
.. autofunction:: brown
.. autofunction:: violet


Other
*****

.. autofunction:: heaviside

For related functions, check :mod:`scipy.signal`.


"""
import itertools
import numpy as np

try:
    from pyfftw.interfaces.numpy_fft import irfft  # Performs much better than numpy's fftpack
except ImportError:  # Use monkey-patching np.fft perhaps instead?
    from numpy.fft import irfft  # pylint: disable=ungrouped-imports

from .signal import normalize


[docs]def noise(N, color='white', state=None): """Noise generator. :param N: Amount of samples. :param color: Color of noise. :param state: State of PRNG. :type state: :class:`np.random.RandomState` """ try: return _noise_generators[color](N, state) except KeyError: raise ValueError("Incorrect color.")
[docs]def white(N, state=None): """ White noise. :param N: Amount of samples. :param state: State of PRNG. :type state: :class:`np.random.RandomState` White noise has a constant power density. It's narrowband spectrum is therefore flat. The power in white noise will increase by a factor of two for each octave band, and therefore increases with 3 dB per octave. """ state = np.random.RandomState() if state is None else state return state.randn(N)
[docs]def pink(N, state=None): """ Pink noise. :param N: Amount of samples. :param state: State of PRNG. :type state: :class:`np.random.RandomState` Pink noise has equal power in bands that are proportionally wide. Power density decreases with 3 dB per octave. """ # This method uses the filter with the following coefficients. #b = np.array([0.049922035, -0.095993537, 0.050612699, -0.004408786]) #a = np.array([1, -2.494956002, 2.017265875, -0.522189400]) #return lfilter(B, A, np.random.randn(N)) # Another way would be using the FFT #x = np.random.randn(N) #X = rfft(x) / N state = np.random.RandomState() if state is None else state uneven = N % 2 X = state.randn(N // 2 + 1 + uneven) + 1j * state.randn(N // 2 + 1 + uneven) S = np.sqrt(np.arange(len(X)) + 1.) # +1 to avoid divide by zero y = (irfft(X / S)).real if uneven: y = y[:-1] return normalize(y)
[docs]def blue(N, state=None): """ Blue noise. :param N: Amount of samples. :param state: State of PRNG. :type state: :class:`np.random.RandomState` Power increases with 6 dB per octave. Power density increases with 3 dB per octave. """ state = np.random.RandomState() if state is None else state uneven = N % 2 X = state.randn(N // 2 + 1 + uneven) + 1j * state.randn(N // 2 + 1 + uneven) S = np.sqrt(np.arange(len(X))) # Filter y = (irfft(X * S)).real if uneven: y = y[:-1] return normalize(y)
[docs]def brown(N, state=None): """ Violet noise. :param N: Amount of samples. :param state: State of PRNG. :type state: :class:`np.random.RandomState` Power decreases with -3 dB per octave. Power density decreases with 6 dB per octave. """ state = np.random.RandomState() if state is None else state uneven = N % 2 X = state.randn(N // 2 + 1 + uneven) + 1j * state.randn(N // 2 + 1 + uneven) S = (np.arange(len(X)) + 1) # Filter y = (irfft(X / S)).real if uneven: y = y[:-1] return normalize(y)
[docs]def violet(N, state=None): """ Violet noise. Power increases with 6 dB per octave. :param N: Amount of samples. :param state: State of PRNG. :type state: :class:`np.random.RandomState` Power increases with +9 dB per octave. Power density increases with +6 dB per octave. """ state = np.random.RandomState() if state is None else state uneven = N % 2 X = state.randn(N // 2 + 1 + uneven) + 1j * state.randn(N // 2 + 1 + uneven) S = (np.arange(len(X))) # Filter y = (irfft(X * S)).real if uneven: y = y[:-1] return normalize(y)
_noise_generators = { 'white': white, 'pink': pink, 'blue': blue, 'brown': brown, 'violet': violet, }
[docs]def noise_generator(N=44100, color='white', state=None): """Noise generator. :param N: Amount of unique samples to generate. :param color: Color of noise. Generate `N` amount of unique samples and cycle over these samples. """ #yield from itertools.cycle(noise(N, color)) # Python 3.3 for sample in itertools.cycle(noise(N, color, state)): yield sample
[docs]def heaviside(N): """Heaviside. Returns the value 0 for `x < 0`, 1 for `x > 0`, and 1/2 for `x = 0`. """ return 0.5 * (np.sign(N) + 1)
__all__ = ['noise', 'white', 'pink', 'blue', 'brown', 'violet', 'noise_generator', 'heaviside']