Beispiel #1
0
def fractional_octave_bands(signal,
                            num_fractions,
                            sampling_rate=None,
                            freq_range=(20.0, 20e3),
                            order=14):
    """Create and/or apply an energy preserving fractional octave filter bank.

    The filters are designed using second order sections of Butterworth
    band-pass filters. Note that if the upper cut-off frequency of a band lies
    above the Nyquist frequency, a high-pass filter is applied instead. Due to
    differences in the design of band-pass and high-pass filters, their slopes
    differ, potentially introducing an error in the summed energy in the stop-
    band region of the respective filters.

    .. note::
        This filter bank has -3 dB cut-off frequencies. For sufficiently large
        values of ``'order'``, the summed energy of the filter bank equals the
        energy of input signal, i.e., the filter bank is energy preserving
        (reconstructing). This is usefull for analysis energetic properties of
        the input signal such as the room acoustic propertie reverberation
        time. For an amplitude preserving filter bank with -6 dB cut-off
        frequencies see
        :py:func:`~pyfar.dsp.filter.reconstructing_fractional_octave_bands`.

    Parameters
    ----------
    signal : Signal, None
        The signal to be filtered. Pass ``None`` to create the filter without
        applying it.
    num_fractions : int, optional
        The number of bands an octave is divided into. Eg., ``1`` refers to
        octave bands and ``3`` to third octave bands. The default is ``1``.
    sampling_rate : None, int
        The sampling rate in Hz. Only required if signal is ``None``. The
        default is ``None``.
    frequency_range : array, tuple, optional
        The lower and upper frequency limits. The default is
         ``frequency_range=(20, 20e3)``
    order : int, optional
        Order of the Butterworth filter. The default is ``14``.

    Returns
    -------
    signal : Signal
        The filtered signal. Only returned if ``sampling_rate = None``.
    filter : FilterSOS
        Filter object. Only returned if ``signal = None``.

    Examples
    --------
    Filter an impulse into octave bands. The summed energy of all bands equals
    the energy of the input signal.

    .. plot::

        >>> import pyfar as pf
        >>> import numpy as np
        >>> import matplotlib.pyplot as plt
        >>> # generate the data
        >>> x = pf.signals.impulse(2**17)
        >>> y = pf.dsp.filter.fractional_octave_bands(
        ...     x, 1, freq_range=(20, 8e3))
        >>> # frequency domain plot
        >>> y_sum = pf.FrequencyData(
        ...     np.sum(np.abs(y.freq)**2, 0), y.frequencies)
        >>> pf.plot.freq(y)
        >>> ax = pf.plot.freq(y_sum, color='k', log_prefix=10, linestyle='--')
        >>> ax.set_title(
        ...     "Filter bands and the sum of their squared magnitudes")
        >>> plt.tight_layout()

    """
    # check input
    if (signal is None and sampling_rate is None) \
            or (signal is not None and sampling_rate is not None):
        raise ValueError('Either signal or sampling_rate must be none.')

    fs = signal.sampling_rate if sampling_rate is None else sampling_rate

    sos = _coefficients_fractional_octave_bands(sampling_rate=fs,
                                                num_fractions=num_fractions,
                                                freq_range=freq_range,
                                                order=order)

    filt = pf.FilterSOS(sos, fs)
    filt.comment = (
        "Second order section 1/{num_fractions} fractional octave band"
        "filter of order {order}")

    # return the filter object
    if signal is None:
        # return the filter object
        return filt
    else:
        # return the filtered signal
        signal_filt = filt.process(signal)
        return signal_filt
