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:
96
Source/OpticalCell.cpp
Normal file
96
Source/OpticalCell.cpp
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "OpticalCell.h"
|
||||
|
||||
OpticalCell::OpticalCell() {}
|
||||
|
||||
void OpticalCell::prepare (double sampleRate)
|
||||
{
|
||||
sr = sampleRate;
|
||||
dt = 1.0 / (2.0 * sampleRate); // 2x oversampled
|
||||
reset();
|
||||
}
|
||||
|
||||
void OpticalCell::reset()
|
||||
{
|
||||
q_el = 0.0;
|
||||
R_cds = R_cds_dark;
|
||||
lightOutput = 0.0;
|
||||
lightHistory = 0.0;
|
||||
currentGain = 1.0;
|
||||
prevInput = 0.0f;
|
||||
}
|
||||
|
||||
void OpticalCell::solveImplicitStep (double inputLevel)
|
||||
{
|
||||
// Drive from sidechain level (rectified, normalized 0..1+)
|
||||
double i_drive = std::max (0.0, inputLevel);
|
||||
|
||||
// Store old state for trapezoidal rule
|
||||
double q_old = q_el;
|
||||
double f_old = (i_drive - q_old / C_el) / R_el;
|
||||
|
||||
// Initial guess (forward Euler)
|
||||
double q_new = q_old + dt * f_old;
|
||||
|
||||
// Newton-Raphson iteration (implicit trapezoidal)
|
||||
for (int iter = 0; iter < maxNewtonIter; ++iter)
|
||||
{
|
||||
double f_new = (i_drive - q_new / C_el) / R_el;
|
||||
double residual = q_new - q_old - (dt / 2.0) * (f_old + f_new);
|
||||
double jacobian = 1.0 + dt / (2.0 * R_el * C_el);
|
||||
|
||||
double delta = residual / jacobian;
|
||||
q_new -= delta;
|
||||
|
||||
if (std::abs (delta) < newtonTol)
|
||||
break;
|
||||
}
|
||||
|
||||
q_el = q_new;
|
||||
|
||||
// Light output proportional to STORED ENERGY (voltage^2)
|
||||
// This ensures continuous light output at steady state
|
||||
double voltage = q_el / C_el;
|
||||
lightOutput = eta_el * voltage * voltage;
|
||||
|
||||
// Update memory (illumination history) — explicit Euler
|
||||
lightHistory += dt * (lightOutput - lightHistory) / memoryTau;
|
||||
lightHistory = std::max (0.0, lightHistory);
|
||||
|
||||
// CdS resistance: R = k * (L_eff)^(-gamma)
|
||||
// Memory effect: effective light includes accumulated history
|
||||
double L_eff = lightOutput + memoryAlpha * lightHistory;
|
||||
double epsilon = 1.0e-10;
|
||||
|
||||
R_cds = k_cds * std::pow (L_eff + epsilon, -gamma);
|
||||
R_cds = std::clamp (R_cds, R_cds_min, R_cds_dark);
|
||||
}
|
||||
|
||||
void OpticalCell::updateGain()
|
||||
{
|
||||
// Shunt attenuator: high R_cds = pass, low R_cds = attenuate
|
||||
currentGain = R_cds / (R_fixed + R_cds);
|
||||
}
|
||||
|
||||
float OpticalCell::processSample (float sidechainLevel, float thresholdLinear)
|
||||
{
|
||||
// Envelope above threshold
|
||||
float envelope = std::max (0.0f, sidechainLevel - thresholdLinear);
|
||||
|
||||
// 2x oversampling: interpolate and process two sub-samples
|
||||
float mid = (prevInput + envelope) * 0.5f;
|
||||
prevInput = envelope;
|
||||
|
||||
solveImplicitStep ((double) mid);
|
||||
solveImplicitStep ((double) envelope);
|
||||
|
||||
updateGain();
|
||||
|
||||
return (float) currentGain;
|
||||
}
|
||||
|
||||
float OpticalCell::getGainReductionDb() const
|
||||
{
|
||||
if (currentGain <= 0.0)
|
||||
return -60.0f;
|
||||
return (float) (20.0 * std::log10 (currentGain));
|
||||
}
|
||||
Reference in New Issue
Block a user