Files
InstaShadow/Source/CompressorEngine.cpp
hariel1985 a587a43ff9 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
2026-03-27 16:03:24 +01:00

146 sor
4.9 KiB
C++

#include "CompressorEngine.h"
CompressorEngine::CompressorEngine() {}
void CompressorEngine::prepare (double sampleRate, int samplesPerBlock)
{
currentSampleRate = sampleRate;
for (auto& cell : optoCells) cell.prepare (sampleRate);
for (auto& comp : vcaComps) comp.prepare (sampleRate);
transformer.prepare (sampleRate, samplesPerBlock);
// Init sidechain HPF
lastScHpfFreq = 0.0f;
updateScHpf (90.0f);
for (auto& f : scHpfFilters) f.reset();
}
void CompressorEngine::updateScHpf (float freqHz)
{
if (std::abs (freqHz - lastScHpfFreq) < 0.1f) return;
lastScHpfFreq = freqHz;
auto coeffs = juce::dsp::IIR::Coefficients<float>::makeHighPass (currentSampleRate, freqHz);
for (auto& f : scHpfFilters)
f.coefficients = coeffs;
}
void CompressorEngine::processBlock (juce::AudioBuffer<float>& buffer)
{
const int numSamples = buffer.getNumSamples();
const int numChannels = std::min (buffer.getNumChannels(), 2);
if (globalBypass.load() || numChannels == 0) return;
// Read parameters once per block
float optoThresh = optoThresholdDb.load();
float optoGain = optoGainDb.load();
float scHpf = optoScHpfHz.load();
bool optoBp = optoBypass.load();
float vcaThresh = vcaThresholdDb.load();
float vcaGain = vcaGainDb.load();
int ratioIdx = std::clamp (vcaRatioIndex.load(), 0, numRatios - 1);
int attackIdx = std::clamp (vcaAttackIndex.load(), 0, numAttacks - 1);
int releaseIdx = std::clamp (vcaReleaseIndex.load(), 0, numReleases - 1);
bool vcaBp = vcaBypass.load();
int xfmrType = transformerType.load();
float outGain = std::pow (10.0f, outputGainDb.load() / 20.0f);
bool linked = stereoLink.load();
float ratio = ratios[ratioIdx];
float attackSec = attacks[attackIdx];
float releaseSec = releases[releaseIdx];
bool dualRel = (releaseSec < 0.0f);
if (dualRel) releaseSec = 0.5f; // fallback (not used in dual mode)
float optoThreshLin = std::pow (10.0f, optoThresh / 20.0f);
float optoGainLin = std::pow (10.0f, optoGain / 20.0f);
float vcaGainLin = std::pow (10.0f, vcaGain / 20.0f);
// Update sidechain HPF
updateScHpf (scHpf);
// Per-sample processing
float peakOptoGr = 0.0f;
float peakVcaGr = 0.0f;
for (int i = 0; i < numSamples; ++i)
{
// Get input samples
float inL = buffer.getSample (0, i);
float inR = (numChannels > 1) ? buffer.getSample (1, i) : inL;
// Sidechain: HPF filtered input for detection
float scL = scHpfFilters[0].processSample (inL);
float scR = (numChannels > 1) ? scHpfFilters[1].processSample (inR) : scL;
// Sidechain level (rectified)
float scLevelL = std::abs (scL);
float scLevelR = std::abs (scR);
if (linked)
{
float scMono = (scLevelL + scLevelR) * 0.5f;
scLevelL = scMono;
scLevelR = scMono;
}
// --- Optical compressor ---
float optoGainL = 1.0f, optoGainR = 1.0f;
if (! optoBp)
{
optoGainL = optoCells[0].processSample (scLevelL, optoThreshLin);
optoGainR = linked ? optoGainL : optoCells[1].processSample (scLevelR, optoThreshLin);
inL *= optoGainL * optoGainLin;
inR *= optoGainR * optoGainLin;
float gr = optoCells[0].getGainReductionDb();
if (gr < peakOptoGr) peakOptoGr = gr;
}
// --- VCA compressor ---
if (! vcaBp)
{
// Convert to dB for VCA
float scDbL = 20.0f * std::log10 (std::max (1.0e-6f, std::abs (inL)));
float scDbR = 20.0f * std::log10 (std::max (1.0e-6f, std::abs (inR)));
if (linked) scDbL = scDbR = std::max (scDbL, scDbR);
float vcaGainL = vcaComps[0].processSample (scDbL, vcaThresh, ratio, attackSec, releaseSec, dualRel);
float vcaGainR = linked ? vcaGainL : vcaComps[1].processSample (scDbR, vcaThresh, ratio, attackSec, releaseSec, dualRel);
inL *= vcaGainL * vcaGainLin;
inR *= vcaGainR * vcaGainLin;
float gr = vcaComps[0].getGainReductionDb();
if (gr < peakVcaGr) peakVcaGr = gr;
}
// Write back (transformer and output gain applied later as block ops)
buffer.setSample (0, i, inL);
if (numChannels > 1) buffer.setSample (1, i, inR);
}
// --- Transformer saturation (block-level, 4x oversampled) ---
if (xfmrType > 0 && xfmrType <= 3)
transformer.processBlock (buffer, (TransformerSaturation::Type) xfmrType);
// --- Output gain ---
buffer.applyGain (outGain);
// --- Metering ---
optoGrDb.store (peakOptoGr);
vcaGrDb.store (peakVcaGr);
outputLevelL.store (buffer.getMagnitude (0, 0, numSamples));
if (numChannels > 1)
outputLevelR.store (buffer.getMagnitude (1, 0, numSamples));
else
outputLevelR.store (outputLevelL.load());
}