saftpumpe/Source/CompressorDSP.h

119 lines
3.9 KiB
C
Raw Permalink Normal View History

2026-07-12 14:18:24 +02:00
#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;
}
};