Source code for voicebox.effects.eq

"""
Equalization effects module.

See ``Filter.build()`` for building basic filter effects.

Simple example:
    >>> from voicebox.effects import Filter
    >>> filter = Filter.build('highpass', 200)
    >>> filter = Filter.build('bandpass', (100, 10000))
"""

from abc import abstractmethod
from dataclasses import dataclass
from functools import lru_cache
from typing import Union, Tuple, Literal, Optional

import numpy as np
from scipy.signal import sosfilt, iirfilter

from voicebox.audio import Audio
from voicebox.effects.effect import Effect

__all__ = [
    "center_to_band",
    "Filter",
    "FilterParamBuilder",
    "IIRFilterParamBuilder",
]

BType = Literal["lowpass", "highpass", "bandpass", "bandstop"]
FType = Literal["butter", "cheby1", "cheby2", "ellip", "bessel"]
Freq = Union[int, float]
Band = Tuple[Freq, Freq]
FreqOrBand = Union[Freq, Band]
SosFilterParam = np.ndarray

# Cache filter parameter building function
iirfilter = lru_cache(iirfilter)


[docs] def center_to_band(freq: Freq, bandwidth: Freq) -> Band: """Converts a center frequency with bandwidth to a frequency band.""" bandwidth /= 2 return freq - bandwidth, freq + bandwidth
[docs] class FilterParamBuilder:
[docs] @abstractmethod def build(self, sample_rate: float) -> SosFilterParam: """Returns filter parameters in ``sos`` format."""
[docs] @dataclass class IIRFilterParamBuilder(FilterParamBuilder): order: int freq: FreqOrBand rp: Optional[float] rs: Optional[float] btype: BType ftype: FType
[docs] def build(self, sample_rate: float) -> SosFilterParam: return iirfilter( self.order, self.freq, rp=self.rp, rs=self.rs, btype=self.btype, analog=False, ftype=self.ftype, output="sos", fs=sample_rate, )
[docs] @dataclass class Filter(Effect): filter_param_builder: FilterParamBuilder
[docs] @classmethod def build( cls, btype: BType, freq: FreqOrBand, order: int = 1, rp: float = None, rs: float = None, ftype: FType = "butter", ) -> "Filter": """ Builds a filter using ``scipy.signal.iirfilter``. See here for details: https://docs.scipy.org/doc/scipy/reference/generated/scipy.signal.iirfilter.html Args: btype (BType): The type of filter. Should be one of ``'lowpass'``, ``'highpass'``, ``'bandpass'``, or ``'bandstop'``. freq (FreqOrBand): The filter frequency in Hz. Should be a single number for lowpass/highpass, or a frequency band as a sequence ``(low_freq, high_freq)`` for bandpass/bandstop. See the ``center_to_band()`` function for easy converting from a center freq with bandwidth to a frequency band. order (int, optional): The order of the filter. Defaults to 1. Higher orders will have faster dropoffs. rp (float, optional): For Chebyshev and elliptic filters, provides the maximum ripple in the passband. (dB) rs (float, optional): For Chebyshev and elliptic filters, provides the minimum attenuation in the stop band. (dB) ftype (FType, optional): The type of IIR filter to design. Defaults to ``'butter'``. Should be one of: - ``'butter'`` (Butterworth) - ``'cheby1'`` (Chebychev I) - ``'cheby2'`` (Chebychev II) - ``'ellip'`` (Cauer/elliptic) - ``'bessel'`` (Bessel/Thomson) Returns: Filter: A Filter instance with the specified parameters. """ param_builder = IIRFilterParamBuilder(order, freq, rp, rs, btype, ftype) return cls(param_builder)
[docs] def apply(self, audio: Audio) -> Audio: filter_params = self.filter_param_builder.build(audio.sample_rate) new_signal = sosfilt(filter_params, audio.signal) return audio.copy(signal=new_signal)