Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
halfband_iir.hpp File Reference

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.

Detailed Description

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

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.

Upsampling (2x)

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.

Downsampling (2x)

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.