mirror of
https://codeberg.org/armin/ambivalence.git
synced 2026-09-01 04:10:48 +02:00
174 lines
No EOL
6.9 KiB
C++
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
|