Beispiel #2
0
def crossover(signal, N, frequency, sampling_rate=None):
    """
    Create and apply Linkwitz-Riley crossover network  [1]_, [2]_.

    Linkwitz-Riley crossover filters are designed by cascading Butterworth
    filters of order `N/2`. where `N` must be even.

    Parameters
    ----------
    signal : Signal, None
        The Signal to be filtered. Pass ``None`` to create the filter without
        applying it.
    N : int
        The order of the Linkwitz-Riley crossover network, must be even.
    frequency : number, array-like
        Characteristic frequencies of the crossover network. If a single number
        is passed, the network consists of a single lowpass and highpass. If
        `M` frequencies are passed, the network consists of 1 lowpass, M-1
        bandpasses, and 1 highpass.
    sampling_rate : None, number
        The sampling rate in Hz. Only required if `signal` is ``None``. The
        default is ``None``.

    Returns
    -------
    signal : Signal
        The filtered signal. Only returned if ``sampling_rate = None``.
    filter : FilterSOS
        Filter object. Only returned if ``signal = None``.

    References
    ----------
    .. [1]  S. H. Linkwitz, 'Active crossover networks for noncoincident
            drivers,' J. Audio Eng. Soc., vol. 24, no. 1, pp. 2–8, Jan. 1976.
    .. [2]  D. Bohn, 'Linkwitz Riley crossovers: A primer,' Rane, RaneNote 160,
            2005.
    """

    # check input
    if (signal is None and sampling_rate is None) \
            or (signal is not None and sampling_rate is not None):
        raise ValueError('Either signal or sampling_rate must be none.')

    if N % 2:
        raise ValueError("The order 'N' must be an even number.")

    # sampling frequency in Hz
    fs = signal.sampling_rate if sampling_rate is None else sampling_rate
    # order of Butterworth filters
    N = int(N / 2)
    # normalized frequency (half-cycle / per sample)
    freq = np.atleast_1d(np.asarray(frequency)) / fs * 2

    # init neutral SOS matrix of shape (freq.size+1, SOS_dim_2, 6)
    n_sos = int(np.ceil(N / 2))  # number of lowpass sos
    SOS_dim_2 = n_sos if freq.size == 1 else 2 * n_sos

    SOS = np.tile(np.array([1, 0, 0, 1, 0, 0], dtype='float64'),
                  (freq.size + 1, SOS_dim_2, 1))

    # get filter coefficients for lowpass
    sos = spsignal.butter(N, freq[0], 'lowpass', analog=False, output='sos')
    SOS[0, 0:n_sos] = sos

    # get filter coefficients for the bandpass if more than one frequency is
    # provided
    for n in range(1, freq.size):
        sos_high = spsignal.butter(N,
                                   freq[n - 1],
                                   'highpass',
                                   analog=False,
                                   output='sos')
        sos_low = spsignal.butter(N,
                                  freq[n],
                                  'lowpass',
                                  analog=False,
                                  output='sos')
        SOS[n] = np.concatenate((sos_high, sos_low))

    # get filter coefficients for the highpass
    sos = spsignal.butter(N, freq[-1], 'highpass', analog=False, output='sos')
    SOS[-1, 0:n_sos] = sos

    # Apply every Butterworth filter twice
    SOS = np.tile(SOS, (1, 2, 1))

    # invert phase in every second channel if the Butterworth order is odd
    # (realized by reversing b-coefficients of the first sos)
    if N % 2:
        SOS[np.arange(1, freq.size + 1, 2), 0, 0:3] *= -1

    # generate filter object
    filt = pf.FilterSOS(SOS, fs)
    freq_list = [str(f) for f in np.array(frequency, ndmin=1)]
    filt.comment = (f"Linkwitz-Riley cross over network of order {N*2} at "
                    f"{', '.join(freq_list)} Hz.")

    # return the filter object
    if signal is None:
        # return the filter object
        return filt
    else:
        # return the filtered signal
        signal_filt = filt.process(signal)
        return signal_filt
