Initial release — InstaShadow mastering compressor v1.0
Dual-stage compressor (optical + VCA) with output transformer saturation. - Port-Hamiltonian T4B opto-cell model with implicit trapezoidal integration - Feed-forward VCA with 7 ratios, 6 attack/release presets, Dual release mode - 3 transformer types (Nickel/Iron/Steel) with 4x oversampled waveshaping - Analog-style needle VU meters, horizontal GR meters - Sidechain HPF, stereo link, independent section bypass - Full state save/restore, CI/CD for Windows/macOS/Linux
This commit is contained in:
100
Source/VCACompressor.cpp
Normal file
100
Source/VCACompressor.cpp
Normal file
@@ -0,0 +1,100 @@
|
||||
#include "VCACompressor.h"
|
||||
|
||||
VCACompressor::VCACompressor() {}
|
||||
|
||||
void VCACompressor::prepare (double sampleRate)
|
||||
{
|
||||
sr = sampleRate;
|
||||
reset();
|
||||
}
|
||||
|
||||
void VCACompressor::reset()
|
||||
{
|
||||
smoothedGrDb = 0.0;
|
||||
dualFastEnv = 0.0;
|
||||
dualSlowEnv = 0.0;
|
||||
wasCompressing = false;
|
||||
currentGrDb = 0.0f;
|
||||
}
|
||||
|
||||
double VCACompressor::makeCoeff (double timeSec) const
|
||||
{
|
||||
if (timeSec <= 0.0) return 0.0;
|
||||
return std::exp (-1.0 / (sr * timeSec));
|
||||
}
|
||||
|
||||
float VCACompressor::computeGainReduction (float inputDb, float thresholdDb, float ratio) const
|
||||
{
|
||||
// Soft-knee gain computer
|
||||
float halfKnee = kneeWidthDb * 0.5f;
|
||||
|
||||
if (inputDb < thresholdDb - halfKnee)
|
||||
{
|
||||
return 0.0f; // below threshold — no compression
|
||||
}
|
||||
else if (inputDb > thresholdDb + halfKnee)
|
||||
{
|
||||
// Above knee — full compression
|
||||
float compressed = thresholdDb + (inputDb - thresholdDb) / ratio;
|
||||
return compressed - inputDb; // negative dB value
|
||||
}
|
||||
else
|
||||
{
|
||||
// In knee region — quadratic interpolation
|
||||
float x = inputDb - thresholdDb + halfKnee;
|
||||
return ((1.0f / ratio) - 1.0f) * x * x / (2.0f * kneeWidthDb);
|
||||
}
|
||||
}
|
||||
|
||||
float VCACompressor::processSample (float sidechainDb, float thresholdDb, float ratio,
|
||||
float attackSec, float releaseSec, bool dualRelease)
|
||||
{
|
||||
// Compute desired gain reduction
|
||||
float desiredGrDb = computeGainReduction (sidechainDb, thresholdDb, ratio);
|
||||
|
||||
if (! dualRelease)
|
||||
{
|
||||
// Standard attack/release smoothing
|
||||
double coeff;
|
||||
if (desiredGrDb < smoothedGrDb)
|
||||
coeff = makeCoeff ((double) attackSec); // attacking (GR going more negative)
|
||||
else
|
||||
coeff = makeCoeff ((double) releaseSec); // releasing
|
||||
|
||||
smoothedGrDb = coeff * smoothedGrDb + (1.0 - coeff) * (double) desiredGrDb;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Dual release mode: two-stage release mimicking optical behavior
|
||||
double attackCoeff = makeCoeff ((double) attackSec);
|
||||
|
||||
if (desiredGrDb < smoothedGrDb)
|
||||
{
|
||||
// Attacking
|
||||
smoothedGrDb = attackCoeff * smoothedGrDb + (1.0 - attackCoeff) * (double) desiredGrDb;
|
||||
dualFastEnv = smoothedGrDb;
|
||||
dualSlowEnv = smoothedGrDb;
|
||||
wasCompressing = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Releasing: blend fast (~60ms) and slow (~2s) release
|
||||
double fastCoeff = makeCoeff (0.06); // 60ms — first 50-80% of recovery
|
||||
double slowCoeff = makeCoeff (2.0); // 2s — remaining recovery
|
||||
|
||||
dualFastEnv = fastCoeff * dualFastEnv + (1.0 - fastCoeff) * (double) desiredGrDb;
|
||||
dualSlowEnv = slowCoeff * dualSlowEnv + (1.0 - slowCoeff) * (double) desiredGrDb;
|
||||
|
||||
// Blend: use the DEEPER (more negative) of the two
|
||||
// This naturally creates the two-stage behavior:
|
||||
// fast env recovers quickly, slow env holds longer
|
||||
smoothedGrDb = std::min (dualFastEnv, dualSlowEnv);
|
||||
wasCompressing = false;
|
||||
}
|
||||
}
|
||||
|
||||
currentGrDb = (float) smoothedGrDb;
|
||||
|
||||
// Convert GR dB to linear gain
|
||||
return std::pow (10.0f, currentGrDb / 20.0f);
|
||||
}
|
||||
Reference in New Issue
Block a user