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
197
Source/DSP/Saturator.h
Normal file
197
Source/DSP/Saturator.h
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
#pragma once
|
||||
#include <cmath>
|
||||
#include <algorithm>
|
||||
|
||||
namespace FDNReverb {
|
||||
|
||||
enum class SaturationMode {
|
||||
Warm = 0,
|
||||
Tape = 1,
|
||||
Tube = 2,
|
||||
Hard = 3
|
||||
};
|
||||
|
||||
class Saturator {
|
||||
public:
|
||||
Saturator() = default;
|
||||
|
||||
void reset() noexcept {
|
||||
prevInput = 0.0f;
|
||||
switch (currentMode) {
|
||||
case SaturationMode::Warm: prevF = 1.0f; break;
|
||||
case SaturationMode::Tape: prevF = 0.0f; break;
|
||||
case SaturationMode::Tube: prevF = 1.0f; break;
|
||||
case SaturationMode::Hard: prevF = 0.0f; break;
|
||||
}
|
||||
}
|
||||
|
||||
void setMode(SaturationMode mode) noexcept {
|
||||
if (mode != currentMode) {
|
||||
currentMode = mode;
|
||||
reset();
|
||||
}
|
||||
}
|
||||
|
||||
void setMode(int modeIndex) noexcept {
|
||||
setMode(static_cast<SaturationMode>(std::clamp(modeIndex, 0, 3)));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// * Step B fix: only the drive curve changed; ADAA structure fully preserved
|
||||
// -------------------------------------------------------------------------
|
||||
// drive = 1 + amount^3 x 1.0 ( maximum 2.0) -> 1 + amount^2 x 2.5 ( maximum 3.5)
|
||||
//
|
||||
// amount | old drive | new drive | effect
|
||||
// -------|----------|----------|--------------------
|
||||
// 0.30 | 1.027 | 1.225 | + about 7.5dB stronger
|
||||
// 0.50 | 1.125 | 1.625 | + about 3.2dB stronger
|
||||
// 0.70 | 1.343 | 2.225 | + about 4.4dB stronger
|
||||
// 1.00 | 2.000 | 3.500 | + about 4.9dB stronger
|
||||
//
|
||||
// -> plugin 24 harmonics visualization
|
||||
// -------------------------------------------------------------------------
|
||||
void setAmount(float amount) noexcept {
|
||||
amount = std::clamp(amount, 0.0f, 1.0f);
|
||||
currentAmount = amount;
|
||||
|
||||
// * Step B: amount^2 x 2.5 stronger
|
||||
drive = 1.0f + amount * amount * 2.5f;
|
||||
wetMix = amount * amount * 0.7f;
|
||||
dryMix = 1.0f - amount * 0.25f;
|
||||
}
|
||||
|
||||
inline float processSample(float input) noexcept {
|
||||
if (currentAmount < 1e-4f) return input;
|
||||
|
||||
const float dryInput = input;
|
||||
const float driven = input * drive;
|
||||
float saturated = 0.0f;
|
||||
|
||||
switch (currentMode) {
|
||||
case SaturationMode::Warm: saturated = processWarm(driven); break;
|
||||
case SaturationMode::Tape: saturated = processTape(driven); break;
|
||||
case SaturationMode::Tube: saturated = processTube(driven); break;
|
||||
case SaturationMode::Hard: saturated = processHard(driven); break;
|
||||
}
|
||||
|
||||
saturated /= drive;
|
||||
return dryInput * dryMix + saturated * wetMix;
|
||||
}
|
||||
|
||||
private:
|
||||
// --- Warm: Vicanek x/sqrt(1+x^2) + ADAA 1 ---
|
||||
inline float processWarm(float x) noexcept {
|
||||
const float F_x = std::sqrt(1.0f + x * x);
|
||||
const float dx = x - prevInput;
|
||||
float y;
|
||||
constexpr float kTol = 1e-5f;
|
||||
if (std::abs(dx) < kTol) {
|
||||
const float xAvg = (x + prevInput) * 0.5f;
|
||||
y = xAvg / std::sqrt(1.0f + xAvg * xAvg);
|
||||
}
|
||||
else {
|
||||
y = (F_x - prevF) / dx;
|
||||
}
|
||||
prevInput = x;
|
||||
prevF = F_x;
|
||||
return y;
|
||||
}
|
||||
|
||||
// --- Tape: Pade x(27+x^2)/(27+9x^2) (ADAA intentional ) ---
|
||||
inline float processTape(float x) noexcept {
|
||||
if (x > 3.0f) { prevInput = x; return 1.0f; }
|
||||
if (x < -3.0f) { prevInput = x; return -1.0f; }
|
||||
const float xsq = x * x;
|
||||
prevInput = x;
|
||||
return x * (27.0f + xsq) / (27.0f + 9.0f * xsq);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Tube: asymmetric ADAA + * Step B: kNeg 1.5 -> 2.0
|
||||
// -------------------------------------------------------------------------
|
||||
// positive side : f(x) = x/sqrt(1+x^2) F(x) = sqrt(1+x^2)
|
||||
// negative side : f(x) = x/sqrt(1+(kNeg.x)^2) F(x) = (1/kNeg^2)sqrt(1+(kNeg.x)^2) + fShift
|
||||
//
|
||||
// C^1 : x=0 F_pos(0) = F_neg(0) = 1 fShift design
|
||||
// F_pos(0) = sqrt1 = 1
|
||||
// F_neg(0) = (1/kNeg^2).sqrt1 + fShift = 1
|
||||
// -> fShift = 1 - 1/kNeg^2
|
||||
//
|
||||
// kNeg=2.0 case : fShift = 1 - 0.25 = 0.75
|
||||
//
|
||||
// kNeg stronger effect :
|
||||
// 2 -> waveform asymmetric
|
||||
// -> harmonics (2f, 4f) plugin visualization
|
||||
// -------------------------------------------------------------------------
|
||||
inline float processTube(float x) noexcept {
|
||||
// * Step B: kNeg = 1.5f -> 2.0f
|
||||
constexpr float kNeg = 2.0f;
|
||||
constexpr float kNeg2 = kNeg * kNeg; // 4.0f
|
||||
constexpr float invKneg2 = 1.0f / kNeg2; // 0.25f
|
||||
constexpr float fShift = 1.0f - invKneg2; // 0.75f
|
||||
|
||||
float F_x;
|
||||
if (x >= 0.0f) {
|
||||
F_x = std::sqrt(1.0f + x * x);
|
||||
}
|
||||
else {
|
||||
const float kx = kNeg * x;
|
||||
F_x = invKneg2 * std::sqrt(1.0f + kx * kx) + fShift;
|
||||
}
|
||||
|
||||
const float dx = x - prevInput;
|
||||
const bool signChanged = (x >= 0.0f) != (prevInput >= 0.0f);
|
||||
|
||||
float y;
|
||||
constexpr float kTol = 1e-5f;
|
||||
if (std::abs(dx) < kTol || signChanged) {
|
||||
// input -> directly
|
||||
if (x >= 0.0f) {
|
||||
y = x / std::sqrt(1.0f + x * x);
|
||||
}
|
||||
else {
|
||||
const float kx = kNeg * x;
|
||||
y = x / std::sqrt(1.0f + kx * kx);
|
||||
}
|
||||
}
|
||||
else {
|
||||
y = (F_x - prevF) / dx;
|
||||
}
|
||||
|
||||
prevInput = x;
|
||||
prevF = F_x;
|
||||
return y;
|
||||
}
|
||||
|
||||
// --- Hard: clipping + ADAA 1 ---
|
||||
inline float processHard(float x) noexcept {
|
||||
float F_x;
|
||||
if (x > 1.0f) F_x = x - 0.5f;
|
||||
else if (x < -1.0f) F_x = -x - 0.5f;
|
||||
else F_x = x * x * 0.5f;
|
||||
|
||||
const float dx = x - prevInput;
|
||||
float y;
|
||||
constexpr float kTol = 1e-5f;
|
||||
if (std::abs(dx) < kTol) {
|
||||
y = std::clamp(x, -1.0f, 1.0f);
|
||||
}
|
||||
else {
|
||||
y = (F_x - prevF) / dx;
|
||||
}
|
||||
|
||||
prevInput = x;
|
||||
prevF = F_x;
|
||||
return y;
|
||||
}
|
||||
|
||||
float prevInput{ 0.0f };
|
||||
float prevF{ 1.0f };
|
||||
SaturationMode currentMode{ SaturationMode::Warm };
|
||||
float currentAmount{ 0.0f };
|
||||
float drive{ 1.0f };
|
||||
float wetMix{ 0.0f };
|
||||
float dryMix{ 1.0f };
|
||||
};
|
||||
|
||||
} // namespace FDNReverb
|
||||
Loading…
Add table
Add a link
Reference in a new issue