#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 #include #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(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 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& v) noexcept; inline void applySignFlipping(std::array& 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 erTaps; std::array inputDiffusers; std::array fdnDelays; // * Thiran allpass interpolation std::array, FDN_ORDER> nestedAllpassDelays; int currentERTapCount{ 0 }; std::array currentERDelaySamples; std::array 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, FDN_ORDER> absorptionFiltersS2; std::array, FDN_ORDER> currentAbsorptionCoeffsS2; #else std::array absorptionFilters; std::array currentAbsorptionCoeffs; #endif std::array lfos; std::array chorusLFOs; // * modulation std::array fdnBaseDelaySamples; std::array 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 dcX1; std::array dcY1; float dcBlockerCoeff{ 0.999f }; // * soft-knee compression: in the FDN feedback loop std::array fdnRmsEnv; float rmsCoeff{ 0.002f }; std::array effectiveRT60; float theoreticalEDT{ 0.0f }; AcousticMetrics acousticMetrics; Saturator saturatorL; Saturator saturatorR; }; } // namespace FDNReverb