Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
pulp::signal::TapeEqSection Class Reference

One first-order EQ section — a (curve, speed) record network, or its exact inverse. More...

#include <tape_machine_components.hpp>

Public Member Functions

void set (const tape::EqTimeConstants &tc, double sample_rate, bool inverse) noexcept
void reset () noexcept
double process (double x) noexcept
double response_db (double hz, double sample_rate) const noexcept
 Magnitude response in dB.

Static Public Attributes

static constexpr double kInversePoleDamping = 0.90
 How far inside the unit circle the inverse's pole is pulled when the forward section's numerator is (1 + z⁻¹) — i.e.
static constexpr double kPrewarpCeiling = 0.49
 Highest corner the prewarp will place, as a fraction of the sample rate.

Detailed Description

One first-order EQ section — a (curve, speed) record network, or its exact inverse.

Realized by bilinear transform with each corner prewarped INDEPENDENTLY. That matters and is easy to get wrong: a single bilinear substitution can place only one corner exactly, and this prototype has a zero at 50 Hz and a pole at 3183 Hz that both have to land. Prewarping the zero's section and the pole's section separately leaves each with a spurious (1 + z⁻¹) factor which CANCELS between them, so the product is a plain first-order section with both corners exact:

(u₂/u₁)·[(u₁+1) + (u₁−1)z⁻¹]
H(z) = ─────────────────────────────── , uᵢ = tan(π·fᵢ/fs), fᵢ = 1/(2π·tᵢ)
(u₂+1) + (u₂−1)z⁻¹

Its DC gain is exactly 1, as the analog prototype's is. The section is then rescaled so it is unity at kEqReferenceHz instead.

The inverse is taken in the DIGITAL domain — numerator and denominator swapped — not by bilinear-transforming 1/H(s) separately. Two independent discretizations of reciprocal analog prototypes are not reciprocal digital filters; swapping the coefficients makes the pair cancel to numerical precision, which is what a record/playback pair bracketing a nonlinearity has to do.

Member Function Documentation

◆ process()

double pulp::signal::TapeEqSection::process ( double x)
inlinenoexcept

◆ reset()

void pulp::signal::TapeEqSection::reset ( )
inlinenoexcept

◆ response_db()

double pulp::signal::TapeEqSection::response_db ( double hz,
double sample_rate ) const
inlinenoexcept

Magnitude response in dB.

Used by the acceptance suite to compare the realized section against the analytic prototype without rendering.

◆ set()

void pulp::signal::TapeEqSection::set ( const tape::EqTimeConstants & tc,
double sample_rate,
bool inverse )
inlinenoexcept

Member Data Documentation

◆ kInversePoleDamping

double pulp::signal::TapeEqSection::kInversePoleDamping = 0.90
staticconstexpr

How far inside the unit circle the inverse's pole is pulled when the forward section's numerator is (1 + z⁻¹) — i.e.

when the curve has no bass shelf and the record network is a plain one-pole with a Nyquist zero. Undamped, the inverse has unbounded gain at Nyquist. [design parameter] default 0.90, range 0.70 .. 0.999.

The tradeoff is monotone in both directions and was measured across every degenerate (curve, speed) at 44.1 / 48 / 192 kHz, worst case of each: at 0.999 the round-trip ripple over 20 Hz–15 kHz is 0.0000 dB and the ceiling +92.4 dB; at 0.98, 0.0015 dB and +67.7 dB; at 0.90, 0.0396 dB and +53.4 dB; at 0.70, 0.4260 dB and +42.9 dB. 0.90 is chosen because it buys 14 dB of ceiling for ripple that is still seven times inside the ±0.3 dB flatness criterion, and 0.70 would break that criterion to buy only ten more.

Note what the numbers say about where the ceiling comes from: even at 0.70 it is +42.9 dB. A reproduce network with no bass shelf is (1 + s·t₂) — a differentiator — so its gain near Nyquist is a property of the standard's prototype and of the sample rate, not of this damping. The damping only decides whether that gain is large or infinite.

◆ kPrewarpCeiling

double pulp::signal::TapeEqSection::kPrewarpCeiling = 0.49
staticconstexpr

Highest corner the prewarp will place, as a fraction of the sample rate.

tan(π·f/fs) diverges at f = fs/2.


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