#pragma once #include #include 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; } };