Filters#

TorchFX provides a comprehensive collection of IIR and FIR filters for audio processing.

Base Classes#

IIR#

class torchfx.filter.IIR(fs=None)[source]#

Bases: AbstractFilter

Base class for Infinite Impulse Response (IIR) filters.

This abstract class provides the foundation for all IIR filter implementations in torchfx. It implements lazy coefficient computation, automatic device management, and the core filtering operation using torchaudio.functional.lfilter.

IIR filters are recursive digital filters characterized by their transfer function H(z) = B(z)/A(z), where B(z) are the feedforward (numerator) coefficients and A(z) are the feedback (denominator) coefficients.

Parameters:

fs (int | None)

fs#

The sampling frequency in Hz. Initially None until set from input signal or manually configured.

Type:

int | None

cutoff#

The cutoff frequency in Hz. For different filter types, this may represent the -3dB point, center frequency, or transition frequency.

Type:

float

a#

Denominator (feedback) coefficients of the filter transfer function. None until computed via compute_coefficients().

Type:

Sequence[float] | Tensor | None

b#

Numerator (feedforward) coefficients of the filter transfer function. None until computed via compute_coefficients().

Type:

Sequence[float] | Tensor | None

Notes

Coefficient Computation Flow:
  1. Filter is instantiated with parameters (fs may be None)

  2. On first forward pass, if coefficients are None, compute_coefficients() is called

  3. Coefficients are converted to tensors and moved to input device

  4. Filtering is performed using lfilter from torchaudio

The lazy evaluation pattern minimizes memory usage when filters are instantiated but not immediately used.

See also

AbstractFilter

Base class for all filters

torchfx.filter.fir.FIR

Base class for FIR filters

forward(x)[source]#

Define the computation performed at every call.

Should be overridden by all subclasses.

Note

Although the recipe for forward pass needs to be defined within this function, one should call the Module instance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.

Parameters:

x (Tensor)

Return type:

Tensor

move_coeff(device, dtype=torch.float32)[source]#

Move the filter coefficients to the specified device and dtype.

Parameters:
  • device (Literal['cpu', 'cuda'] | ~torch.device)

  • dtype (dtype)

Return type:

None

FIR Filters#

FIR#

class torchfx.filter.FIR(b)[source]#

Bases: AbstractFilter

Efficient FIR filter implementation using PyTorch conv1d operation.

The FIR class implements a basic Finite Impulse Response filter that accepts pre-computed filter coefficients and applies them using PyTorch’s optimized conv1d operation. This provides GPU-accelerated filtering with automatic device management and support for mono, stereo, and batched audio processing.

FIR filters are always stable (no feedback) and can provide linear phase response when designed with symmetric coefficients. This class serves as the base for DesignableFIR and can also be used directly when you have custom-designed coefficients from external tools or algorithms.

Parameters:

b (array_like) – FIR filter coefficients (numerator). Can be a Python list, NumPy array, or any array-like object. The coefficients define the filter’s impulse response. Length determines filter order (num_taps = len(b)).

kernel#

Registered buffer containing the flipped filter coefficients in shape [1, 1, K] where K is the number of taps. The buffer is automatically moved to the input tensor’s device during forward pass.

Type:

Tensor

a#

Denominator coefficients, always [1.0] for FIR filters (no feedback).

Type:

list[float]

Shape Support
-------------
The forward pass supports three input tensor shapes
- [T]
Type:

Mono audio, single channel

- [C, T]
Type:

Multi-channel audio (e.g., stereo where C=2)

- [B, C, T]
Type:

Batched multi-channel audio

Output shape matches input shape exactly. Time dimension T is preserved through
appropriate padding.
Implementation Details
----------------------
\*\*Convolution-Based Filtering**

Uses PyTorch’s conv1d with grouped convolution where each channel is filtered independently with the same kernel. This is equivalent to applying the FIR filter to each channel separately but is much more efficient.

\*\*Kernel Flipping**

Coefficients are flipped (reversed) before storage to ensure causal convolution behavior compatible with scipy.signal.lfilter. This means the first coefficient in b corresponds to the current sample, the second to the previous sample, etc.

\*\*Padding Strategy**

Right-side padding of (K-1) samples is applied where K is the number of taps. This maintains the original signal length and ensures causal filtering (no future samples influence past outputs).

\*\*Device Management**

The kernel buffer is automatically moved to match the input tensor’s device and dtype. No manual device management is required.

\*\*No Gradient Computation**

All filtering operations execute under @torch.no_grad() context since FIR filters have fixed coefficients (not trainable parameters).

See also

DesignableFIR

FIR filter with automatic coefficient design via window method

torchfx.filter.iir.IIR

IIR filter base class for comparison

AbstractFilter

Base class for all filters

scipy.signal.lfilter

Compatible filtering function in SciPy

Notes

Linear Phase Property:

If coefficients are symmetric (b[i] = b[N-1-i]), the filter has linear phase, meaning constant group delay across all frequencies. This preserves waveform shape without phase distortion, critical for audio mastering and measurement.

Stability:

FIR filters are always BIBO (Bounded Input Bounded Output) stable because they have no poles (denominator is always 1). This makes them suitable for applications where stability is critical.

Computational Complexity:

Time complexity is O(N × K) where N is signal length and K is number of taps. Space complexity is O(K) for kernel storage plus O(N) for padded input.

References

For comprehensive FIR filter theory and design patterns, see wiki/4.2 FIR Filters.md.

For implementation details and performance characteristics, see the “Implementation Details” section in wiki/4.2 FIR Filters.md.

Examples

Basic moving average filter (5-tap):

>>> import torch
>>> from torchfx.filter import FIR
>>>
>>> # Define coefficients for simple moving average
>>> coeffs = [0.2, 0.2, 0.2, 0.2, 0.2]
>>> ma_filter = FIR(b=coeffs)
>>>
>>> # Apply to mono signal
>>> signal = torch.randn(44100)
>>> smoothed = ma_filter(signal)
>>> print(smoothed.shape)  # torch.Size([44100])

Custom FIR coefficients from external design:

>>> import numpy as np
>>> from scipy.signal import firwin
>>>
>>> # Design filter using scipy directly
>>> taps = firwin(numtaps=101, cutoff=5000, fs=44100, window='hamming')
>>>
>>> # Use coefficients in torchfx FIR filter
>>> fir = FIR(b=taps)
>>>
>>> # Apply to audio
>>> audio = torch.randn(44100)
>>> filtered = fir(audio)

Processing stereo audio [C, T]:

>>> # Stereo signal with 2 channels
>>> stereo = torch.randn(2, 44100)
>>>
>>> # Filter applies same coefficients to both channels independently
>>> fir = FIR(b=[0.25, 0.5, 0.25])  # Simple 3-tap filter
>>> filtered_stereo = fir(stereo)
>>> print(filtered_stereo.shape)  # torch.Size([2, 44100])

Batch processing [B, C, T]:

>>> # Batch of 8 stereo signals
>>> batch = torch.randn(8, 2, 44100)
>>>
>>> # Apply filter to entire batch
>>> fir = FIR(b=[0.2, 0.2, 0.2, 0.2, 0.2])
>>> filtered_batch = fir(batch)
>>> print(filtered_batch.shape)  # torch.Size([8, 2, 44100])

GPU acceleration:

>>> # Move input to GPU
>>> signal_gpu = torch.randn(44100, device='cuda')
>>>
>>> # Filter automatically runs on GPU
>>> fir = FIR(b=[0.25, 0.5, 0.25])
>>> filtered_gpu = fir(signal_gpu)
>>> print(filtered_gpu.device)  # cuda:0

Designing a differentiator FIR filter:

>>> # Differentiator approximates derivative
>>> # Simple 3-tap differentiator
>>> diff_filter = FIR(b=[-0.5, 0.0, 0.5])
>>>
>>> # Apply to signal
>>> signal = torch.sin(torch.linspace(0, 10, 1000))
>>> derivative = diff_filter(signal)
>>> # Output approximates cosine

Hilbert transformer for analytic signal:

>>> # Design Hilbert transformer (90-degree phase shift)
>>> # This is a simplified example - real Hilbert needs more taps
>>> from scipy.signal import hilbert as scipy_hilbert
>>> impulse = np.zeros(101)
>>> impulse[50] = 1
>>> analytic = scipy_hilbert(impulse)
>>> hilbert_coeffs = np.imag(analytic)
>>>
>>> # Create FIR filter
>>> hilbert_fir = FIR(b=hilbert_coeffs)
>>>
>>> # Apply to get quadrature component
>>> signal = torch.randn(1000)
>>> quadrature = hilbert_fir(signal)

Using with torchfx.Wave pipeline:

>>> import torchfx as fx
>>>
>>> # Load audio
>>> wave = fx.Wave.from_file("audio.wav")
>>>
>>> # Design custom FIR coefficients
>>> coeffs = [0.1, 0.2, 0.4, 0.2, 0.1]  # Simple lowpass
>>> fir = FIR(b=coeffs)
>>>
>>> # Apply in pipeline
>>> result = wave | fir
>>> result.save("filtered.wav")

Cascading multiple FIR filters:

>>> # Design two FIR filters
>>> fir1 = FIR(b=[0.25, 0.5, 0.25])  # First smoothing stage
>>> fir2 = FIR(b=[0.25, 0.5, 0.25])  # Second smoothing stage
>>>
>>> # Cascade them (equivalent to convolving coefficients)
>>> signal = torch.randn(44100)
>>> filtered = fir2(fir1(signal))
>>>
>>> # Or use pipeline syntax
>>> wave = fx.Wave(signal, fs=44100)
>>> result = wave | fir1 | fir2

Verifying filter stability (always stable for FIR):

>>> # FIR filters are always stable because denominator a = [1.0]
>>> fir = FIR(b=[0.1, 0.2, 0.4, 0.2, 0.1])
>>> print(fir.a)  # [1.0] - no feedback, always stable
>>>
>>> # Even with arbitrary coefficients, FIR is stable
>>> unstable_looking = FIR(b=[100, -200, 100])
>>> print(unstable_looking.a)  # Still [1.0] - stable
compute_coefficients()[source]#

Compute the filter coefficients.

Return type:

None

forward(x)[source]#

Define the computation performed at every call.

Should be overridden by all subclasses.

