mirror of
https://codeberg.org/armin/ambivalence.git
synced 2026-09-01 04:10:48 +02:00
init
This commit is contained in:
parent
29c860587e
commit
42cf8432bf
57 changed files with 5782 additions and 0 deletions
174
Source/DSP/UniversalEngine.h
Normal file
174
Source/DSP/UniversalEngine.h
Normal file
|
|
@ -0,0 +1,174 @@
|
|||
#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
|
||||
Loading…
Add table
Add a link
Reference in a new issue