Polyphase IIR allpass-network half-band filters for 2x oversampling. More...
#include <algorithm>#include <array>#include <cmath>#include <complex>#include <cstddef>#include <limits>#include <numbers>#include <span>#include <type_traits>Go to the source code of this file.
Classes | |
| class | pulp::signal::HalfBandAllpassSectionT< SampleType > |
| One single-coefficient allpass section run at the sub-rate. More... | |
| struct | pulp::signal::HalfBandIirDesign |
| Coefficients of one elliptic polyphase half-band filter, ascending. More... | |
| class | pulp::signal::HalfBandIirStageT< SampleType > |
| Fixed-storage polyphase half-band stage that filters whole blocks: the engine behind HalfBandUpsampler2xT, HalfBandDownsampler2xT, and the oversampler's polyphase lane. More... | |
| class | pulp::signal::HalfBandUpsampler2xT< SampleType > |
| 2x half-band upsampler. More... | |
| class | pulp::signal::HalfBandDownsampler2xT< SampleType > |
| 2x half-band downsampler. More... | |
Namespaces | |
| namespace | pulp |
| namespace | pulp::signal |
| namespace | pulp::signal::detail |
| Round-2 effect-wave vocabulary. | |
Typedefs | |
| using | pulp::signal::HalfBandAllpassSection = HalfBandAllpassSectionT<float> |
| using | pulp::signal::HalfBandAllpassSection64 = HalfBandAllpassSectionT<double> |
| using | pulp::signal::HalfBandIirStage = HalfBandIirStageT<float> |
| using | pulp::signal::HalfBandIirStage64 = HalfBandIirStageT<double> |
| using | pulp::signal::HalfBandUpsampler2x = HalfBandUpsampler2xT<float> |
| using | pulp::signal::HalfBandUpsampler2x64 = HalfBandUpsampler2xT<double> |
| using | pulp::signal::HalfBandDownsampler2x = HalfBandDownsampler2xT<float> |
| using | pulp::signal::HalfBandDownsampler2x64 = HalfBandDownsampler2xT<double> |
Functions | |
| HalfBandIirDesign | pulp::signal::design_halfband_iir (std::size_t coefficient_count, double passband_edge) noexcept |
| Closed-form elliptic half-band design (see the file comment). | |
| template<typename Coefficient> | |
| void | pulp::signal::detail::halfband_path_response (const HalfBandIirDesign &design, bool path_a, double frequency, std::complex< double > &response, double &group_delay) noexcept |
| template<typename Coefficient = double> | |
| std::complex< double > | pulp::signal::halfband_iir_response (const HalfBandIirDesign &design, double frequency) noexcept |
| Complex response of design at frequency cycles per sample of the filter's own rate: 1/2 (A(z^2) + z^-1 B(z^2)). | |
| template<typename Coefficient = double> | |
| double | pulp::signal::halfband_iir_magnitude_db (const HalfBandIirDesign &design, double frequency) noexcept |
| Magnitude of design in dB at frequency (cycles per sample of the filter's own rate). | |
| template<typename Coefficient = double> | |
| double | pulp::signal::halfband_iir_group_delay (const HalfBandIirDesign &design, double frequency) noexcept |
| Group delay of H(z) in samples of the filter's own rate. | |
| template<typename Coefficient = double> | |
| double | pulp::signal::halfband_iir_stopband_db (const HalfBandIirDesign &design) noexcept |
| Worst-case (largest) stopband magnitude in dB over [stopband_edge(), 0.5], sampled densely enough to land on every ripple peak of designs up to kHalfBandIirMaxCoefficients. | |
| template<typename Coefficient> | |
| HalfBandIirDesign | pulp::signal::detail::halfband_design_from_paths (std::span< const Coefficient > a, std::span< const Coefficient > b) noexcept |
| HalfBandIirDesign | pulp::signal::detail::default_halfband_design () noexcept |
Variables | |
| constexpr std::size_t | pulp::signal::kHalfBandIirMaxCoefficients = 16 |
| Largest coefficient count a HalfBandIirDesign carries (eight sections per path). | |
| constexpr double | pulp::signal::kHalfBandIirMinPassbandEdge = 1e-4 |
| Smallest passband edge design_halfband_iir() accepts, in cycles per sample at the filter's own rate. | |
| constexpr std::array< float, 3 > | pulp::signal::kDefaultCoefficientsA |
| Default half-band design: design_halfband_iir(6, 0.21), path A. | |
| constexpr std::array< float, 3 > | pulp::signal::kDefaultCoefficientsB |
| Default half-band design, path B. See kDefaultCoefficientsA. | |
Polyphase IIR allpass-network half-band filters for 2x oversampling.
A half-band filter has its transition centred on a quarter of its own sample rate. The polyphase decomposition expresses an odd-order elliptic half-band lowpass as two parallel allpass paths:
H(z) = 1/2 * (A(z^2) + z^-1 * B(z^2))
where each path is a cascade of second-order sections with a single real coefficient a:
S(z) = (a + z^-2) / (1 + a * z^-2)
run at the sub-rate as the first-order recurrence
y[n] = a * (x[n] - y[n-1]) + x[n-1]
which costs one multiply-add pair per section per sub-rate sample.
design_halfband_iir(count, passband_edge) derives the coefficients in closed form. An odd-order (2N+1) elliptic lowpass whose stopband edge mirrors its passband edge about a quarter of the sample rate is power complementary (|H(w)|^2 + |H(pi - w)|^2 = 1), and after the bilinear map every pole lies on the imaginary axis at +/- j*sqrt(a_i). The pole radii follow from the selectivity k = tan^2(pi * passband_edge), the elliptic nome of the complementary modulus, and the theta-series form of the Jacobi sn function at 2*i*K/(2N+1) — see Valenzuela & Constantinides, "Digital signal processing schemes for efficient interpolation and decimation" (IEE Proc. 1983), and Vaidyanathan, "Multirate Systems and Filter Banks", ch. 5. Ascending coefficients alternate between path A (even indices) and path B (odd indices).
The response is equiripple in the stopband, monotonic in the passband, and sample-rate-invariant by construction: the same coefficients are correct at 44.1 kHz, 48 kHz, 96 kHz, or any other rate. The stopband floor depends only on the order and the transition width:
passband edge 0.21 (of the 2x rate): N=5 -79.3 dB, N=6 -94.8 dB,
N=8 -125.9 dB
passband edge 0.225: N=6 -80.1 dB, N=11 -146.3 dB
passband edge 0.2425: N=12 -114.6 dB, N=16 -153.2 dB
halfband_iir_response(), halfband_iir_group_delay(), and halfband_iir_stopband_db() evaluate a design without running audio through it.
HalfBandUpsampler2x::process(x, out_lo, out_hi) takes one input sample and produces two output samples at twice the rate. Path A becomes the "low" output (the phase aligned with the input), path B the "high" output (the in-between sample). Each path is individually allpass; the half-band response emerges once the two are interleaved into a single 2x-rate stream, where the paths' phase difference is 0 in the passband (samples reinforce) and pi in the stopband (they cancel). This is exactly H(z) applied to the zero-stuffed input with a gain of 2.
HalfBandDownsampler2x::process(in_lo, in_hi) consumes two input samples at the higher rate and emits one decimated output: the odd sample drives path A, the even sample path B, and the outputs are averaged. That is H(z) followed by keeping every odd-phase sample, so the decimator leads H's own group delay by one high-rate sample.