Louis Couka

Building an Insane Resonant 2000dB/oct Filter

Published: 30/10/2020 | Author: Louis Couka

Table Of Contents

Introduction

Designing filters with exceptionally steep slopes, such as a 2000 dB/octave filter, poses a significant technical challenge. While Butterworth filters are known for their smooth response, achieving a cutoff slope of this magnitude would require an impractically high filter order, calculated as \(N = \frac{2000}{6} \approx 333\). Such high-order designs are computationally intensive and inefficient for real-time processing.

To overcome this, elliptic filters offer an efficient alternative. Elliptic filters can achieve sharp roll-offs with much lower orders, making them ideal for real-time applications where extreme precision in frequency cutoff is essential. In this article, we’ll explore the design of a 2000 dB/octave filter using an elliptic approach while ensuring the filter remains musical and consistent with previously established filter designs, such as the resonant Butterworth filter.

Elliptic Filters: An Efficient Solution for Steep Slopes

Elliptic filters provide the steepest transition between the passband and stopband among standard filter types by allowing small ripples within both bands. Although these ripples are technically undesirable, they are often inaudible and thus a practical compromise for achieving steep cutoffs at lower orders.

Designing elliptic filters relies on advanced mathematical functions, specifically Jacobi elliptic functions. Generating filter coefficients for elliptic filters is commonly done using libraries such as SciPy’s signal.ellip() function. This approach enables us to concentrate on fine-tuning the filter’s performance characteristics without getting bogged down by complex mathematics.

Addressing the “Knee” for Musicality

A common issue with brickwall filters with extremely steep slopes, like the 2000 dB/octave filter, is the abrupt transition around the cutoff frequency. Such sharp transitions can produce unmusical artifacts, including ringing. To mitigate these effects, we want to introduce a parameter to control the knee of the filter. This parameter aims to soften the response around the cutoff frequency, resulting in a smoother and more musical impulse response, which is especially valuable for audio applications.

Consistency with Existing Filters: Design Constraints and Integration

To maintain consistency across our filter designs, we propose a unified filter structure with three core parameters: slope, Q, and frequency. This ensures that the brickwall filter can be easily integrated into our existing filter architecture as simply another slope setting, 2000 dB/oct in this case. The Q-factor will allow users to adjust the resonance and smooth the knee, addressing the musicality problem discussed earlier.

This unified design simplifies the user experience, as they can now adjust these three parameters to create a wide variety of filters, from gentle slopes to extreme brickwall configurations, all within a single framework.

Building the Prototype Filter

Step 1: Using the Resonant Butterworth Filter as a Reference

We start by referencing the transfer function of a Resonant Butterworth filter, which provides a consistent foundation. A slope of 2000 dB/oct would theoretically require a filter order of \(N \approx 333\), which, which is computationally prohibitive. Instead, we will approximate this using an elliptic filter.

Step 2: Approximating with an Elliptic Filter

We use SciPy’s signal.ellip() function to generate an elliptic filter, modifying it to align its magnitude with that of the theoretical Resonant Butterworth filter. These adjustments include:

  • Centering the cutoff frequency: The cutoff frequency is aligned to ensure that the most resonant section of the filter matches the desired global cutoff frequency. This follows a similar approach to the one we used in the design of the Resonant Butterworth filter.
  • Using the most resonant section from the corresponding resonant Butterworth filter. This ensures that the magnitude in the passband is matched accurately, particularly for low Q-factor values.

Step 3: Setting Filter Parameters

We configure the elliptic filter’s parameters as follows:

  • N_ellip: We use an elliptic filter of order 16, which is the highest order we currently use for filters with slopes around 96 dB/oct. This provides a good balance between performance and precision.
  • rp (passband ripple): Set to 0.1 dB, which ensures minimal ripple in the passband while maintaining a sharp transition to the stopband.
  • rs (stopband attenuation): Set to 100 dB, providing a strong attenuation in the stopband.

With these settings, we approximate the steep slope of a 2000 dB/oct filter while minimizing CPU load. Figures 1 and 2 illustrate the frequency response of the filter for different Q values, with Figure 2 zooming in on the transition band to showcase the filter’s behavior at this critical region.

Python
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import numpy as np
import matplotlib.pyplot as plt
from scipy import signal
import warnings
warnings.filterwarnings("ignore")

def plot(title, w):
    n = 2000/6
    for i, q in enumerate(2**np.linspace(-12, 4, 5)*0.5**0.5):
        # Elliptic approx
        sos = signal.ellip(16, 0.2, 100, 1, analog=True, output="sos")
        wc_offset = sos[-1][5]**-0.5
        sos *= np.array([1, wc_offset, wc_offset**2]*2) # Recenter elliptic filter
        # sos[-1][4] /= q / (np.abs(signal.freqs(*signal.sos2tf(sos), [1])[1])) # Normalize Q factor using the gain value at wc
        sos[-1][4] = (-2 * np.cos((n+1)/(2*n) * np.pi)) / (np.sqrt(2)*q) # Use Q factor from the butterworth filter formula
        _, h = signal.freqs(*signal.sos2tf(sos), w)
        plt.plot(np.log2(w), 20*np.log10(np.abs(h)), c=plt.rcParams['axes.prop_cycle'].by_key()['color'][1], label = "Resonant Elliptic N=16" if i == 0 else "")

        # Resonant Butterworth filter with an heavy slope
        g = np.sqrt((1-2*np.cos(np.pi/n)*w**2+w**4)/((1+w**(2*n))*(1+((1-np.cos(np.pi/n))/q**2-2)*w**2+w**4)))
        plt.plot(np.log2(w), 20*np.log10(g), linestyle=(0, (4, )), c="white", alpha=0.5, label = "Resonant Butterworth N≈" + str(int(n)) if i == 0 else "")

    plt.title(title)
    plt.xlabel("Frequency (octaves)")
    plt.ylabel("Gain ( dB)")
    plt.ylim(-120, 30)
    plt.legend()
    plt.show()

plot("Figure 1 - Brickwall Filter for various Q-factors", np.logspace(-5, 5, 10000, base = 2))
plot("Figure 2 - Brickwall Filter for various Q-factors - Zoomed on Transition Band", np.logspace(-0.1, 0.1, 1000, base = 2))

Python - Console Output

Python - Console Output

Conclusion

In this article, we’ve demonstrated how to design a 2000 dB/oct resonant filter using elliptic filter theory, while solving the musicality issue commonly associated with brickwall filters. By introducing a Q-control to adjust the knee of the filter, we provide users with a versatile, musical, and efficient filter design that integrates seamlessly into existing architectures.

Going Further

The success of this approach with a 2000 dB/oct filter suggests that we can extend this technique to design filters with any slope between 96 dB/oct and 2000 dB/oct continuously. This would allow for lowpass, highpass, and bandpass filters with seamless, continuous slope control, akin to what has been implemented for peak and shelf filters in UVI Shade. Future work could focus on refining and expanding this capability.