mirror of
https://codeberg.org/armin/ambivalence.git
synced 2026-09-01 12:20:48 +02:00
130 lines
6.1 KiB
C
130 lines
6.1 KiB
C
|
|
#pragma once
|
||
|
|
#include "DSPConstants.h"
|
||
|
|
#include "BiquadFilters.h"
|
||
|
|
#include "../AlgorithmPresets.h"
|
||
|
|
#include <array>
|
||
|
|
|
||
|
|
namespace FDNReverb {
|
||
|
|
|
||
|
|
// -----------------------------------------------------------------------------
|
||
|
|
// MagnitudeResponseFitter
|
||
|
|
// -----------------------------------------------------------------------------
|
||
|
|
// designs the 10-band RT60 absorption filters for the FDN.
|
||
|
|
//
|
||
|
|
// design modes :
|
||
|
|
// Stage 1 (Jot first-order orthogonalizing):
|
||
|
|
// Jot-Chaigne (AES Preprint 3030, 1991) first-order orthogonalizing filters.
|
||
|
|
// matched at DC and Nyquist with 2 design points.
|
||
|
|
//
|
||
|
|
// Stage 2c (Valimaki-Liski cumulative GEQ):
|
||
|
|
// Valimaki & Liski (IEEE SPL 2017) Interaction Matrix + WLS
|
||
|
|
// exact fit across the 10 bands.
|
||
|
|
//
|
||
|
|
// safety guarantee :
|
||
|
|
// - targets are clamped to 0 dB or below -> loop gain <= 1 is guaranteed
|
||
|
|
// - band 0 midGain and b0/b1/b2 are absorbed into the applied filter
|
||
|
|
// - LF/HF corrections are independent GEQ targets in dB
|
||
|
|
//
|
||
|
|
// important :
|
||
|
|
// - per-band decay in dB: -60*m / (fs*T60)
|
||
|
|
// avoids the "2 kHz T60 assumption" of Schlecht-Habets (DAFx-17)
|
||
|
|
// - design runs offline (message thread); the resulting Biquad coefficients
|
||
|
|
// are used on the audio thread
|
||
|
|
// -----------------------------------------------------------------------------
|
||
|
|
class MagnitudeResponseFitter {
|
||
|
|
public:
|
||
|
|
enum class DesignMode {
|
||
|
|
Stage1_Jot1stOrder, // Jot first-order orthogonalizing (2 pts: DC/Nyquist)
|
||
|
|
Stage2_BiquadGEQ // Valimaki-Liski cumulative GEQ (exact at 10 bands)
|
||
|
|
};
|
||
|
|
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// Stage 1 design result (existing)
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// ABSO_STAGES = 3 Biquads:
|
||
|
|
// coeffs[0] = gain (Jot first-order orthogonalizing filter, Biquad form)
|
||
|
|
// coeffs[1] = low-band correction (Low Shelf, LF Absorption)
|
||
|
|
// coeffs[2] = high-band correction (High Shelf, HF Damping)
|
||
|
|
struct DesignResult {
|
||
|
|
std::array<BiquadCoeffs, ABSO_STAGES> coeffs;
|
||
|
|
float dcGain{ 1.0f };
|
||
|
|
float nyquistGain{ 1.0f };
|
||
|
|
float pole{ 0.0f };
|
||
|
|
};
|
||
|
|
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// Stage 2c design result
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// 10-band GEQ:
|
||
|
|
// geqStages[0] = band 0 (31.25 Hz), midGain absorbed into the coefficient
|
||
|
|
// geqStages[1..9] = bands 1-9 (62.5 Hz - 16 kHz), GEQ
|
||
|
|
//
|
||
|
|
// filter chain: geqStages[0] -> geqStages[1] -> ... -> geqStages[9]
|
||
|
|
// no separate midGain stage is needed (absorbed into band 0).
|
||
|
|
struct DesignResultStage2 {
|
||
|
|
std::array<BiquadCoeffs, NUM_BANDS> geqStages; // 10-band GEQ
|
||
|
|
|
||
|
|
// visualization
|
||
|
|
std::array<float, NUM_BANDS> targetDb; // per-band target dB (after clamping)
|
||
|
|
std::array<float, NUM_BANDS> commandDb; // WLS-solved command dB
|
||
|
|
float midGainAbsorbed{ 1.0f }; // midGain absorbed into band 0
|
||
|
|
};
|
||
|
|
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// Stage 1 design function (existing)
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
static DesignResult design(
|
||
|
|
int delaySamples,
|
||
|
|
double sampleRate,
|
||
|
|
const std::array<float, NUM_BANDS>& rt60,
|
||
|
|
float hfDamping,
|
||
|
|
float lfAbsorption);
|
||
|
|
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// Stage 2c design function
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
static DesignResultStage2 designStage2(
|
||
|
|
int delaySamples,
|
||
|
|
double sampleRate,
|
||
|
|
const std::array<float, NUM_BANDS>& rt60,
|
||
|
|
float hfDamping,
|
||
|
|
float lfAbsorption);
|
||
|
|
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
// precompute the interaction matrix once (per sample rate)
|
||
|
|
// -------------------------------------------------------------------------
|
||
|
|
static void precomputeInteractionMatrix(double sampleRate);
|
||
|
|
static double getCachedSampleRate() noexcept { return cachedSampleRate; }
|
||
|
|
|
||
|
|
private:
|
||
|
|
// -- Stage 1 --
|
||
|
|
static float t60ToLoopGain(float t60Seconds, int delaySamples, double sampleRate) noexcept;
|
||
|
|
static float computeJotPole(float gDC, float alphaRatio) noexcept;
|
||
|
|
static BiquadCoeffs orthogonalizedFirstOrderToBiquad(float gain, float pole) noexcept;
|
||
|
|
static float getT60AtDC(const std::array<float, NUM_BANDS>& rt60) noexcept;
|
||
|
|
static float getT60AtNyquist(const std::array<float, NUM_BANDS>& rt60, double sampleRate) noexcept;
|
||
|
|
|
||
|
|
// -- Stage 2 --
|
||
|
|
static BiquadCoeffs designSymmetricPeakBiquad(
|
||
|
|
float fcHz, float gainDB, float Q, double sampleRate) noexcept;
|
||
|
|
static const std::array<float, NUM_BANDS>& getBandFreqs() noexcept { return BAND_FREQ; }
|
||
|
|
static const std::array<float, NUM_BANDS>& getBandQs() noexcept;
|
||
|
|
static float biquadMagnitudeDB(const BiquadCoeffs& c, float fEval, double sampleRate) noexcept;
|
||
|
|
static void solveLDLT10(
|
||
|
|
const std::array<std::array<double, NUM_BANDS>, NUM_BANDS>& A,
|
||
|
|
const std::array<double, NUM_BANDS>& b,
|
||
|
|
std::array<double, NUM_BANDS>& x) noexcept;
|
||
|
|
|
||
|
|
// absorb the entire DC gain of the Biquad (b0, b1, b2) into a gain
|
||
|
|
// so an independent DC gain can be applied to the filter mathematically
|
||
|
|
static BiquadCoeffs absorbGainIntoBiquad(const BiquadCoeffs& c, float linearGain) noexcept;
|
||
|
|
|
||
|
|
// -- Stage 2 static --
|
||
|
|
static std::array<std::array<double, NUM_BANDS>, NUM_BANDS> cachedB;
|
||
|
|
static std::array<std::array<double, NUM_BANDS>, NUM_BANDS> cachedBtWB;
|
||
|
|
static std::array<double, NUM_BANDS> cachedW;
|
||
|
|
static double cachedSampleRate;
|
||
|
|
static bool cacheValid;
|
||
|
|
};
|
||
|
|
|
||
|
|
} // namespace FDNReverb
|