Note

Although the recipe for forward pass needs to be defined within this function, one should call the Module instance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.

Parameters:

x (Tensor)

Return type:

Tensor

DesignableFIR#

class torchfx.filter.DesignableFIR(cutoff, num_taps, fs=None, pass_zero=True, window='hamming')[source]#

Bases: FIR

FIR filter with automatic coefficient design using the window method.

DesignableFIR extends the base FIR class by automatically computing filter coefficients using the windowed Fourier series method via scipy.signal.firwin. Instead of providing pre-computed coefficients, you specify the desired filter characteristics (cutoff frequency, number of taps, window type) and the class designs the filter for you.

This class is ideal for standard filtering tasks (lowpass, highpass, bandpass, bandstop) where you don’t need custom coefficient design. The window method provides intuitive trade-offs between transition bandwidth and stopband attenuation.

Parameters:
  • cutoff (float or Sequence[float]) –

    Cutoff frequency or frequencies in Hz. The interpretation depends on the number of cutoff values provided:

    • Single value with pass_zero=True: Lowpass filter

    • Single value with pass_zero=False: Highpass filter

    • Two values [low, high] with pass_zero=False: Bandpass filter (low to high)

    • Two values [low, high] with pass_zero=True: Bandstop filter (notch low to high)

  • num_taps (int) –

    Number of filter taps (coefficients). Must be odd for type I FIR filters. Higher values provide sharper frequency transitions but increase computational cost and latency. Typical values:

    • 51-101: Fast, moderate selectivity

    • 101-201: Balanced, good selectivity

    • 201-501: Excellent selectivity, higher latency

  • fs (int or None, optional) – Sampling frequency in Hz. If None, the filter coefficients are not computed during initialization. The sampling frequency can be set later via the fs attribute or automatically when used in a pipeline with Wave objects. Default is None.

  • pass_zero (bool, optional) –

    Controls the filter type when a single cutoff is provided:

    • True: Lowpass (gain of 1 at DC, passes zero frequency)

    • False: Highpass (gain of 0 at DC, blocks zero frequency)

    For two cutoff frequencies:

    • True: Bandstop (notch filter)

    • False: Bandpass

    Default is True (lowpass).

  • window (WindowType, optional) –

    Window function to apply during filter design. Controls the trade-off between main lobe width (transition bandwidth) and side lobe attenuation (stopband ripple). Common options:

    • ”hamming”: Good general-purpose window (-43 dB sidelobes)

    • ”hann”: Smooth transitions (-31 dB sidelobes)

    • ”blackman”: Excellent stopband rejection (-58 dB sidelobes)

    • ”kaiser”: Adjustable characteristics (requires beta parameter)

    • ”bartlett”: Triangular window (-25 dB sidelobes)

    Default is “hamming”.

cutoff#

Stored cutoff frequency or frequencies in Hz.

Type:

float or Sequence[float]

num_taps#

Stored number of filter taps.

Type:

int

fs#

Sampling frequency in Hz. Initially None until set.

Type:

int or None

pass_zero#

Stored pass_zero parameter controlling filter type.

Type:

bool

window#

Stored window function type.

Type:

WindowType

b#

Computed filter coefficients (numerator). None until compute_coefficients() is called, which happens automatically on first use or when fs is set.

Type:

ArrayLike or None

kernel#

Inherited from FIR. Registered buffer containing flipped coefficients.

Type:

Tensor

a#

Inherited from FIR. Always [1.0] for FIR filters.

Type:

list[float]

compute_coefficients()[source]#

Computes FIR filter coefficients using scipy.signal.firwin and initializes the parent FIR class with the computed coefficients. Called automatically when fs is set and coefficients haven’t been computed yet.

Return type:

None

Window Method Theory
--------------------
The window method designs FIR filters by:
1. Starting with the ideal frequency response (infinite impulse response)
2. Applying inverse Fourier transform to get time-domain impulse response
3. Truncating to finite length (num_taps samples)
4. Applying a window function to reduce Gibbs phenomenon (ripple)
Different window functions provide different trade-offs:
- **Narrow main lobe** (e.g., rectangular): Sharp transitions but high ripple
- **Wide main lobe** (e.g., Blackman): Smooth transitions, low ripple
- **Medium** (e.g., Hamming, Hann): Balanced trade-off
Design Guidelines
-----------------
\*\*Choosing Number of Taps**:

Rule of thumb for transition bandwidth Δf at sampling rate fs:

num_taps ≈ (stopband_atten_dB / 22) * (fs / Δf)

For example, -60 dB stopband attenuation with 1000 Hz transition at 44100 Hz:

num_taps ≈ (60 / 22) * (44100 / 1000) ≈ 120

\*\*Choosing Window Type**:
  • Hamming: Good default for most applications

  • Hann: When smooth transitions are more important than stopband rejection

  • Blackman: When maximum stopband rejection is critical (audio mastering)

  • Kaiser: When you need precise control over specifications

\*\*Linear Phase Property**:

All filters designed by this class have linear phase (constant group delay) because the window method produces symmetric coefficients. Group delay is:

group_delay = (num_taps - 1) / (2 * fs) seconds

See also

FIR

Base FIR filter class accepting pre-computed coefficients

torchfx.filter.iir.Butterworth

IIR alternative for comparison

scipy.signal.firwin

Underlying filter design function

scipy.signal.freqz

Analyze designed filter frequency response

Notes

Computational Cost vs IIR:

FIR filters typically require 10-100x more taps than equivalent IIR filters for similar frequency selectivity. However, FIR filters guarantee stability and can provide linear phase, which IIR filters cannot (except at specific frequencies).

Odd vs Even Tap Count:

For most filter types, use odd number of taps. Even tap counts can be used but may not have zero-phase at Nyquist frequency for certain filter types.

Frequency Response Analysis:

Use scipy.signal.freqz to visualize the designed filter’s frequency response:

from scipy.signal import freqz w, h = freqz(filter.b, worN=8000, fs=filter.fs) plt.plot(w, 20*np.log10(abs(h)))

References

For comprehensive filter design examples and window function comparisons, see wiki/4.2 FIR Filters.md.

For API reference and parameter details, see wiki/8.3 torchfx.filter.md.

For guidance on choosing between FIR and IIR filters, see wiki/4 Filters.md.

Examples

Basic lowpass filter design:

>>> import torch
>>> from torchfx.filter import DesignableFIR
>>>
>>> # Design lowpass at 5 kHz with default Hamming window
>>> lpf = DesignableFIR(
...     cutoff=5000.0,
...     num_taps=101,
...     fs=44100,
...     pass_zero=True,
...     window="hamming"
... )
>>>
>>> # Apply to mono signal
>>> signal = torch.randn(44100)
>>> filtered = lpf(signal)
>>> print(filtered.shape)  # torch.Size([44100])

Highpass filter to remove low-frequency rumble:

>>> # Remove frequencies below 100 Hz
>>> hpf = DesignableFIR(
...     cutoff=100.0,
...     num_taps=201,  # Higher taps for sharp low-frequency cutoff
...     fs=44100,
...     pass_zero=False,  # Highpass
...     window="blackman"  # High stopband rejection
... )
>>>
>>> # Apply to audio with DC offset and rumble
>>> audio = torch.randn(44100)
>>> clean = hpf(audio)

Bandpass filter for voice frequencies:

>>> # Extract telephone bandwidth (300-3400 Hz)
>>> bpf = DesignableFIR(
...     cutoff=[300.0, 3400.0],  # Two cutoffs
...     num_taps=201,
...     fs=44100,
...     pass_zero=False,  # Bandpass
...     window="hamming"
... )
>>>
>>> # Apply to speech signal
>>> speech = torch.randn(44100)
>>> telephone_quality = bpf(speech)

Bandstop (notch) filter to remove interference:

>>> # Remove 50-70 Hz hum band (European power line + harmonics)
>>> notch = DesignableFIR(
...     cutoff=[50.0, 70.0],
...     num_taps=301,  # High order for narrow notch
...     fs=44100,
...     pass_zero=True,  # Bandstop
...     window="blackman"
... )
>>>
>>> # Apply to remove power line interference
>>> noisy = torch.randn(44100)
>>> clean = notch(noisy)

Comparing different window types:

>>> import matplotlib.pyplot as plt
>>> from scipy.signal import freqz
>>> import numpy as np
>>>
>>> # Design same filter with different windows
>>> windows = {
...     "hamming": DesignableFIR(5000, 101, 44100, window="hamming"),
...     "hann": DesignableFIR(5000, 101, 44100, window="hann"),
...     "blackman": DesignableFIR(5000, 101, 44100, window="blackman")
... }
>>>
>>> # Plot frequency responses
>>> for name, fir in windows.items():
...     w, h = freqz(fir.b, worN=8000, fs=44100)
...     plt.plot(w, 20*np.log10(abs(h)), label=name)
>>> plt.xlabel("Frequency (Hz)")
>>> plt.ylabel("Magnitude (dB)")
>>> plt.legend()
>>> plt.grid(True)
>>> plt.title("Window Function Comparison")

Exploring the effect of tap count:

>>> # Low, medium, high tap counts
>>> fir_51 = DesignableFIR(5000, num_taps=51, fs=44100)
>>> fir_101 = DesignableFIR(5000, num_taps=101, fs=44100)
>>> fir_201 = DesignableFIR(5000, num_taps=201, fs=44100)
>>>
>>> # Compare frequency responses
>>> from scipy.signal import freqz
>>> for taps, fir in [(51, fir_51), (101, fir_101), (201, fir_201)]:
...     w, h = freqz(fir.b, worN=8000, fs=44100)
...     plt.plot(w, 20*np.log10(abs(h)), label=f"{taps} taps")
>>> plt.xlabel("Frequency (Hz)")
>>> plt.ylabel("Magnitude (dB)")
>>> plt.legend()
>>> plt.title("Effect of Tap Count on Transition Bandwidth")

Deferred coefficient computation (fs=None):

>>> # Create filter without computing coefficients
>>> fir = DesignableFIR(cutoff=5000, num_taps=101, fs=None)
>>> print(fir.b)  # None - coefficients not computed yet
>>>
>>> # Set sampling frequency later
>>> fir.fs = 44100
>>> fir.compute_coefficients()
>>> print(fir.b is not None)  # True - coefficients now computed

