Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::HilbertQuadratureNetworkT< SampleType > Class Template Reference

A pair of allpass cascades whose outputs stand a quarter cycle apart across nearly the whole band — the analytic-signal splitter an SSB shifter needs. More...

#include <frequency_shifter_ssb.hpp>

Classes

struct  Outputs

Public Member Functions

void reset ()
 Clears state.
Outputs process (SampleType x)

Static Public Member Functions

static constexpr double band_low_hz (double sample_rate)
 Lower edge of the design band in Hz at a given rate.
static constexpr double band_high_hz (double sample_rate)
 Upper edge of the design band in Hz at a given rate.

Static Public Attributes

static constexpr std::size_t kSections = 4
 Sections per branch.
static constexpr std::array< double, kSections > kInPhasePoles
 Pole magnitudes of the in-phase (undelayed) branch.
static constexpr std::array< double, kSections > kQuadraturePoles
 Pole magnitudes of the quadrature (one-sample-delayed) branch.
static constexpr double kBandEdgeNormalized = 4.6e-4
 The lower edge of the design band, as a fraction of the sample rate.
static constexpr double kImageRejectDb = 40.0
 The rejection floor the shipped table holds across the design band.

Detailed Description

template<typename SampleType = double>
class pulp::signal::HilbertQuadratureNetworkT< SampleType >

A pair of allpass cascades whose outputs stand a quarter cycle apart across nearly the whole band — the analytic-signal splitter an SSB shifter needs.

Neither output is the input. Both branches are allpass, so both preserve the input's magnitude spectrum exactly and move only its phase; what carries information is the DIFFERENCE between them. A single filter cannot produce that, which is why the structure is a pair.

Each section is second-order with one coefficient:

c − z⁻² c = a²
A(z) = ─────────────── , poles at z = ±√c , |A(e^{jω})| = 1
1 − c·z⁻²
@ c
Definition spectrum_trace.hpp:35
@ e
Definition formant_filter_bank.hpp:20

This is not the section form a polyphase half-band decimator uses, and the difference is one sign, so it is worth stating before someone "corrects" it. The half-band form is (c + z⁻²)/(1 + c·z⁻²), with poles at ±j√c. Fed the SAME coefficient table, that form produces a branch pair whose phase difference is 0 below fs/4 and π above it — a half-band filter, not a quadrature pair. The two forms are related by z → jz, which is the half-band-to-Hilbert rotation, and that identity is precisely why one published table serves both uses. Ship the wrong sign and the module still runs, still sounds like something, and rejects no image at all.

Both branches are cascades of kSections such sections and the QUADRATURE branch carries an additional z⁻¹. Of the four ways to assign (delayed, undelayed) × (table A, table B) to (I, Q), only two give a phase difference that is constant across the band rather than one that drifts with ω; of those two, this one leaves the IN-PHASE branch undelayed, so the module's impulse response is non-zero at sample 0 and latency_samples() is honestly 0 (the other choice puts a bulk sample of delay on I and would have to report 1).

Stability is structural, not a property of the fitted values: |z| = √c < 1 for any 0 < c < 1, so any coefficient search can be re-run without ever risking an unstable filter. What a coefficient search CAN get wrong is the image rejection, which is why that — and not the digits — is what the acceptance suite asserts.

RT contract: every member allocates nothing, takes no locks, and is safe per sample on the audio thread.

Member Function Documentation

◆ band_high_hz()

template<typename SampleType = double>
constexpr double pulp::signal::HilbertQuadratureNetworkT< SampleType >::band_high_hz ( double sample_rate)
inlinestaticconstexpr

Upper edge of the design band in Hz at a given rate.

◆ band_low_hz()

template<typename SampleType = double>
constexpr double pulp::signal::HilbertQuadratureNetworkT< SampleType >::band_low_hz ( double sample_rate)
inlinestaticconstexpr

Lower edge of the design band in Hz at a given rate.

Computed from the shipped constant, never restated.

◆ process()

template<typename SampleType = double>
Outputs pulp::signal::HilbertQuadratureNetworkT< SampleType >::process ( SampleType x)
inline

◆ reset()

template<typename SampleType = double>
void pulp::signal::HilbertQuadratureNetworkT< SampleType >::reset ( )
inline

Clears state.

Cannot clear coefficients — there are none to clear; they are compile-time constants read directly by process().

Member Data Documentation

◆ kBandEdgeNormalized

template<typename SampleType = double>
double pulp::signal::HilbertQuadratureNetworkT< SampleType >::kBandEdgeNormalized = 4.6e-4
staticconstexpr

The lower edge of the design band, as a fraction of the sample rate.

A fixed allpass table is a normalised-frequency design, so this is the only form in which the edge is a constant at all (see the file doc block). Measured: kImageRejectDb is met on [kBandEdgeNormalized·fs, (0.5 − kBandEdgeNormalized)·fs], which is 20.3 Hz .. 22.08 kHz at 44.1 kHz and 22.1 Hz .. 23.98 kHz at 48 kHz. The 40 dB crossover itself is at 4.34e-4; this sits a little above it so the claim carries margin rather than sitting on the knife edge.

◆ kImageRejectDb

template<typename SampleType = double>
double pulp::signal::HilbertQuadratureNetworkT< SampleType >::kImageRejectDb = 40.0
staticconstexpr

The rejection floor the shipped table holds across the design band.

[design parameter] default 40 dB, range 30 .. 60 dB — the range is the span a regenerated table at kSections 3 .. 6 can reach; 40 dB is what four sections deliver.

◆ kInPhasePoles

template<typename SampleType = double>
std::array<double, kSections> pulp::signal::HilbertQuadratureNetworkT< SampleType >::kInPhasePoles
staticconstexpr
Initial value:
= {
0.4021921162426, 0.8561710882420, 0.9722909545651, 0.9952884791278}

Pole magnitudes of the in-phase (undelayed) branch.

[design parameter] — a calibration table, validated by the acceptance suite's measured image rejection rather than by inspecting digits. Reached from the published minimax design cited in the file doc block; the section coefficient is the SQUARE of each entry.

◆ kQuadraturePoles

template<typename SampleType = double>
std::array<double, kSections> pulp::signal::HilbertQuadratureNetworkT< SampleType >::kQuadraturePoles
staticconstexpr
Initial value:
= {
0.6923878, 0.9360654322959, 0.9882295226860, 0.9987488452737}

Pole magnitudes of the quadrature (one-sample-delayed) branch.

[design parameter] — same provenance and same validation as above.

◆ kSections

template<typename SampleType = double>
std::size_t pulp::signal::HilbertQuadratureNetworkT< SampleType >::kSections = 4
staticconstexpr

Sections per branch.

[design parameter] default 4, range 3 .. 6. Four is Niemitalo's published operating point and measures ≈ 44 dB of broadband rejection here; the range exists so the table can be regenerated deeper without a structural change if a future spec wants a tighter floor.


The documentation for this class was generated from the following file: