ambivalence/Source/DSP/UniversalEngine.h
2026-08-15 15:36:28 +02:00

174 lines
No EOL
6.9 KiB
C++

#pragma once
#include "DelayMemory.h"
#include "BiquadFilters.h"
#include "MagnitudeResponseFitter.h"
#include "AcousticMetrics.h"
#include "Saturator.h"
#include "OutputLimiter.h"
#include "OutputEQ.h"
#include "../PluginParameters.h"
#include <array>
#include <cmath>
#define AMBIVALENCE_USE_STAGE2_ABSORPTION 1
namespace FDNReverb {
enum class ReverbTopology { Room, Hall, Plate, Spring, Goldfoil };
// -----------------------------------------------------------------------------
// BandlimitedNoiseLFO: color noise + 1 IIR LPF
// -----------------------------------------------------------------------------
struct BandlimitedNoiseLFO {
uint32_t state{ 12345u };
float smoothed{ 0.0f };
float rateMultiplier{ 1.0f };
inline float nextNoise() noexcept {
state ^= state << 13;
state ^= state >> 17;
state ^= state << 5;
return static_cast<float>(state) * 2.3283064365386963e-10f * 2.0f - 1.0f;
}
inline float tick(float lpfCoeff) noexcept {
smoothed += (nextNoise() - smoothed) * lpfCoeff;
return smoothed;
}
};
// -----------------------------------------------------------------------------
// ChorusLFO: sine-wave phase (modulation)
// -----------------------------------------------------------------------------
struct ChorusLFO {
float phase{ 0.0f };
float phaseInc{ 0.0f };
float rateScale{ 1.0f }; // per-channel rate coefficient (multiplier)
// * CPU: std::sin() replaced by a parabolic approximation (max error ~0.06%, 5-10x faster)
inline float tick() noexcept {
phase += phaseInc;
if (phase >= 1.0f) phase -= 1.0f;
// Parabolic sine: phase [0,1) -> sin(2pi.phase)
const float x = phase < 0.5f ? phase : phase - 1.0f;
const float para = 16.0f * x * (0.5f - std::abs(x));
return para * (0.775f + 0.225f * std::abs(para));
}
};
class UniversalEngine {
public:
UniversalEngine();
void prepare(double sampleRate, int maxBlockSize);
void reset();
void setParams(const DSPParams& p);
void processBlock(const float* inL, const float* inR,
float* outL, float* outR, int numSamples) noexcept;
std::array<float, NUM_BANDS> getEffectiveRT60() const noexcept { return effectiveRT60; }
float getD50() const noexcept { return acousticMetrics.getD50(); }
float getC50() const noexcept { return acousticMetrics.getC50(); }
float getC80() const noexcept { return acousticMetrics.getC80(); }
float getEDT() const noexcept { return theoreticalEDT; }
const AcousticMetrics& getAcousticMetrics() const noexcept { return acousticMetrics; }
int getERTapCount() const noexcept { return currentERTapCount; }
float getERTapDelaySamples(int index) const noexcept {
return (index >= 0 && index < currentERTapCount) ? currentERDelaySamples[index] : 0.0f;
}
float getERTapGain(int index) const noexcept {
return (index >= 0 && index < currentERTapCount) ? currentERGains[index] : 0.0f;
}
double getSampleRate() const noexcept { return fs; }
bool isERBypassed() const noexcept { return bypassER; }
private:
void updateTopologyAndRouting();
void calculatePrimePowerDelays();
inline void fastWalshHadamardTransform(std::array<float, 16>& v) noexcept;
inline void applySignFlipping(std::array<float, 16>& v) noexcept;
// --- FDN loop saturation ---
inline static float processMicroSaturation(float x) noexcept {
constexpr float kInScale = 0.15f;
constexpr float kOutScale = 1.0f / kInScale;
const float xs = x * kInScale;
if (xs > 3.0f) return kOutScale;
if (xs < -3.0f) return -kOutScale;
const float xsq = xs * xs;
return (xs * (27.0f + xsq) / (27.0f + 9.0f * xsq)) * kOutScale;
}
DelayMemoryPool memoryPool;
double fs{ 48000.0 };
DSPParams activeParams;
ReverbTopology currentTopology{ ReverbTopology::Room };
static constexpr int FDN_ORDER = 16;
static constexpr int SERIAL_APF_STAGES = 3; // * Allpass stages
// * PreDelay (max 500 ms)
LinearDelayLine preDelayLine;
float preDelaySamples{ 0.0f };
LinearDelayLine erDelay;
std::array<float, 16> erTaps;
std::array<LinearDelayLine, 4> inputDiffusers;
std::array<ThiranDelayLine, FDN_ORDER> fdnDelays; // * Thiran allpass interpolation
std::array<std::array<LinearDelayLine, SERIAL_APF_STAGES>, FDN_ORDER> nestedAllpassDelays;
int currentERTapCount{ 0 };
std::array<float, MAX_ER_TAPS> currentERDelaySamples;
std::array<float, MAX_ER_TAPS> currentERGains;
OutputLimiter outputLimiter;
OutputEQ outputEQ; // * Phase 5 added
float duckingEnvelope{ 0.0f };
float duckingAttackCoeff{ 0.0f };
float duckingReleaseCoeff{ 0.0f };
#if AMBIVALENCE_USE_STAGE2_ABSORPTION
std::array<std::array<BiquadState, ABSO_STAGES_S2>, FDN_ORDER> absorptionFiltersS2;
std::array<std::array<BiquadCoeffs, ABSO_STAGES_S2>, FDN_ORDER> currentAbsorptionCoeffsS2;
#else
std::array<BiquadState, FDN_ORDER> absorptionFilters;
std::array<BiquadCoeffs, FDN_ORDER> currentAbsorptionCoeffs;
#endif
std::array<BandlimitedNoiseLFO, FDN_ORDER> lfos;
std::array<ChorusLFO, FDN_ORDER> chorusLFOs; // * modulation
std::array<float, FDN_ORDER> fdnBaseDelaySamples;
std::array<float, FDN_ORDER> fbVec;
float apfGain{ 0.618f };
bool bypassER{ false };
bool bypassInputDiffusers{ false }; // * new: default false
float lateMixScale{ 1.0f };
float lateMakeupGainLinear{ 1.0f };
// * Phase 5 addition: Diffusion
float diffusionSensitivity{ 1.0f };
// * metallic sound: DecayTime depends on parameters
float microSatBlend{ 1.0f }; // FDN loop saturation blend (0 = bypass, 1 = full)
float modDepthScale{ 1.0f }; // modulation depth scale (increases with Decay time)
// * DC: prevent DC accumulation in the FDN loop
std::array<float, FDN_ORDER> dcX1;
std::array<float, FDN_ORDER> dcY1;
float dcBlockerCoeff{ 0.999f };
// * soft-knee compression: in the FDN feedback loop
std::array<float, FDN_ORDER> fdnRmsEnv;
float rmsCoeff{ 0.002f };
std::array<float, NUM_BANDS> effectiveRT60;
float theoreticalEDT{ 0.0f };
AcousticMetrics acousticMetrics;
Saturator saturatorL;
Saturator saturatorR;
};
} // namespace FDNReverb