This is a (short, dependency-free, single-file C++ header) IIR Hilbert filter with an 0BSD License.
It uses a pre-baked continuous-time filter, which is converted into a discrete-time filter (with an upper cutoff of 20kHz) for a given sample-rate. This gives it the same phase/amplitude responses at higher sample-rates (as per my API London 2024 talk).
#include "hilbert.h"
using HilbertIIR = signalsmith::hilbert::HilbertIIR<float>;
Single-channel:
HilbertIIR hilbert(48000); // 48kHz
float in = /*...*/;
std::complex<float> out = hilbert(in);
Multi-channel:
HilbertIIR hilbert(44100, 2); // 44.1kHz stereo
float in0 = /*...*/, in1 = /*...*/;
std::complex<float> out0 = hilbert(in0, 0);
std::complex<float> out1 = hilbert(in1, 1);
Reset the state to 0:
hilbert.reset();
The constructor has an optional third passbandGain
argument. This defaults to 2
to compensate for the negative frequencies being removed, and means the volume will match if you just use complex.real()
.
Here's the impulse/spectrum when running at 44.1kHz:
Here's the impulse/spectrum when running at 96kHz:
The passband ripple is 0.5dB.
A continuous-time filter is designed in Python / SciPy (design/design.py
). It's rephrased as the sum of parallel complex 1-pole filters, and printed as C++ code.
The C++ code (hilbert.h
) then converts this to a discrete-time filter using the Impulse Invariant Transform, so we now have the sum of parallel discrete-time complex 1-pole filters. This is what we use to compute the result.
The code in design/main.cpp
generates SVG plots of the impulse/spectrum, so if you change the filter design you can see the results.
The license is 0BSD, which lets you use/modify/redistribute the code for any purpose ("public domain equivalent"). If you need anything else, get in touch.