// filter_coeffs.h — Zavalishin topology-preserving-transform state-variable coefficients.
// The rate enters ONLY through g = tan(pi*fc/sr); there is no reference or calibration rate
// anywhere in this module, and reintroducing one would restore the rate-dependent resonance
// the TPT rewrite exists to remove.

#pragma once

#include "core/instrument/engine/filter/filter_params.h"

namespace reasampler::instrument::engine::filter {

// The two-integrator SVF's per-sample constants. a1/a2/a3 are the algebraic solution of the
// zero-delay feedback loop, so the kernel needs no iteration.
struct SvfCoeffs {
    float g = 0.0f;   // tan(pi*fc/sr) — the ONLY place the sample rate appears
    float k = 1.0f;   // 1/Q, the damping term
    float a1 = 1.0f;
    float a2 = 0.0f;
    float a3 = 0.0f;
};

// Highest fraction of the sample rate the pre-warp stays well-conditioned at: tan() diverges
// as fc approaches sr/2.
inline constexpr double kFilterNyquistFraction = 0.48;

// cutoffHz is clamped into [kFilterCutoffMinHz, kFilterNyquistFraction*sampleRate] and q into
// [kFilterQMin, kFilterQMax]. A non-positive sampleRate yields g == 0 — we refuse to invent a
// rate rather than assume 44.1k.
//
// Float storage is safe HERE in a way it was not for the retired Direct Form I path. DF1 encoded
// pole proximity in a1 -> -2, a2 -> +1 and cancelled them against each other every sample; at
// fc/sr ~ 1e-4 that ~17-bit cancellation moved the measured 20 Hz/192 kHz LP peak by -27%
// (true-peak scan) to -57% (point measurement at the analytic peak frequency, since the
// degraded pole itself moves) -- and the error is non-monotone with rate, not a fixed percentage
// (+5% high at 96 kHz). TPT encodes the same proximity in a1's small DEVIATION from 1, which
// float resolves: measured against an exact-double evaluation of the same difference equation
// (which matches the analytic target to within measurement noise), TPT's float32-narrowed
// coefficients land genuinely ~0.02% low at 48 kHz, widening to ~0.03% low at 192 kHz -- both
// comfortably inside the test's 0.4% tolerance.
SvfCoeffs svfCoeffs(float cutoffHz, float q, double sampleRate);

}  // namespace reasampler::instrument::engine::filter