Beispiel #3
0
def cheby1(signal, N, ripple, frequency, btype='lowpass', sampling_rate=None):
    """
    Create and apply digital Chebyshev Type I IIR filter.

    This is a wrapper for ``scipy.signal.cheby1``. Which creates digital
    Chebyshev Type I filter coefficients in second-order sections (SOS).

    Parameters
    ----------
    signal : Signal, None
        The Signal to be filtered. Pass None to create the filter without
        applying it.
    N : int
        The order of the Chebychev filter
    ripple : number
        The passband ripple in dB.
    frequency : number, array like
        The cut off-frequency in Hz if `btype` is ``'lowpass'`` or
        ``'highpass'``. An array like containing the lower and upper cut-off
        frequencies in Hz if `btype` is ``'bandpass'`` or ``'bandstop'``.
    btype : str
        One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``,
        ``'bandstop'``. The default is ``'lowpass'``.
    sampling_rate : None, number
        The sampling rate in Hz. Only required if signal is ``None``. The
        default is ``None``.

    Returns
    -------
    signal : Signal
        The filtered signal. Only returned if ``sampling_rate = None``.
    filter : FilterSOS
        SOS Filter object. Only returned if ``signal = None``.
    """

    # check input
    if (signal is None and sampling_rate is None) \
            or (signal is not None and sampling_rate is not None):
        raise ValueError('Either signal or sampling_rate must be none.')

    # sampling frequency in Hz
    fs = signal.sampling_rate if sampling_rate is None else sampling_rate
    # normalized frequency (half-cycle / per sample)
    frequency_norm = np.asarray(frequency) / fs * 2

    # get filter coefficients
    sos = spsignal.cheby1(N,
                          ripple,
                          frequency_norm,
                          btype,
                          analog=False,
                          output='sos')

    # generate filter object
    filt = pf.FilterSOS(sos, fs)
    filt.comment = (f"Chebychev Type I {btype} of order {N}. "
                    f"Cut-off frequency {frequency} Hz. "
                    f"Pass band ripple {ripple} dB.")

    # return the filter object
    if signal is None:
        # return the filter object
        return filt
    else:
        # return the filtered signal
        signal_filt = filt.process(signal)
        return signal_filt
Beispiel #4
0
def bessel(signal,
           N,
           frequency,
           btype='lowpass',
           norm='phase',
           sampling_rate=None):
    """
    Create and apply digital Bessel/Thomson IIR filter.

    This is a wrapper for ``scipy.signal.bessel``. Which creates digital
    Butterworth filter coefficients in second-order sections (SOS).

    Parameters
    ----------
    signal : Signal, None
        The Signal to be filtered. Pass None to create the filter without
        applying it.
    N : int
        The order of the Bessel/Thomson filter
    frequency : number, array like
        The cut off-frequency in Hz if `btype` is ``'lowpass'`` or
        ``'highpass'``. An array
        like containing the lower and upper cut-off frequencies in Hz if
        `btype` is bandpass or bandstop.
    btype : str
        One of the following ``'lowpass'``, ``'highpass'``, ``'bandpass'``,
        ``'bandstop'``. The default is ``'lowpass'``.
    norm : str
        Critical frequency normalization:

        ``'phase'``
            The filter is normalized such that the phase response reaches its
            midpoint at angular (e.g. rad/s) frequency `Wn`. This happens for
            both low-pass and high-pass filters, so this is the
            "phase-matched" case.
            The magnitude response asymptotes are the same as a Butterworth
            filter of the same order with a cutoff of `Wn`.
            This is the default, and matches MATLAB's implementation.
        ``'delay'``
            The filter is normalized such that the group delay in the passband
            is 1/`Wn` (e.g., seconds). This is the "natural" type obtained by
            solving Bessel polynomials.
        ``'mag'``
            The filter is normalized such that the gain magnitude is -3 dB at
            the angular frequency `Wn`.

        The default is 'phase'.
    sampling_rate : None, number
        The sampling rate in Hz. Only required if signal is None. The default
        is None.

    Returns
    -------
    signal : Signal
        The filtered signal. Only returned if ``sampling_rate = None``.
    filter : FilterSOS
        SOS Filter object. Only returned if ``signal = None``.
    """

    # check input
    if (signal is None and sampling_rate is None) \
            or (signal is not None and sampling_rate is not None):
        raise ValueError('Either signal or sampling_rate must be none.')

    # sampling frequency in Hz
    fs = signal.sampling_rate if sampling_rate is None else sampling_rate
    # normalized frequency (half-cycle / per sample)
    frequency_norm = np.asarray(frequency) / fs * 2

    # get filter coefficients
    sos = spsignal.bessel(N,
                          frequency_norm,
                          btype,
                          analog=False,
                          output='sos',
                          norm=norm)

    # generate filter object
    filt = pf.FilterSOS(sos, fs)
    filt.comment = (f"Bessel/Thomson {btype} of order {N} and '{norm}' "
                    f"normalization. Cut-off frequency {frequency} Hz.")

    # return the filter object
    if signal is None:
        # return the filter object
        return filt
    else:
        # return the filtered signal
        signal_filt = filt.process(signal)
        return signal_filt