mirror of
https://codeberg.org/armin/saftpumpe.git
synced 2026-09-01 12:20:46 +02:00
119 lines
3.9 KiB
C++
119 lines
3.9 KiB
C++
#pragma once
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
class CompressorDSP {
|
|
public:
|
|
CompressorDSP() = default;
|
|
|
|
void prepare(double sampleRate, int samplesPerBlock) {
|
|
this->sampleRate = sampleRate;
|
|
envelope = 0.0;
|
|
gainReduction = 0.0;
|
|
}
|
|
|
|
void reset() {
|
|
envelope = 0.0;
|
|
gainReduction = 0.0;
|
|
}
|
|
|
|
struct Params {
|
|
float threshold = -20.0f; // dB
|
|
float ratio = 4.0f; // 2:1 to 8:1
|
|
float attack = 0.03f; // seconds
|
|
float release = 0.3f; // seconds (auto mode uses program-dependent)
|
|
float knee = 6.0f; // dB soft knee
|
|
float makeup = 0.0f; // dB
|
|
bool autoRelease = true;
|
|
float mix = 1.0f; // dry/wet
|
|
};
|
|
|
|
void setParams(const Params& p) {
|
|
params = p;
|
|
// Convert times to coefficients with analog-style smoothing
|
|
attackCoeff = std::exp(-1.0 / (sampleRate * std::max(0.001f, params.attack)));
|
|
|
|
// Auto release: program-dependent (faster for transients, slower for sustained)
|
|
if (params.autoRelease) {
|
|
releaseCoeff = std::exp(-1.0 / (sampleRate * 0.4));
|
|
} else {
|
|
releaseCoeff = std::exp(-1.0 / (sampleRate * std::max(0.01f, params.release)));
|
|
}
|
|
}
|
|
|
|
// Process a single sample (mono)
|
|
float processSample(float input) {
|
|
float absInput = std::abs(input);
|
|
|
|
// Input level in dB
|
|
float inputDb = (absInput > 1e-10f) ? 20.0f * std::log10(absInput) : -100.0f;
|
|
|
|
// Calculate desired gain reduction with soft knee
|
|
float gainDb = 0.0f;
|
|
float overThreshold = inputDb - params.threshold;
|
|
|
|
if (overThreshold > -params.knee / 2.0f) {
|
|
if (overThreshold < params.knee / 2.0f) {
|
|
// Soft knee region - smooth transition
|
|
float x = overThreshold + params.knee / 2.0f;
|
|
gainDb = (1.0f / params.ratio - 1.0f) * (x * x) / (2.0f * params.knee);
|
|
} else {
|
|
// Above knee - full compression
|
|
gainDb = overThreshold * (1.0f / params.ratio - 1.0f);
|
|
}
|
|
}
|
|
|
|
// Smooth envelope follower (analog-style ballistics)
|
|
float coeff = (gainDb < envelope) ? attackCoeff : releaseCoeff;
|
|
|
|
// Auto release: faster release for small reductions, slower for big hits
|
|
if (params.autoRelease && gainDb < envelope) {
|
|
float reductionAmount = std::abs(envelope);
|
|
float autoReleaseAdj = std::clamp(reductionAmount / 12.0f, 0.2f, 1.0f);
|
|
coeff = std::exp(-1.0 / (sampleRate * 0.15 * autoReleaseAdj));
|
|
}
|
|
|
|
envelope = coeff * envelope + (1.0f - coeff) * gainDb;
|
|
|
|
// Apply makeup gain and convert to linear
|
|
float totalGainDb = envelope + params.makeup;
|
|
float gain = std::pow(10.0f, totalGainDb / 20.0f);
|
|
|
|
// Store for metering
|
|
gainReduction = envelope;
|
|
|
|
// Apply compression with analog saturation
|
|
float compressed = input * gain;
|
|
|
|
// Subtle analog saturation (soft clipping like VCA bus compressor)
|
|
compressed = softClip(compressed);
|
|
|
|
// Dry/wet mix
|
|
float output = input * (1.0f - params.mix) + compressed * params.mix;
|
|
|
|
return output;
|
|
}
|
|
|
|
float getGainReduction() const { return gainReduction; }
|
|
|
|
private:
|
|
double sampleRate = 44100.0;
|
|
Params params;
|
|
|
|
float attackCoeff = 0.0f;
|
|
float releaseCoeff = 0.0f;
|
|
float envelope = 0.0f;
|
|
float gainReduction = 0.0f;
|
|
|
|
// Analog-style soft clipping (tanh-like)
|
|
float softClip(float x) {
|
|
// Gentle saturation that adds warmth
|
|
float drive = 1.2f; // Subtle drive
|
|
x *= drive;
|
|
|
|
// Tanh saturation for musical distortion
|
|
float out = std::tanh(x);
|
|
|
|
return out;
|
|
}
|
|
};
|