Using with torchfx.Wave pipeline (automatic fs configuration):

>>> import torchfx as fx
>>>
>>> # Load audio
>>> wave = fx.Wave.from_file("audio.wav")  # wave.fs = 44100
>>>
>>> # Create filter without fs - will be set from wave
>>> lpf = DesignableFIR(cutoff=8000, num_taps=101, pass_zero=True)
>>> print(lpf.fs)  # None
>>>
>>> # Apply using pipe operator - fs set automatically
>>> filtered = wave | lpf
>>> print(lpf.fs)  # 44100 - set from wave
>>> filtered.save("filtered.wav")

Anti-aliasing filter before downsampling:

>>> # Downsample from 44100 Hz to 22050 Hz
>>> # Design anti-aliasing filter at 0.4 * new_rate
>>> anti_alias = DesignableFIR(
...     cutoff=0.4 * 22050,  # 8820 Hz
...     num_taps=201,
...     fs=44100,
...     pass_zero=True,
...     window="blackman"  # High stopband attenuation
... )
>>>
>>> # Filter then downsample
>>> audio = torch.randn(44100)
>>> filtered = anti_alias(audio)
>>> downsampled = filtered[::2]  # Decimate by factor of 2
>>> print(downsampled.shape)  # torch.Size([22050])

Multi-band processing with multiple DesignableFIR filters:

>>> # Create 3-band crossover
>>> low = DesignableFIR(cutoff=200, num_taps=201, fs=44100, pass_zero=True)
>>> mid = DesignableFIR(cutoff=[200, 2000], num_taps=201, fs=44100, pass_zero=False)
>>> high = DesignableFIR(cutoff=2000, num_taps=201, fs=44100, pass_zero=False)
>>>
>>> # Process each band separately
>>> audio = torch.randn(44100)
>>> low_band = low(audio)
>>> mid_band = mid(audio)
>>> high_band = high(audio)
>>>
>>> # Apply different processing to each band, then recombine
>>> # (processing steps omitted for brevity)
>>> mixed = low_band + mid_band + high_band

GPU acceleration:

>>> # Create signal on GPU
>>> signal_gpu = torch.randn(44100, device="cuda")
>>>
>>> # Design filter
>>> fir = DesignableFIR(5000, num_taps=101, fs=44100, window="hamming")
>>>
>>> # Filter automatically runs on GPU
>>> filtered_gpu = fir(signal_gpu)
>>> print(filtered_gpu.device)  # cuda:0

Batch processing stereo audio:

>>> # Batch of 8 stereo signals
>>> batch = torch.randn(8, 2, 44100)
>>>
>>> # Apply same filter to all signals
>>> lpf = DesignableFIR(cutoff=5000, num_taps=101, fs=44100)
>>> filtered_batch = lpf(batch)
>>> print(filtered_batch.shape)  # torch.Size([8, 2, 44100])

Verifying linear phase property:

>>> # Design filter
>>> fir = DesignableFIR(cutoff=5000, num_taps=101, fs=44100, window="hamming")
>>>
>>> # Check coefficient symmetry (implies linear phase)
>>> import numpy as np
>>> coeffs = np.array(fir.b)
>>> is_symmetric = np.allclose(coeffs, coeffs[::-1])
>>> print(is_symmetric)  # True - linear phase
>>>
>>> # Calculate group delay
>>> group_delay = (fir.num_taps - 1) / (2 * fir.fs)
>>> print(f"Group delay: {group_delay*1000:.2f} ms")  # Constant for all frequencies

Cascading filters for sharper rolloff:

>>> # Two cascaded filters provide steeper rolloff
>>> lpf1 = DesignableFIR(cutoff=5000, num_taps=101, fs=44100)
>>> lpf2 = DesignableFIR(cutoff=5000, num_taps=101, fs=44100)
>>>
>>> # Apply in series
>>> signal = torch.randn(44100)
>>> filtered = lpf2(lpf1(signal))
>>>
>>> # Or use pipeline
>>> import torchfx as fx
>>> wave = fx.Wave(signal, fs=44100)
>>> result = wave | lpf1 | lpf2

Custom window with parameters (Kaiser window):

>>> # Kaiser window allows precise specification of stopband attenuation
>>> # beta parameter controls trade-off: higher beta = more attenuation
>>> kaiser_fir = DesignableFIR(
...     cutoff=5000,
...     num_taps=101,
...     fs=44100,
...     window=("kaiser", 8.0)  # Tuple: (name, beta)
... )
>>>
>>> # Apply filter
>>> signal = torch.randn(44100)
>>> filtered = kaiser_fir(signal)
compute_coefficients()[source]#

Compute the filter coefficients.

Return type:

None

IIR Filters#

Standard Filters#

Butterworth#

class torchfx.filter.Butterworth(btype, cutoff, order=4, order_scale='linear', fs=None, a=None, b=None)[source]#

Bases: IIR

Butterworth filter with maximally flat passband response.

Butterworth filters provide the smoothest possible passband response with no ripple in either passband or stopband. They offer a good balance between frequency selectivity and phase linearity, making them ideal for general-purpose audio filtering applications.

The filter has a monotonic frequency response with -3 dB gain at the cutoff frequency. The rolloff steepness is determined by the filter order, with higher orders providing sharper transitions at the cost of increased phase distortion.

Parameters:
  • btype (str) – Filter type. One of: - “lowpass”: Passes frequencies below cutoff - “highpass”: Passes frequencies above cutoff - “bandpass”: Passes frequencies within a band - “bandstop”: Rejects frequencies within a band

  • cutoff (float) – Cutoff frequency in Hz. This is the -3 dB point for lowpass/highpass filters.

  • order (int, default=4) – Filter order. Higher orders provide steeper rolloff but increased phase distortion. The rolloff rate is approximately 6 * order dB per octave. Typical values: 2-8 for audio applications.

  • order_scale ({"linear", "db"}, default="linear") – Order scaling mode: - “linear”: Use order value directly - “db”: Divide order by 6 (useful for octave-based specifications)

  • fs (int | None, default=None) – Sampling frequency in Hz. If None, will be set automatically from the input signal.

  • a (Sequence[float] | None, default=None) – Pre-computed denominator coefficients. If provided, coefficient computation is skipped.

  • b (Sequence[float] | None, default=None) – Pre-computed numerator coefficients. If provided, coefficient computation is skipped.

btype#

The filter type.

Type:

str

order#

The effective filter order (after scaling).

Type:

int

Examples

Low-pass filter at 1 kHz with 4th order:

