feat: serial low-level shaper (Low Slope + Low Curve) before the compressor

Add a per-channel below-threshold shaper composed in series ahead of the comp:
gain = low_shape(level) + comp(level + low_shape(level)). The compressor's
threshold now sees the shaped level, so a Low Slope boost lifts quiet material
up into compression (and a cut pulls it out). Anchored at the -60 dB silence
floor. Low Curve bends the shaper toward a bounded saturation so the serial
composition doesn't blow up (0 = straight line).

gain_computer split into comp_gain_db + low_gain_db and composed; shared with
the editor gain-curve display. Slider order rearranged to read in signal order
(pre-gain -> low shaper -> compressor -> output). Defaults (slope 1, curve 0)
reproduce the plain compressor; 17 tests pass.

Known: the bipolar behaviour isn't final yet (milestone commit).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Mikkeli Matlock
2026-06-26 00:53:40 +09:00
parent 123703d34d
commit a4c542b2d9
6 changed files with 120 additions and 15 deletions
+5
View File
@@ -47,10 +47,15 @@ pub(crate) fn create(params: Arc<Codename206Params>, meters: Arc<Meters>) -> Opt
// One column of controls for a single compressor channel (placeholder layout).
let band_col = |ui: &mut egui::Ui, title: &str, p: &CompressorParams| {
// Roughly in signal order: input drive -> low shaper -> compressor -> output.
ui.strong(title);
ui.label("Pre-gain");
ui.add(widgets::ParamSlider::for_param(&p.pre_gain_db, setter));
ui.add(widgets::ParamSlider::for_param(&p.detection, setter));
ui.label("Low Slope");
ui.add(widgets::ParamSlider::for_param(&p.low_slope, setter));
ui.label("Low Curve");
ui.add(widgets::ParamSlider::for_param(&p.low_curve, setter));
ui.label("Threshold");
ui.add(widgets::ParamSlider::for_param(&p.threshold_db, setter));
ui.label("Ratio");