>>> import torchfx as fx
>>> import torch
>>> lpf = fx.filter.Butterworth(btype="lowpass", cutoff=1000, order=4, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

High-pass filter to remove subsonic frequencies:

>>> hpf = fx.filter.Butterworth(btype="highpass", cutoff=20, order=2, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> clean = wave | hpf

Comparing different orders (steeper rolloff with higher order):

>>> # 2nd order: gentle 12 dB/octave rolloff
>>> lpf2 = fx.filter.Butterworth(btype="lowpass", cutoff=1000, order=2, fs=44100)
>>> # 8th order: steep 48 dB/octave rolloff
>>> lpf8 = fx.filter.Butterworth(btype="lowpass", cutoff=1000, order=8, fs=44100)

Notes

Frequency Response Characteristics:
  • Passband: Maximally flat (no ripple)

  • Stopband: Monotonic rolloff, no ripple

  • -3 dB point: Exactly at cutoff frequency

  • Rolloff rate: ~6 * order dB/octave in stopband

  • Phase response: Nonlinear (increases with order)

The Butterworth filter is designed to have a frequency response magnitude that is as flat as possible in the passband. The squared magnitude response is:

|H(jω)|² = 1 / (1 + (ω/ωc)^(2n))

where n is the filter order and ωc is the cutoff frequency.

For audio applications, orders 2-4 are common for gentle filtering, while orders 6-8 provide sharper transitions. Very high orders (>12) may exhibit numerical instability.

References

See also

HiButterworth

High-pass Butterworth convenience class

LoButterworth

Low-pass Butterworth convenience class

Chebyshev1

Type 1 Chebyshev filter with passband ripple

Elliptic

Elliptic filter with optimized rolloff

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

class torchfx.filter.HiButterworth(cutoff, order=5, order_scale='linear', fs=None)[source]#

Bases: Butterworth

High-pass Butterworth filter convenience class.

This is a convenience class that creates a high-pass Butterworth filter by automatically setting btype=”highpass”. Provides maximally flat response in the passband with smooth rolloff.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz (-3 dB point). Frequencies below this are attenuated.

  • order (int, default=5) – Filter order. Determines rolloff steepness (~6*order dB/octave).

  • order_scale ({"linear", "db"}, default="linear") – Order scaling mode.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Remove DC offset and subsonic frequencies:

>>> import torchfx as fx
>>> hpf = fx.filter.HiButterworth(cutoff=20, order=2, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> clean = wave | hpf

Remove low-frequency rumble with gentle slope:

>>> hpf = fx.filter.HiButterworth(cutoff=80, order=4, fs=44100)
>>> wave = fx.Wave.from_file("recording.wav")
>>> filtered = wave | hpf

See also

Butterworth

Base Butterworth filter class

LoButterworth

Low-pass variant

class torchfx.filter.LoButterworth(cutoff, order=5, order_scale='linear', fs=None)[source]#

Bases: Butterworth

Low-pass Butterworth filter convenience class.

This is a convenience class that creates a low-pass Butterworth filter by automatically setting btype=”lowpass”. Provides maximally flat response in the passband with smooth rolloff.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz (-3 dB point). Frequencies above this are attenuated.

  • order (int, default=5) – Filter order. Determines rolloff steepness (~6*order dB/octave).

  • order_scale ({"linear", "db"}, default="linear") – Order scaling mode.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Remove high-frequency noise:

>>> import torchfx as fx
>>> lpf = fx.filter.LoButterworth(cutoff=8000, order=4, fs=44100)
>>> wave = fx.Wave.from_file("noisy.wav")
>>> clean = wave | lpf

Telephone bandwidth simulation:

>>> lpf = fx.filter.LoButterworth(cutoff=3400, order=6, fs=44100)
>>> wave = fx.Wave.from_file("voice.wav")
>>> telephone = wave | lpf

See also

Butterworth

Base Butterworth filter class

HiButterworth

High-pass variant

Chebyshev Type I#

class torchfx.filter.Chebyshev1(btype, cutoff, order=4, ripple=0.1, fs=None, a=None, b=None)[source]#

Bases: IIR

Chebyshev Type I filter with equalized passband ripple.

Chebyshev Type I filters trade passband ripple for steeper rolloff compared to Butterworth filters. They provide sharper transitions between passband and stopband for a given filter order, making them suitable when aggressive frequency selectivity is needed and some passband ripple is acceptable.

The filter exhibits equalized ripple in the passband (oscillates between 1 and 1-ε where ε is determined by the ripple parameter) and monotonic rolloff in the stopband.

Parameters:
  • btype (str) – Filter type. One of: - “lowpass”: Passes frequencies below cutoff - “highpass”: Passes frequencies above cutoff - “bandpass”: Passes frequencies within a band - “bandstop”: Rejects frequencies within a band

  • cutoff (float) – Cutoff frequency in Hz. For lowpass/highpass, this is the frequency where the response exits the ripple band and enters the stopband.

  • order (int, default=4) – Filter order. Determines both the number of ripples in the passband (order ripples) and the rolloff steepness. Higher orders provide steeper rolloff.

  • ripple (float, default=0.1) – Maximum passband ripple in dB. Typical values: - 0.01 dB: Minimal ripple, closer to Butterworth response - 0.1 dB: Good balance (default) - 0.5 dB: Noticeable ripple but very sharp rolloff - 1.0 dB: Aggressive rolloff, significant passband distortion

  • fs (int | None, default=None) – Sampling frequency in Hz. If None, will be set automatically from the input signal.

  • a (Sequence[float] | None, default=None) – Pre-computed denominator coefficients.

  • b (Sequence[float] | None, default=None) – Pre-computed numerator coefficients.

btype#

The filter type.

Type:

str

order#

The filter order.

Type:

int

ripple#

Maximum passband ripple in dB.

Type:

float

Examples

Low-pass filter with tight cutoff and minimal ripple:

>>> import torchfx as fx
>>> lpf = fx.filter.Chebyshev1(btype="lowpass", cutoff=5000, order=4,
...                            ripple=0.1, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

High-pass filter with aggressive rolloff:

>>> hpf = fx.filter.Chebyshev1(btype="highpass", cutoff=100, order=6,
...                            ripple=0.5, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | hpf

Comparing ripple settings (more ripple = steeper rolloff):

>>> # Minimal ripple: gentler rolloff, closer to Butterworth
>>> cheby_gentle = fx.filter.Chebyshev1(btype="lowpass", cutoff=1000,
...                                     order=4, ripple=0.01, fs=44100)
>>> # Higher ripple: steeper rolloff
>>> cheby_steep = fx.filter.Chebyshev1(btype="lowpass", cutoff=1000,
...                                    order=4, ripple=1.0, fs=44100)

Notes

Frequency Response Characteristics:
  • Passband: Equalized ripple (oscillates between 0 and -ripple dB)

  • Stopband: Monotonic rolloff, no ripple

  • Transition: Sharper than Butterworth for same order

  • Phase response: More nonlinear than Butterworth

The ripple parameter controls the trade-off between passband flatness and rolloff steepness. Smaller ripple values produce responses closer to Butterworth, while larger values provide sharper transitions at the cost of passband distortion.

For audio applications where passband flatness is critical (e.g., mastering), use small ripple values (0.01-0.1 dB). For aggressive filtering where some passband coloration is acceptable (e.g., anti-aliasing), larger ripple values (0.5-1.0 dB) provide better stopband attenuation.

The Chebyshev Type I response is defined by:

|H(jω)|² = 1 / (1 + ε² Tₙ²(ω/ωc))

where Tₙ is the nth-order Chebyshev polynomial and ε is related to the ripple.

See also

HiChebyshev1

High-pass Chebyshev Type I convenience class

LoChebyshev1

Low-pass Chebyshev Type I convenience class

Chebyshev2

Type II Chebyshev with stopband ripple

Butterworth

Butterworth filter with no ripple

Elliptic

Elliptic filter with ripple in both bands

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

class torchfx.filter.HiChebyshev1(cutoff, order=4, ripple=0.1, fs=None)[source]#

Bases: Chebyshev1

High-pass Chebyshev Type I filter convenience class.

This is a convenience class that creates a high-pass Chebyshev Type I filter by automatically setting btype=”highpass”. High-pass filters attenuate frequencies below the cutoff while passing higher frequencies.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz. Frequencies below this are attenuated.

  • order (int, default=4) – Filter order. Higher values provide steeper rolloff.

  • ripple (float, default=0.1) – Maximum passband ripple in dB.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Remove low-frequency rumble:

>>> import torchfx as fx
>>> hpf = fx.filter.HiChebyshev1(cutoff=80, order=4, ripple=0.1, fs=44100)
>>> wave = fx.Wave.from_file("recording.wav")
>>> clean = wave | hpf

See also

Chebyshev1

Base Chebyshev Type I filter class

LoChebyshev1

Low-pass variant

class torchfx.filter.LoChebyshev1(cutoff, order=4, ripple=0.1, fs=None)[source]#

Bases: Chebyshev1

Low-pass Chebyshev Type I filter convenience class.

This is a convenience class that creates a low-pass Chebyshev Type I filter by automatically setting btype=”lowpass”. Low-pass filters attenuate frequencies above the cutoff while passing lower frequencies.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz. Frequencies above this are attenuated.

  • order (int, default=4) – Filter order. Higher values provide steeper rolloff.

  • ripple (float, default=0.1) – Maximum passband ripple in dB.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Anti-aliasing filter before downsampling:

>>> import torchfx as fx
>>> lpf = fx.filter.LoChebyshev1(cutoff=8000, order=6, ripple=0.5, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

See also

Chebyshev1

Base Chebyshev Type I filter class

HiChebyshev1

High-pass variant

Chebyshev Type II#

class torchfx.filter.Chebyshev2(btype, cutoff, order=4, ripple=0.1, fs=None)[source]#

Bases: IIR

Chebyshev Type II filter with equalized stopband ripple.

Chebyshev Type II filters (also called inverse Chebyshev) have a monotonic passband response and equalized ripple in the stopband. They offer a compromise between Butterworth (no ripple anywhere) and Chebyshev Type I (passband ripple), providing sharper rolloff than Butterworth while maintaining a clean passband.

The filter is characterized by a maximally flat passband (like Butterworth) but with oscillations in the stopband that allow for steeper rolloff at a given order.

Parameters:
  • btype (str) – Filter type. One of: - “lowpass”: Passes frequencies below cutoff - “highpass”: Passes frequencies above cutoff - “bandpass”: Passes frequencies within a band - “bandstop”: Rejects frequencies within a band

  • cutoff (float) – Cutoff frequency in Hz. This is the frequency where the monotonic passband response transitions to the oscillating stopband.

  • order (int, default=4) – Filter order. Higher orders provide steeper rolloff and more stopband oscillations.

  • ripple (float, default=0.1) – Minimum stopband attenuation in dB. This specifies how much the stopband oscillates above the final attenuation level. Typical values: - 20 dB: Moderate stopband attenuation - 40 dB: Good stopband attenuation (common default) - 60 dB: Strong stopband attenuation - 80 dB: Very strong stopband rejection

  • fs (int | None, default=None) – Sampling frequency in Hz. If None, will be set automatically from the input signal.

btype#

The filter type.

Type:

str

order#

The filter order.

Type:

int

ripple#

Minimum stopband attenuation in dB.

Type:

float

Examples

Low-pass filter with clean passband:

>>> import torchfx as fx
>>> lpf = fx.filter.Chebyshev2(btype="lowpass", cutoff=5000, order=4,
...                            ripple=40, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

High-pass filter with strong stopband rejection:

>>> hpf = fx.filter.Chebyshev2(btype="highpass", cutoff=100, order=6,
...                            ripple=60, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | hpf

Anti-aliasing filter with very high stopband attenuation:

>>> aa_filter = fx.filter.Chebyshev2(btype="lowpass", cutoff=18000,
...                                  order=8, ripple=80, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | aa_filter

Notes

Frequency Response Characteristics:
  • Passband: Monotonic, maximally flat (no ripple)

  • Stopband: Equalized ripple, oscillates around final attenuation level

  • Transition: Sharper than Butterworth, less sharp than Type I Chebyshev

  • Phase response: Nonlinear, but often better than Type I

Chebyshev Type II filters are ideal when you need:
  • Clean passband (no frequency-dependent gain variations)

  • Sharper rolloff than Butterworth

  • Acceptance of stopband ripple (usually inaudible for audio)

The ripple parameter in Type II filters has opposite meaning from Type I: it specifies the minimum stopband attenuation rather than passband ripple. Higher ripple values mean better stopband rejection but require steeper transition bands.

For audio applications, Type II is often preferred over Type I because:
  • Passband stays clean (no coloration of wanted frequencies)

  • Stopband ripple is usually irrelevant (already heavily attenuated)

  • Good compromise between Butterworth and Type I characteristics

See also

HiChebyshev2

High-pass Chebyshev Type II convenience class

LoChebyshev2

Low-pass Chebyshev Type II convenience class

Chebyshev1

Type I Chebyshev with passband ripple

Butterworth

Butterworth filter with no ripple

Elliptic

Elliptic filter with ripple in both bands

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

class torchfx.filter.HiChebyshev2(cutoff, order=4, ripple=0.1, fs=None)[source]#

Bases: Chebyshev2

High-pass Chebyshev Type II filter convenience class.

This is a convenience class that creates a high-pass Chebyshev Type II filter by automatically setting btype=”highpass”. Provides clean passband with stopband ripple.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz. Frequencies below this are attenuated.

  • order (int, default=4) – Filter order. Higher values provide steeper rolloff.

  • ripple (float, default=0.1) – Minimum stopband attenuation in dB.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

>>> import torchfx as fx
>>> hpf = fx.filter.HiChebyshev2(cutoff=100, order=4, ripple=40, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | hpf

See also

Chebyshev2

Base Chebyshev Type II filter class

LoChebyshev2

Low-pass variant

class torchfx.filter.LoChebyshev2(cutoff, order=4, ripple=0.1, fs=None)[source]#

Bases: Chebyshev2

Low-pass Chebyshev Type II filter convenience class.

This is a convenience class that creates a low-pass Chebyshev Type II filter by automatically setting btype=”lowpass”. Provides clean passband with stopband ripple.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz. Frequencies above this are attenuated.

  • order (int, default=4) – Filter order. Higher values provide steeper rolloff.

  • ripple (float, default=0.1) – Minimum stopband attenuation in dB.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

>>> import torchfx as fx
>>> lpf = fx.filter.LoChebyshev2(cutoff=5000, order=4, ripple=40, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

See also

Chebyshev2

Base Chebyshev Type II filter class

HiChebyshev2

High-pass variant

Elliptic#

class torchfx.filter.Elliptic(btype, cutoff, order=4, passband_ripple=0.1, stopband_attenuation=40, fs=None)[source]#

Bases: IIR

Elliptic (Cauer) filter with optimal transition bandwidth.

Elliptic filters (also called Cauer filters) achieve the sharpest possible transition between passband and stopband for a given filter order. They do this by allowing equalized ripple in both the passband and stopband, making them optimal when you need maximum frequency selectivity with minimum filter order.

Of all classical IIR filter types, elliptic filters provide:
  • Steepest rolloff for a given order

  • Lowest order for given specifications

  • Most aggressive frequency separation

However, they also have:
  • Ripple in both passband and stopband

  • Most nonlinear phase response

  • Potential for passband coloration

Parameters:
  • btype (str) – Filter type: - “lowpass”: Passes frequencies below cutoff - “highpass”: Passes frequencies above cutoff - “bandpass”: Passes frequencies within a band - “bandstop”: Rejects frequencies within a band

  • cutoff (float) – Cutoff frequency in Hz. This is the edge of the passband where ripple transitions to stopband.

  • order (int, default=4) – Filter order. For elliptic filters, even low orders provide steep rolloff. Typical values: 3-6 for most applications.

  • passband_ripple (float, default=0.1) – Maximum passband ripple in dB. Controls the amount of gain variation allowed in the passband. Typical values: - 0.01 dB: Minimal ripple, closer to Chebyshev Type II - 0.1 dB: Good balance (default) - 0.5 dB: More aggressive rolloff, noticeable ripple - 1.0 dB: Very steep rolloff, significant passband distortion

  • stopband_attenuation (float, default=40) – Minimum stopband attenuation in dB. Specifies how much the stopband is attenuated below 0 dB. Typical values: - 20 dB: Light attenuation - 40 dB: Good attenuation (default) - 60 dB: Strong attenuation - 80+ dB: Very strong rejection

  • fs (int | None, default=None) – Sampling frequency in Hz.

btype#

The filter type.

Type:

str

order#

The filter order.

Type:

int

passband_ripple#

Maximum passband ripple in dB.

Type:

float

stopband_attenuation#

Minimum stopband attenuation in dB.

Type:

float

Examples

Steep low-pass filter for anti-aliasing:

>>> import torchfx as fx
>>> lpf = fx.filter.Elliptic(btype="lowpass", cutoff=18000, order=5,
...                          passband_ripple=0.1, stopband_attenuation=60,
...                          fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

Aggressive high-pass filter with minimal order:

>>> hpf = fx.filter.Elliptic(btype="highpass", cutoff=100, order=3,
...                          passband_ripple=0.5, stopband_attenuation=40,
...                          fs=44100)
>>> wave = fx.Wave.from_file("recording.wav")
>>> clean = wave | hpf

Comparing ripple settings (more ripple = steeper rolloff):

>>> # Conservative: minimal passband ripple
>>> ellip_gentle = fx.filter.Elliptic(btype="lowpass", cutoff=5000, order=4,
...                                   passband_ripple=0.01, stopband_attenuation=40,
...                                   fs=44100)
>>> # Aggressive: higher passband ripple for steeper transition
>>> ellip_steep = fx.filter.Elliptic(btype="lowpass", cutoff=5000, order=4,
...                                  passband_ripple=1.0, stopband_attenuation=60,
...                                  fs=44100)

Very low order filter (3rd order elliptic can match 6th order Butterworth):

>>> ellip3 = fx.filter.Elliptic(btype="lowpass", cutoff=1000, order=3,
...                             passband_ripple=0.1, stopband_attenuation=40,
...                             fs=44100)

Notes

Frequency Response Characteristics:
  • Passband: Equalized ripple (oscillates between 0 and -ripple dB)

  • Stopband: Equalized ripple (oscillates around -attenuation dB)

  • Transition: Sharpest of all classical filters

  • Phase response: Highly nonlinear (worst of classical filters)

Design Trade-offs:
  • Increasing passband_ripple → Steeper transition

  • Increasing stopband_attenuation → Steeper transition

  • Increasing order → Steeper transition and more ripples

  • All three parameters are interdependent in the design

When to Use Elliptic Filters:

✓ Need steepest possible rolloff ✓ Filter order is constrained (computational resources) ✓ Phase linearity is not critical ✓ Can tolerate some passband and stopband ripple ✓ Anti-aliasing before downsampling ✓ Interference rejection with narrow transition bands

When NOT to Use:

✗ Phase linearity is important (use FIR instead) ✗ Passband must be perfectly flat (use Butterworth) ✗ Critical listening applications (use Chebyshev Type II or Butterworth)

Comparison with other filters (for same specifications):
  • Elliptic: Lowest order, steepest rolloff, ripple everywhere

  • Chebyshev I: Higher order, passband ripple only

  • Chebyshev II: Higher order, stopband ripple only

  • Butterworth: Highest order, no ripple, smoothest response

The elliptic filter design is based on elliptic rational functions (Jacobi elliptic functions), which allow the equiripple behavior in both bands. The filter achieves optimal Chebyshev approximation in both passband and stopband simultaneously.

Caution: Very high stopband attenuation requirements (>80 dB) combined with tight passband ripple (<0.1 dB) may result in numerical instability or require high filter orders.

References

See also

HiElliptic

High-pass elliptic convenience class

LoElliptic

Low-pass elliptic convenience class

Chebyshev1

Type I Chebyshev with passband ripple only

Chebyshev2

Type II Chebyshev with stopband ripple only

Butterworth

Butterworth filter with no ripple

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

class torchfx.filter.HiElliptic(cutoff, order=4, passband_ripple=0.1, stopband_attenuation=40, fs=None)[source]#

Bases: Elliptic

High-pass Elliptic filter convenience class.

This convenience class creates a high-pass elliptic filter by automatically setting btype=”highpass”. Provides the steepest possible high-pass rolloff for a given filter order, with ripple in both passband and stopband.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz. Frequencies below this are attenuated.

  • order (int, default=4) – Filter order. Even low orders (3-4) provide steep rolloff.

  • passband_ripple (float, default=0.1) – Maximum passband ripple in dB.

  • stopband_attenuation (float, default=40) – Minimum stopband attenuation in dB.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Remove low frequencies with aggressive rolloff:

>>> import torchfx as fx
>>> hpf = fx.filter.HiElliptic(cutoff=100, order=4, passband_ripple=0.1,
...                            stopband_attenuation=60, fs=44100)
>>> wave = fx.Wave.from_file("recording.wav")
>>> clean = wave | hpf

Low-order but steep filter:

>>> hpf = fx.filter.HiElliptic(cutoff=80, order=3, passband_ripple=0.5,
...                            stopband_attenuation=40, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | hpf

See also

Elliptic

Base elliptic filter class

LoElliptic

Low-pass variant

HiChebyshev1

High-pass with passband ripple only

class torchfx.filter.LoElliptic(cutoff, order=4, passband_ripple=0.1, stopband_attenuation=40, fs=None)[source]#

Bases: Elliptic

Low-pass Elliptic filter convenience class.

This convenience class creates a low-pass elliptic filter by automatically setting btype=”lowpass”. Provides the steepest possible low-pass rolloff for a given filter order, with ripple in both passband and stopband.

Parameters:
  • cutoff (float) – Cutoff frequency in Hz. Frequencies above this are attenuated.

  • order (int, default=4) – Filter order. Even low orders (3-4) provide steep rolloff.

  • passband_ripple (float, default=0.1) – Maximum passband ripple in dB.

  • stopband_attenuation (float, default=40) – Minimum stopband attenuation in dB.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Steep anti-aliasing filter before downsampling:

>>> import torchfx as fx
>>> lpf = fx.filter.LoElliptic(cutoff=18000, order=5, passband_ripple=0.1,
...                            stopband_attenuation=60, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | lpf

Minimal order noise reduction:

>>> lpf = fx.filter.LoElliptic(cutoff=8000, order=3, passband_ripple=0.5,
...                            stopband_attenuation=40, fs=44100)
>>> wave = fx.Wave.from_file("noisy.wav")
>>> clean = wave | lpf

See also

Elliptic

Base elliptic filter class

HiElliptic

High-pass variant

LoChebyshev1

Low-pass with passband ripple only

Linkwitz-Riley#

class torchfx.filter.LinkwitzRiley(btype, cutoff, order=4, order_scale='linear', fs=None)[source]#

Bases: IIR

Linkwitz-Riley crossover filter for speaker systems.

Linkwitz-Riley filters are designed specifically for crossover applications where audio is split into multiple frequency bands for different speakers (e.g., woofer, tweeter). They are created by cascading two identical Butterworth filters, resulting in several desirable properties:

  1. -6 dB gain at the cutoff frequency for both high-pass and low-pass sections

  2. Flat magnitude response when high-pass and low-pass outputs are summed

  3. Zero phase difference between outputs at crossover frequency

  4. Complementary magnitude responses

These properties make Linkwitz-Riley filters ideal for multi-way speaker systems, ensuring smooth transitions between drivers with no amplitude or phase anomalies.

The filter order must be an even integer (2, 4, 8, etc.) because it’s formed by cascading two Butterworth filters. The effective order is twice that of each Butterworth stage.

Parameters:
  • btype (str) – Filter type: “lowpass” or “highpass”. For crossover applications, use matching pairs of low-pass and high-pass filters at the same frequency.

  • cutoff (float) – Crossover frequency in Hz. This is where both the low-pass and high-pass filters will be at -6 dB. Common crossover frequencies: - 80-120 Hz: Subwoofer crossover - 200-500 Hz: Woofer-midrange crossover - 2000-3500 Hz: Midrange-tweeter crossover

  • order (int, default=4) – Filter order. Must be a positive even integer (2, 4, 8, 12, etc.). Common orders: - 2nd order (12 dB/octave): Gentle slope, good phase - 4th order (24 dB/octave): Standard for many systems - 8th order (48 dB/octave): Steep slope for problem drivers

  • order_scale ({"linear", "db"}, default="linear") – Order scaling mode.

  • fs (int | None, default=None) – Sampling frequency in Hz.

btype#

The filter type.

Type:

str

order#

The filter order (must be even).

Type:

int

Examples

Two-way crossover at 2 kHz (woofer and tweeter):

>>> import torchfx as fx
>>> lowpass = fx.filter.LinkwitzRiley(btype="lowpass", cutoff=2000, order=4, fs=44100)
>>> highpass = fx.filter.LinkwitzRiley(btype="highpass", cutoff=2000, order=4, fs=44100)
>>> wave = fx.Wave.from_file("full_range.wav")
>>> woofer_signal = wave | lowpass
>>> tweeter_signal = wave | highpass
>>> # When combined: woofer_signal + tweeter_signal = wave (flat response)

Subwoofer crossover at 80 Hz:

>>> sub_lpf = fx.filter.LoLinkwitzRiley(cutoff=80, order=4, fs=44100)
>>> main_hpf = fx.filter.HiLinkwitzRiley(cutoff=80, order=4, fs=44100)
>>> wave = fx.Wave.from_file("music.wav")
>>> subwoofer = wave | sub_lpf
>>> mains = wave | main_hpf

Three-way crossover system:

>>> # Low: <200 Hz, Mid: 200-3000 Hz, High: >3000 Hz
>>> low = fx.filter.LoLinkwitzRiley(cutoff=200, order=4, fs=44100)
>>> mid_hp = fx.filter.HiLinkwitzRiley(cutoff=200, order=4, fs=44100)
>>> mid_lp = fx.filter.LoLinkwitzRiley(cutoff=3000, order=4, fs=44100)
>>> high = fx.filter.HiLinkwitzRiley(cutoff=3000, order=4, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> woofer = wave | low
>>> midrange = wave | mid_hp | mid_lp
>>> tweeter = wave | high

Steep 8th-order crossover for difficult driver pairing:

>>> lpf = fx.filter.LoLinkwitzRiley(cutoff=500, order=8, fs=44100)
>>> hpf = fx.filter.HiLinkwitzRiley(cutoff=500, order=8, fs=44100)

Notes

Key Properties of Linkwitz-Riley Filters:
  • Magnitude sum: |H_LP|² + |H_HP|² = 1 (flat combined response)

  • Phase alignment: 0° difference at crossover frequency

  • Slope: 6 * order dB/octave (e.g., 24 dB/octave for 4th order)

  • Crossover point: -6 dB for both filters

Advantages over other crossover designs:
  • No peaks or dips when signals are summed

  • Excellent phase coherence

  • Predictable, well-behaved response

  • Industry-standard for professional audio

Common crossover orders:
  • 2nd order (LR2): 12 dB/octave, wide transition, good for close drivers

  • 4th order (LR4): 24 dB/octave, most popular, good all-around

  • 8th order (LR8): 48 dB/octave, steep isolation, for problem cases

The filter is formed by cascading two Butterworth filters of half the specified order. For example, a 4th-order LR filter uses two 2nd-order Butterworth filters. This is implemented by convolving the Butterworth coefficients with themselves.

Physical interpretation: The -6 dB crossover point means each driver contributes equally to the acoustic output at the crossover frequency, resulting in flat total response when properly aligned.

References

See also

HiLinkwitzRiley

High-pass Linkwitz-Riley convenience class

LoLinkwitzRiley

Low-pass Linkwitz-Riley convenience class

Butterworth

Base Butterworth filter used in LR construction

compute_coefficients()[source]#

Compute the filter coefficients.

The method calculates the coefficients for a Butterworth filter of half the specified order and then cascades it with itself by convolving the numerator and denominator coefficients.

Return type:

None

class torchfx.filter.HiLinkwitzRiley(cutoff, order=4, order_scale='linear', fs=None)[source]#

Bases: LinkwitzRiley

High-pass Linkwitz-Riley crossover filter convenience class.

This convenience class creates a high-pass Linkwitz-Riley filter by automatically setting btype=”highpass”. Used in crossover networks to send high frequencies to tweeters or mid-range drivers.

Parameters:
  • cutoff (float) – Crossover frequency in Hz (-6 dB point).

  • order (int, default=4) – Filter order (must be even). Common values: 2, 4, 8.

  • order_scale ({"linear", "db"}, default="linear") – Order scaling mode.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Tweeter crossover at 3 kHz:

>>> import torchfx as fx
>>> hpf = fx.filter.HiLinkwitzRiley(cutoff=3000, order=4, fs=44100)
>>> wave = fx.Wave.from_file("full_range.wav")
>>> tweeter = wave | hpf

See also

LinkwitzRiley

Base Linkwitz-Riley filter class

LoLinkwitzRiley

Low-pass variant

class torchfx.filter.LoLinkwitzRiley(cutoff, order=4, order_scale='linear', fs=None)[source]#

Bases: LinkwitzRiley

Low-pass Linkwitz-Riley crossover filter convenience class.

This convenience class creates a low-pass Linkwitz-Riley filter by automatically setting btype=”lowpass”. Used in crossover networks to send low frequencies to woofers or subwoofers.

Parameters:
  • cutoff (float) – Crossover frequency in Hz (-6 dB point).

  • order (int, default=4) – Filter order (must be even). Common values: 2, 4, 8.

  • order_scale ({"linear", "db"}, default="linear") – Order scaling mode.

  • fs (int | None, default=None) – Sampling frequency in Hz.

Examples

Subwoofer crossover at 80 Hz:

>>> import torchfx as fx
>>> lpf = fx.filter.LoLinkwitzRiley(cutoff=80, order=4, fs=44100)
>>> wave = fx.Wave.from_file("music.wav")
>>> subwoofer = wave | lpf

See also

LinkwitzRiley

Base Linkwitz-Riley filter class

HiLinkwitzRiley

High-pass variant

Equalizers and Specialty Filters#

Shelving Filters#

class torchfx.filter.HiShelving(cutoff, q, gain, gain_scale='linear', fs=None)[source]#

Bases: Shelving

High-frequency shelving filter for treble control.

A high-shelving filter boosts or cuts frequencies above the cutoff frequency. This filter is commonly used in audio equalization to adjust treble/brightness, air frequencies, or apply de-emphasis curves. It provides smooth, gradual transitions similar to analog tone controls.

The filter affects all frequencies above the cutoff with a constant gain in dB, creating a shelf-like frequency response. The transition steepness is controlled by the Q parameter.

Parameters:
  • cutoff (float) – Transition frequency in Hz. Frequencies above this will be boosted or cut. Common values: - 2000-4000 Hz: Presence boost/cut - 6000-8000 Hz: Brightness control - 10000-12000 Hz: Air/sparkle adjustment

  • q (float) – Quality factor controlling transition steepness. Typical values: - 0.5: Gentle, analog-like transition - 0.707: Standard transition (Butterworth-like) - 1.0: Steeper transition

  • gain (float) – Shelf gain amount. Interpretation depends on gain_scale: - For “linear”: Linear gain factor (e.g., 2.0 for +6 dB) - For “db”: Gain in dB (e.g., 6.0 for +6 dB boost, -3.0 for cut)

  • gain_scale ({"linear", "db"}, default="linear") – How to interpret the gain parameter: - “linear”: Direct linear gain multiplier - “db”: Gain in decibels (converted internally to linear)

  • fs (int | None, default=None) – Sampling frequency in Hz.

gain#

The linear gain factor (converted from dB if necessary).

Type:

float

Examples

Boost high frequencies for brightness:

>>> import torchfx as fx
>>> shelf = fx.filter.HiShelving(cutoff=8000, q=0.707, gain=3, gain_scale="db", fs=44100)
>>> wave = fx.Wave.from_file("dull_recording.wav")
>>> bright = wave | shelf

Cut harsh high frequencies:

>>> shelf = fx.filter.HiShelving(cutoff=6000, q=0.5, gain=-4, gain_scale="db", fs=44100)
>>> wave = fx.Wave.from_file("harsh.wav")
>>> smooth = wave | shelf

Add air to vocals:

>>> air_shelf = fx.filter.HiShelving(cutoff=12000, q=0.707, gain=2, gain_scale="db", fs=44100)
>>> vocals = fx.Wave.from_file("vocals.wav")
>>> airy_vocals = vocals | air_shelf

Using linear gain (2.0 = approximately +6 dB):

>>> shelf = fx.filter.HiShelving(cutoff=10000, q=0.707, gain=2.0, gain_scale="linear", fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | shelf

Notes

High-shelving filters are one of the most commonly used EQ types in audio production. They’re essential for:

  • Brightening dull recordings

  • Taming harsh cymbals or sibilance

  • Adding presence to vocals

  • Adjusting overall tonal balance

The filter uses the Audio EQ Cookbook biquad equations, providing accurate analog-style shelving response. The Q parameter affects how quickly the transition occurs but doesn’t affect the final shelf gain.

For subtle, musical adjustments, use moderate Q values (0.5-0.707) and moderate gains (±3 to ±6 dB). Higher Q values create steeper transitions but may sound less natural.

See also

LoShelving

Low-frequency shelving filter

ParametricEQ

Parametric bell-shaped filter

Shelving

Base class for shelving filters

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

class torchfx.filter.LoShelving(cutoff, q, gain, gain_scale='linear', fs=None)[source]#

Bases: Shelving

Low-frequency shelving filter for bass control.

A low-shelving filter boosts or cuts frequencies below the cutoff frequency. This filter is commonly used in audio equalization to adjust bass frequencies, add warmth, reduce muddiness, or apply bass management. It provides smooth, gradual transitions similar to analog bass/treble controls.

The filter affects all frequencies below the cutoff with a constant gain in dB, creating a shelf-like frequency response. The transition steepness is controlled by the Q parameter.

Parameters:
  • cutoff (float) – Transition frequency in Hz. Frequencies below this will be boosted or cut. Common values: - 60-100 Hz: Sub-bass control - 100-200 Hz: Bass warmth/muddiness - 200-400 Hz: Low-mid body - 400-800 Hz: Fullness/boxiness

  • q (float) – Quality factor controlling transition steepness. Higher Q values result in steeper transitions. Typical values: - 0.5: Gentle, wide transition (musical, analog-like) - 0.707: Moderate transition (common default, Butterworth-like) - 1.0: Steeper transition - 2.0+: Very steep transition

  • gain (float) – Shelf gain amount. Interpretation depends on gain_scale: - For “linear”: Linear gain factor (e.g., 2.0 for +6 dB) - For “db”: Gain in dB (e.g., 6.0 for +6 dB boost, -3.0 for cut)

  • gain_scale ({"linear", "db"}, default="linear") – How to interpret the gain parameter: - “linear”: Direct linear gain multiplier - “db”: Gain in decibels (converted internally to linear)

  • fs (int | None, default=None) – The sampling frequency in Hz.

gain#

The linear gain factor (converted from dB if necessary).

Type:

float

Examples

Add warmth by boosting low frequencies:

>>> import torchfx as fx
>>> shelf = fx.filter.LoShelving(cutoff=200, q=0.707, gain=4, gain_scale="db", fs=44100)
>>> wave = fx.Wave.from_file("thin_recording.wav")
>>> warm = wave | shelf

Reduce muddiness by cutting low-mids:

>>> shelf = fx.filter.LoShelving(cutoff=250, q=0.5, gain=-3, gain_scale="db", fs=44100)
>>> wave = fx.Wave.from_file("muddy_mix.wav")
>>> clean = wave | shelf

Boost sub-bass for electronic music:

>>> sub_shelf = fx.filter.LoShelving(cutoff=80, q=0.707, gain=5, gain_scale="db", fs=44100)
>>> track = fx.Wave.from_file("edm_track.wav")
>>> punchy = track | sub_shelf

Using linear gain:

>>> shelf = fx.filter.LoShelving(cutoff=150, q=0.707, gain=1.5, gain_scale="linear", fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> filtered = wave | shelf

Combining low and high shelving for complete tonal shaping:

>>> lo_shelf = fx.filter.LoShelving(cutoff=200, q=0.707, gain=3, gain_scale="db", fs=44100)
>>> hi_shelf = fx.filter.HiShelving(cutoff=8000, q=0.707, gain=-2, gain_scale="db", fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> shaped = wave | lo_shelf | hi_shelf

Notes

Low-shelving filters are fundamental tools in audio production for:
  • Adding warmth and body to thin recordings

  • Reducing muddiness in the low-midrange

  • Bass management and room correction

  • Matching tonal balance between tracks

  • Creating vintage or “warm” sound characteristics

The filter uses the Audio EQ Cookbook biquad equations, providing accurate analog-style shelving response. The Q parameter controls transition steepness but doesn’t affect the final shelf gain.

Common Applications:
  • Mastering: Subtle low-end adjustments (±2 to ±4 dB at 80-150 Hz)

  • Mixing: Controlling bass buildup (cuts at 100-300 Hz)

  • Sound design: Creating warmth or thickness

  • Broadcast: Meeting bass response standards

For musical, natural-sounding results, use moderate Q values (0.5-0.707) and conservative gains (±3 to ±6 dB). Excessive low-frequency boost can cause distortion, speaker damage, or headroom issues.

References

See also

HiShelving

High-frequency shelving filter

ParametricEQ

Parametric bell-shaped filter

Shelving

Base class for shelving filters

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

Parametric EQ#

class torchfx.filter.ParametricEQ(frequency, q, gain, fs=None)[source]#

Bases: IIR

Parametric equalizer with bell-shaped frequency response.

A parametric EQ is a bell-shaped filter (also called a peaking filter) that boosts or cuts a specific frequency range. It’s the most versatile and commonly used type of EQ in music production, audio mastering, and live sound reinforcement, providing precise control over frequency, bandwidth, and gain.

Unlike shelving filters that affect all frequencies above or below a cutoff, parametric EQs create a localized boost or cut centered at a specific frequency, with the bandwidth controlled by the Q parameter. This makes them ideal for surgical frequency adjustments, tonal shaping, and corrective equalization.

The filter is implemented using the Audio EQ Cookbook biquad equations, providing accurate analog-style peaking response.

Parameters:
  • frequency (float) – Center frequency in Hz where the peak or dip occurs. This is the frequency that receives the maximum boost or cut. Common applications: - 100-250 Hz: Body, warmth, or muddiness control - 250-500 Hz: Fullness or boxiness - 500-2000 Hz: Presence, honk, or nasality - 2000-5000 Hz: Clarity, definition, or harshness - 5000-10000 Hz: Brilliance, air, or sibilance - 10000+ Hz: Sparkle or extreme high-frequency detail

  • q (float) – Quality factor (Q) controlling the bandwidth of the filter. Higher Q values result in narrower, more focused adjustments. The bandwidth in Hz is approximately frequency / Q. Typical values: - 0.3-0.5: Very wide, gentle, musical (subtle tonal shaping) - 0.7-1.0: Wide, natural (general tonal adjustments) - 1.0-2.0: Moderate, focused (typical mixing applications) - 2.0-5.0: Narrow, surgical (problem frequency removal) - 5.0-10.0: Very narrow (feedback suppression, notch-like) - 10.0+: Extremely narrow (surgical removal)

  • gain (float) – Gain in dB at the center frequency. Positive values boost, negative values cut. Typical values: - ±1-2 dB: Subtle, transparent adjustments - ±3-6 dB: Moderate, audible changes - ±6-12 dB: Aggressive, obvious EQ - ±12+ dB: Extreme correction (use with caution)

  • fs (int | None, default=None) – Sampling frequency in Hz.

cutoff#

The center frequency (stored internally).

Type:

float

q#

The quality factor.

Type:

float

gain_db#

The gain in dB.

Type:

float

gain#

The linear gain factor (10^(gain_db/20)).

Type:

float

Examples

Boost presence at 3 kHz for vocal clarity:

>>> import torchfx as fx
>>> eq = fx.filter.ParametricEQ(frequency=3000, q=1.0, gain=4, fs=44100)
>>> vocals = fx.Wave.from_file("vocals.wav")
>>> clear_vocals = vocals | eq

Cut muddy low-mids at 250 Hz:

>>> eq = fx.filter.ParametricEQ(frequency=250, q=1.5, gain=-3, fs=44100)
>>> mix = fx.Wave.from_file("mix.wav")
>>> clean_mix = mix | eq

Add warmth with wide boost at 120 Hz:

>>> eq = fx.filter.ParametricEQ(frequency=120, q=0.5, gain=3, fs=44100)
>>> bass = fx.Wave.from_file("bass.wav")
>>> warm_bass = bass | eq

Surgical removal of resonance at 800 Hz:

>>> eq = fx.filter.ParametricEQ(frequency=800, q=5.0, gain=-6, fs=44100)
>>> guitar = fx.Wave.from_file("guitar.wav")
>>> smooth_guitar = guitar | eq

Multiple EQ bands in series:

>>> # Low-mid cut, presence boost, air boost
>>> eq1 = fx.filter.ParametricEQ(frequency=200, q=1.0, gain=-2, fs=44100)
>>> eq2 = fx.filter.ParametricEQ(frequency=3000, q=1.5, gain=3, fs=44100)
>>> eq3 = fx.filter.ParametricEQ(frequency=10000, q=0.7, gain=2, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> eq_chain = wave | eq1 | eq2 | eq3

Feedback suppression with very narrow Q:

>>> # Identify feedback frequency (e.g., 2450 Hz), then apply narrow cut
>>> notch_eq = fx.filter.ParametricEQ(frequency=2450, q=10.0, gain=-12, fs=44100)
>>> live_mix = fx.Wave.from_file("live.wav")
>>> controlled = live_mix | notch_eq

Notes

Q Factor and Bandwidth Relationship:
  • Bandwidth (Hz) ≈ frequency / Q

  • Bandwidth (octaves) ≈ 2 * sinh⁻¹(1/(2Q)) / ln(2)

  • Low Q (< 1): Wide bandwidth, affects many frequencies, musical

  • Medium Q (≈ 1): Moderate bandwidth, natural sound

  • High Q (> 2): Narrow bandwidth, focused correction, can sound unnatural

Common Applications:
  • Vocal production: Boost 2-5 kHz for clarity, cut 200-400 Hz for muddiness

  • Kick drum: Boost 60-80 Hz for thump, 3-5 kHz for attack

  • Snare: Boost 200 Hz for body, 5 kHz for snap

  • Bass: Cut 200-300 Hz for clarity, boost 80-100 Hz for weight

  • Acoustic guitar: Boost 5-7 kHz for sparkle, cut 200-300 Hz for mud

  • Mix mastering: Subtle adjustments (±1-3 dB, Q=0.5-1.0)

Best Practices:
  • Start with narrow Q to identify problem frequencies, then widen for musical result

  • Use cuts more than boosts (subtractive EQ philosophy)

  • Lower frequencies generally need wider Q (lower Q values)

  • Higher frequencies can use narrower Q without sounding unnatural

  • A/B compare frequently to avoid over-EQing

  • Use multiple gentle bands rather than one extreme adjustment

The parametric EQ uses the following biquad equations from the Audio EQ Cookbook:

ω₀ = 2π * frequency / fs α = sin(ω₀) / (2 * Q) A = 10^(gain/20)

b₀ = 1 + α * A b₁ = -2 * cos(ω₀) b₂ = 1 - α * A a₀ = 1 + α / A a₁ = -2 * cos(ω₀) a₂ = 1 - α / A

Caution: Very high Q values (>10) can cause ringing artifacts and may sound unnatural. Very large gain values (>12 dB) can introduce distortion and reduce headroom. Always monitor levels after applying EQ.

References

See also

Peaking

General peaking filter implementation

HiShelving

High-frequency shelving filter

LoShelving

Low-frequency shelving filter

Notch

Notch filter for frequency rejection

compute_coefficients()[source]#

Compute the filter coefficients using the Audio EQ Cookbook formulas.

Return type:

None

Other Filters#

class torchfx.filter.Notch(cutoff, q, fs=None)[source]#

Bases: IIR

Notch filter for narrow-band frequency rejection.

A notch filter (also called a band-stop or band-reject filter) attenuates a narrow band of frequencies centered around a specified frequency. This is ideal for removing tonal interference, hum, resonances, or feedback frequencies without affecting the surrounding spectrum.

The filter creates a sharp dip in the frequency response at the notch frequency, with the width of the notch controlled by the Q parameter.

Parameters:
  • cutoff (float) – Notch center frequency in Hz. This frequency will be maximally attenuated. Common applications: - 50 Hz: Mains hum (Europe, Australia) - 60 Hz: Mains hum (North America, Japan) - 100/120 Hz: Second harmonic of mains hum - Any resonant frequency or feedback tone

  • q (float) – Quality factor determining the notch width. Higher Q values result in narrower notches that affect fewer adjacent frequencies. The notch bandwidth in Hz is approximately cutoff / Q. Typical values: - 5-10: Wide notch, affects broader frequency range - 10-30: Moderate notch (common for hum removal) - 30+: Very narrow notch, surgical removal

  • fs (int | None, default=None) – Sampling frequency in Hz.

cutoff#

The notch center frequency.

Type:

float

q#

The quality factor.

Type:

float

Examples

Remove 60 Hz mains hum:

>>> import torchfx as fx
>>> notch = fx.filter.Notch(cutoff=60, q=30, fs=44100)
>>> wave = fx.Wave.from_file("humming_recording.wav")
>>> clean = wave | notch

Remove 50 Hz hum (European mains):

>>> notch = fx.filter.Notch(cutoff=50, q=30, fs=44100)
>>> wave = fx.Wave.from_file("recording.wav")
>>> filtered = wave | notch

Remove feedback frequency:

>>> # First identify the feedback frequency (e.g., 2350 Hz)
>>> notch = fx.filter.Notch(cutoff=2350, q=20, fs=44100)
>>> wave = fx.Wave.from_file("live_recording.wav")
>>> no_feedback = wave | notch

Remove multiple hum harmonics:

>>> # Remove 60 Hz fundamental and 120 Hz harmonic
>>> notch1 = fx.filter.Notch(cutoff=60, q=30, fs=44100)
>>> notch2 = fx.filter.Notch(cutoff=120, q=30, fs=44100)
>>> wave = fx.Wave.from_file("humming.wav")
>>> clean = wave | notch1 | notch2

Wide notch for resonance control:

>>> notch = fx.filter.Notch(cutoff=800, q=5, fs=44100)
>>> wave = fx.Wave.from_file("resonant.wav")
>>> controlled = wave | notch

Notes

Notch filters are essential tools for:
  • Removing mains hum (50/60 Hz) and harmonics

  • Eliminating feedback frequencies in live sound

  • Reducing room resonances and standing waves

  • Removing tonal interference (AC hum, electrical noise)

  • De-essing (though specialized de-essers are often better)

The Q parameter is critical:
  • Too low: Notch is wide and affects too many frequencies, causing audible coloration

  • Too high: Notch may not be wide enough to fully remove the problem frequency, especially if it varies slightly

For hum removal, Q values of 20-30 are typical. For surgical removal of specific tones, Q values up to 50-100 can be used, but be aware that very high Q filters can ring or become unstable.

The notch filter is implemented using scipy.signal.iirnotch, which designs a second-order IIR notch filter with maximum attenuation at the specified frequency.

Caution: Overuse of notch filters can make audio sound unnatural. Use sparingly and only when necessary. Consider addressing the source of the problem (ground loops, shielding, etc.) rather than filtering.

See also

Peaking

Peaking filter (inverse of notch)

ParametricEQ

Parametric EQ for boosting/cutting frequency bands

Butterworth

Broader frequency control

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None

class torchfx.filter.AllPass(cutoff, q, fs=None)[source]#

Bases: IIR

All-pass filter for phase manipulation without magnitude change.

An all-pass filter passes all frequencies with unity magnitude (no amplitude change) but introduces frequency-dependent phase shift. These filters are primarily used for phase correction, creating reverb/delay effects, and designing crossover networks with linear phase response.

While the magnitude response is flat across all frequencies, the phase response varies with frequency, which can be used to align phase between different signal paths or create time-domain dispersion effects.

Parameters:
  • cutoff (float) – Center frequency in Hz where maximum phase shift occurs. The phase shift is frequency-dependent, with maximum shift at this frequency.

  • q (float) – Quality factor controlling the rate of phase change. Higher Q values result in more rapid phase transitions around the center frequency.

  • fs (int | None, default=None) – Sampling frequency in Hz.

cutoff#

The center frequency.

Type:

float

q#

The quality factor.

Type:

float

Examples

Phase correction for speaker alignment:

>>> import torchfx as fx
>>> allpass = fx.filter.AllPass(cutoff=1000, q=0.707, fs=44100)
>>> wave = fx.Wave.from_file("audio.wav")
>>> phase_shifted = wave | allpass

Creating a phaser effect (using multiple all-pass stages):

>>> ap1 = fx.filter.AllPass(cutoff=500, q=1.0, fs=44100)
>>> ap2 = fx.filter.AllPass(cutoff=1000, q=1.0, fs=44100)
>>> ap3 = fx.filter.AllPass(cutoff=2000, q=1.0, fs=44100)
>>> wave = fx.Wave.from_file("guitar.wav")
>>> phased = wave | ap1 | ap2 | ap3

Notes

All-pass filters have unique properties:
  • Magnitude response: Unity gain at all frequencies (0 dB)

  • Phase response: Frequency-dependent, non-linear

  • Applications: Phase correction, reverb design, time dispersion

Common uses in audio:
  • Phase alignment in multi-driver speaker systems

  • Reverb algorithm building blocks

  • Creating vintage phaser/flanger effects

  • Group delay compensation

  • Hilbert transform implementation (90° phase shift)

The all-pass filter is particularly useful in:
  • Crossover networks: Aligning phase between drivers

  • Reverb design: Creating dense reflections and diffusion

  • Effect chains: Adding subtle movement and depth

  • Room correction: Compensating for acoustic phase distortions

Implementation note: This uses scipy.signal.iirpeak which creates a peaking filter structure. For a true all-pass filter, ensure the gain is set appropriately (typically handled internally).

Caution: While all-pass filters don’t change magnitude, they do affect the time- domain response and can introduce pre-ringing or post-ringing artifacts. Use judiciously in critical listening applications.

See also

Peaking

Peaking filter with magnitude change

LinkwitzRiley

Crossover filters with phase-aligned outputs

compute_coefficients()[source]#

Compute the filter’s transfer function coefficients.

This abstract method must be implemented by all AbstractFilter subclasses. It is responsible for computing the filter’s transfer function coefficients (typically the numerator b and denominator a coefficients) and storing them in instance attributes.

The method is called automatically during the first forward() pass when the _has_computed_coeff property returns False. It should not be called directly by users in typical usage scenarios.

Implementation Requirements#

Implementations of this method should:

  1. Verify sampling frequency: Check that self.fs is not None before attempting to compute coefficients. Raise a ValueError if it is None.

  2. Design filter coefficients: Use SciPy signal processing functions (e.g., scipy.signal.butter, scipy.signal.cheby1, scipy.signal.firwin) or custom algorithms to compute filter coefficients based on the filter’s parameters (cutoff frequency, order, Q factor, etc.).

  3. Store coefficients: Save the computed coefficients in instance attributes (typically self.a for denominator and self.b for numerator).

  4. Normalize frequencies: For filters that use normalized frequencies (most SciPy functions), normalize cutoff frequencies by the Nyquist frequency (fs/2).

Notes

  • For IIR filters, both a (denominator) and b (numerator) coefficients are typically stored as sequences or arrays.

  • For FIR filters, only b (numerator) coefficients are needed, with a set to [1.0].

  • Coefficients are initially stored as Python sequences or NumPy arrays and are later converted to PyTorch tensors during the forward() pass.

  • This method should be deterministic - calling it multiple times with the same parameters should produce identical coefficients.

raises ValueError:

If required parameters (especially fs) are not set before coefficient computation is attempted.

Examples

Implementing compute_coefficients for a Butterworth lowpass filter:

>>> from scipy.signal import butter
>>>
>>> def compute_coefficients(self):
...     '''Compute Butterworth lowpass coefficients.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Normalize cutoff to Nyquist frequency
...     nyquist = 0.5 * self.fs
...     normalized_cutoff = self.cutoff / nyquist
...     # Design filter using SciPy
...     self.b, self.a = butter(self.order, normalized_cutoff, btype='low')

Implementing compute_coefficients for a custom shelving filter:

>>> import numpy as np
>>>
>>> def compute_coefficients(self):
...     '''Compute high-shelving filter coefficients using biquad formulas.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Calculate angular frequency
...     omega = 2 * np.pi * self.cutoff / self.fs
...     alpha = np.sin(omega) / (2 * self.q)
...     A = self.gain  # Linear gain
...     # Compute biquad coefficients
...     b0 = A * ((A + 1) + (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha)
...     b1 = -2 * A * ((A - 1) + (A + 1) * np.cos(omega))
...     b2 = A * ((A + 1) + (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha)
...     a0 = (A + 1) - (A - 1) * np.cos(omega) + 2 * np.sqrt(A) * alpha
...     a1 = 2 * ((A - 1) - (A + 1) * np.cos(omega))
...     a2 = (A + 1) - (A - 1) * np.cos(omega) - 2 * np.sqrt(A) * alpha
...     # Normalize by a0 and store
...     self.b = [b0/a0, b1/a0, b2/a0]
...     self.a = [1.0, a1/a0, a2/a0]

Implementing compute_coefficients for an FIR filter:

>>> from scipy.signal import firwin
>>>
>>> def compute_coefficients(self):
...     '''Compute FIR filter coefficients using window method.'''
...     if self.fs is None:
...         raise ValueError("Sampling frequency must be set")
...     # Use scipy firwin to design FIR filter
...     self.b = firwin(self.num_taps, self.cutoff, fs=self.fs,
...                     pass_zero=self.pass_zero, window=self.window)
...     self.a = [1.0]  # FIR filters have trivial denominator
Return type:

None