refactor: split lib.rs into params and editor modules
Move parameter structs, defaults, and build_settings into src/params.rs; move the egui editor into src/editor.rs. lib.rs now holds only the plugin shell, DSP wiring, and process(). No behavior change (16/16 tests pass). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+3
-3
@@ -1,8 +1,8 @@
|
|||||||
//! DSP building blocks for Codename 206.
|
//! DSP building blocks for Codename 206.
|
||||||
//!
|
//!
|
||||||
//! Stage 2 introduces the full-band compressor (also the engine that will be reused
|
//! The signal chain: a `crossover` filterbank splits into bands, each band (plus the summed
|
||||||
//! per band and for the 'All' aggregate channel — see README.md). Later stages add the
|
//! 'All' channel) runs a `compressor`, and a `limiter` (with a true-peak `oversampler` detector)
|
||||||
//! crossover filterbank, output limiter, and oversampler alongside it.
|
//! is the final stage. `biquad` is the shared filter primitive the crossover is built from.
|
||||||
|
|
||||||
pub mod biquad;
|
pub mod biquad;
|
||||||
pub mod compressor;
|
pub mod compressor;
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
//! egui editor.
|
||||||
|
//!
|
||||||
|
//! Placeholder layout for now — Stage 6 replaces it with meters, gain-reduction displays, a
|
||||||
|
//! gain-curve view and draggable crossover handles. Built to stay usable meanwhile: a resizable
|
||||||
|
//! window with a vertical scroll area so every control is reachable at any window size, global
|
||||||
|
//! controls in a label|slider grid, and the four channels (low/mid/high/all) side by side.
|
||||||
|
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use nih_plug_egui::{
|
||||||
|
create_egui_editor,
|
||||||
|
egui::{self, Vec2},
|
||||||
|
resizable_window::ResizableWindow,
|
||||||
|
widgets,
|
||||||
|
};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::params::{Codename206Params, CompressorParams};
|
||||||
|
use crate::Codename206;
|
||||||
|
|
||||||
|
/// Build the plugin editor over a shared handle to the params.
|
||||||
|
pub(crate) fn create(params: Arc<Codename206Params>) -> Option<Box<dyn Editor>> {
|
||||||
|
let egui_state = params.editor_state.clone();
|
||||||
|
create_egui_editor(
|
||||||
|
params.editor_state.clone(),
|
||||||
|
(),
|
||||||
|
|_, _| {},
|
||||||
|
move |egui_ctx, setter, _state| {
|
||||||
|
// One column of controls for a single compressor channel.
|
||||||
|
let band_col = |ui: &mut egui::Ui, title: &str, p: &CompressorParams| {
|
||||||
|
ui.strong(title);
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.detection, setter));
|
||||||
|
ui.label("Threshold");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.threshold_db, setter));
|
||||||
|
ui.label("Ratio");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.ratio, setter));
|
||||||
|
ui.label("Knee");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.knee_db, setter));
|
||||||
|
ui.label("Attack");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.attack_ms, setter));
|
||||||
|
ui.label("Release");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.release_ms, setter));
|
||||||
|
ui.label("Makeup");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.makeup_db, setter));
|
||||||
|
ui.add(widgets::ParamSlider::for_param(&p.bypass, setter));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Resizable window; vertical scroll so every control stays reachable even when the
|
||||||
|
// window is small. (Placeholder layout — Stage 6 will replace it.)
|
||||||
|
ResizableWindow::new("editor")
|
||||||
|
.min_size(Vec2::new(480.0, 320.0))
|
||||||
|
.show(egui_ctx, egui_state.as_ref(), |ui| {
|
||||||
|
egui::ScrollArea::vertical().show(ui, |ui| {
|
||||||
|
ui.heading(Codename206::NAME);
|
||||||
|
// Global controls stacked vertically so they never overflow sideways.
|
||||||
|
egui::Grid::new("globals").num_columns(2).show(ui, |ui| {
|
||||||
|
ui.label("Xover Lo/Mid");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(¶ms.crossover_low_hz, setter));
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("Xover Mid/Hi");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(¶ms.crossover_high_hz, setter));
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("Look-ahead");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(¶ms.look_ahead_ms, setter));
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("Ceiling");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(¶ms.output_ceiling_db, setter));
|
||||||
|
ui.end_row();
|
||||||
|
ui.label("Lim Release");
|
||||||
|
ui.add(widgets::ParamSlider::for_param(¶ms.limiter_release_ms, setter));
|
||||||
|
ui.end_row();
|
||||||
|
});
|
||||||
|
ui.separator();
|
||||||
|
ui.columns(4, |cols| {
|
||||||
|
band_col(&mut cols[0], "LOW", ¶ms.low);
|
||||||
|
band_col(&mut cols[1], "MID", ¶ms.mid);
|
||||||
|
band_col(&mut cols[2], "HIGH", ¶ms.high);
|
||||||
|
band_col(&mut cols[3], "ALL", ¶ms.all);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
+6
-253
@@ -1,16 +1,14 @@
|
|||||||
use nih_plug::prelude::*;
|
use nih_plug::prelude::*;
|
||||||
use nih_plug_egui::{
|
|
||||||
create_egui_editor,
|
|
||||||
egui::{self, Vec2},
|
|
||||||
resizable_window::ResizableWindow,
|
|
||||||
widgets, EguiState,
|
|
||||||
};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
mod dsp;
|
mod dsp;
|
||||||
use dsp::compressor::{Compressor, CompressorSettings, MAX_LOOKAHEAD_MS};
|
mod editor;
|
||||||
|
mod params;
|
||||||
|
|
||||||
|
use dsp::compressor::{Compressor, MAX_LOOKAHEAD_MS};
|
||||||
use dsp::crossover::Crossover;
|
use dsp::crossover::Crossover;
|
||||||
use dsp::limiter::Limiter;
|
use dsp::limiter::Limiter;
|
||||||
|
use params::{build_settings, Codename206Params};
|
||||||
|
|
||||||
/// Band indices into the compressor array: low, mid, high, then the 'All' aggregate channel.
|
/// Band indices into the compressor array: low, mid, high, then the 'All' aggregate channel.
|
||||||
const LOW: usize = 0;
|
const LOW: usize = 0;
|
||||||
@@ -18,17 +16,6 @@ const MID: usize = 1;
|
|||||||
const HIGH: usize = 2;
|
const HIGH: usize = 2;
|
||||||
const ALL: usize = 3;
|
const ALL: usize = 3;
|
||||||
|
|
||||||
/// Level-detection mode for a compressor's detector.
|
|
||||||
#[derive(Enum, PartialEq, Clone, Copy)]
|
|
||||||
enum DetectionMode {
|
|
||||||
#[id = "peak"]
|
|
||||||
#[name = "Peak"]
|
|
||||||
Peak,
|
|
||||||
#[id = "rms"]
|
|
||||||
#[name = "RMS"]
|
|
||||||
Rms,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Codename 206 — Stage 3: 3-band crossover + per-band compressors summed into an 'All' channel.
|
/// Codename 206 — Stage 3: 3-band crossover + per-band compressors summed into an 'All' channel.
|
||||||
///
|
///
|
||||||
/// Signal: input → LR4 crossover → {low, mid, high} each through their own compressor → sum →
|
/// Signal: input → LR4 crossover → {low, mid, high} each through their own compressor → sum →
|
||||||
@@ -44,58 +31,6 @@ struct Codename206 {
|
|||||||
limiter: Limiter,
|
limiter: Limiter,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Params)]
|
|
||||||
struct Codename206Params {
|
|
||||||
#[persist = "editor-state"]
|
|
||||||
editor_state: Arc<EguiState>,
|
|
||||||
|
|
||||||
/// Low/Mid crossover frequency.
|
|
||||||
#[id = "xover_lo"]
|
|
||||||
pub crossover_low_hz: FloatParam,
|
|
||||||
/// Mid/High crossover frequency.
|
|
||||||
#[id = "xover_hi"]
|
|
||||||
pub crossover_high_hz: FloatParam,
|
|
||||||
/// Global look-ahead time (constant reported latency — safe to adjust during playback).
|
|
||||||
#[id = "lookahead"]
|
|
||||||
pub look_ahead_ms: FloatParam,
|
|
||||||
|
|
||||||
/// Output brickwall ceiling (the limiter never lets output exceed this).
|
|
||||||
#[id = "ceiling"]
|
|
||||||
pub output_ceiling_db: FloatParam,
|
|
||||||
/// Output limiter release time.
|
|
||||||
#[id = "lim_rel"]
|
|
||||||
pub limiter_release_ms: FloatParam,
|
|
||||||
|
|
||||||
#[nested(id_prefix = "low", group = "Low")]
|
|
||||||
pub low: CompressorParams,
|
|
||||||
#[nested(id_prefix = "mid", group = "Mid")]
|
|
||||||
pub mid: CompressorParams,
|
|
||||||
#[nested(id_prefix = "high", group = "High")]
|
|
||||||
pub high: CompressorParams,
|
|
||||||
#[nested(id_prefix = "all", group = "All")]
|
|
||||||
pub all: CompressorParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Params)]
|
|
||||||
struct CompressorParams {
|
|
||||||
#[id = "detect"]
|
|
||||||
pub detection: EnumParam<DetectionMode>,
|
|
||||||
#[id = "thresh"]
|
|
||||||
pub threshold_db: FloatParam,
|
|
||||||
#[id = "ratio"]
|
|
||||||
pub ratio: FloatParam,
|
|
||||||
#[id = "knee"]
|
|
||||||
pub knee_db: FloatParam,
|
|
||||||
#[id = "attack"]
|
|
||||||
pub attack_ms: FloatParam,
|
|
||||||
#[id = "release"]
|
|
||||||
pub release_ms: FloatParam,
|
|
||||||
#[id = "makeup"]
|
|
||||||
pub makeup_db: FloatParam,
|
|
||||||
#[id = "bypass"]
|
|
||||||
pub bypass: BoolParam,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Codename206 {
|
impl Default for Codename206 {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
@@ -108,127 +43,6 @@ impl Default for Codename206 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Codename206Params {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
editor_state: EguiState::from_size(760, 520),
|
|
||||||
|
|
||||||
crossover_low_hz: FloatParam::new(
|
|
||||||
"Crossover Lo/Mid",
|
|
||||||
200.0,
|
|
||||||
FloatRange::Skewed { min: 30.0, max: 1_000.0, factor: FloatRange::skew_factor(-1.0) },
|
|
||||||
)
|
|
||||||
.with_value_to_string(formatters::v2s_f32_hz_then_khz(0))
|
|
||||||
.with_string_to_value(formatters::s2v_f32_hz_then_khz()),
|
|
||||||
|
|
||||||
crossover_high_hz: FloatParam::new(
|
|
||||||
"Crossover Mid/Hi",
|
|
||||||
2_500.0,
|
|
||||||
FloatRange::Skewed { min: 500.0, max: 18_000.0, factor: FloatRange::skew_factor(-1.0) },
|
|
||||||
)
|
|
||||||
.with_value_to_string(formatters::v2s_f32_hz_then_khz(0))
|
|
||||||
.with_string_to_value(formatters::s2v_f32_hz_then_khz()),
|
|
||||||
|
|
||||||
look_ahead_ms: FloatParam::new(
|
|
||||||
"Look-ahead",
|
|
||||||
2.0,
|
|
||||||
FloatRange::Linear { min: 0.0, max: MAX_LOOKAHEAD_MS },
|
|
||||||
)
|
|
||||||
.with_unit(" ms")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(2)),
|
|
||||||
|
|
||||||
output_ceiling_db: FloatParam::new(
|
|
||||||
"Ceiling",
|
|
||||||
0.0,
|
|
||||||
FloatRange::Linear { min: -24.0, max: 0.0 },
|
|
||||||
)
|
|
||||||
.with_unit(" dB")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
|
||||||
|
|
||||||
limiter_release_ms: FloatParam::new(
|
|
||||||
"Limiter Release",
|
|
||||||
100.0,
|
|
||||||
FloatRange::Skewed { min: 1.0, max: 1_000.0, factor: FloatRange::skew_factor(-2.0) },
|
|
||||||
)
|
|
||||||
.with_unit(" ms")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
|
||||||
|
|
||||||
low: CompressorParams::default(),
|
|
||||||
mid: CompressorParams::default(),
|
|
||||||
high: CompressorParams::default(),
|
|
||||||
all: CompressorParams::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for CompressorParams {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
detection: EnumParam::new("Detection", DetectionMode::Peak),
|
|
||||||
|
|
||||||
threshold_db: FloatParam::new(
|
|
||||||
"Threshold",
|
|
||||||
-18.0,
|
|
||||||
FloatRange::Linear { min: -60.0, max: 0.0 },
|
|
||||||
)
|
|
||||||
.with_unit(" dB")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
|
||||||
|
|
||||||
ratio: FloatParam::new(
|
|
||||||
"Ratio",
|
|
||||||
2.0,
|
|
||||||
FloatRange::Skewed { min: 1.0, max: 20.0, factor: FloatRange::skew_factor(-1.0) },
|
|
||||||
)
|
|
||||||
.with_value_to_string(Arc::new(|v| format!("{v:.2} : 1")))
|
|
||||||
.with_string_to_value(Arc::new(|s| {
|
|
||||||
s.split(':').next().and_then(|x| x.trim().parse::<f32>().ok())
|
|
||||||
})),
|
|
||||||
|
|
||||||
knee_db: FloatParam::new("Knee", 6.0, FloatRange::Linear { min: 0.0, max: 24.0 })
|
|
||||||
.with_unit(" dB")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
|
||||||
|
|
||||||
attack_ms: FloatParam::new(
|
|
||||||
"Attack",
|
|
||||||
10.0,
|
|
||||||
FloatRange::Skewed { min: 0.0, max: 100.0, factor: FloatRange::skew_factor(-2.0) },
|
|
||||||
)
|
|
||||||
.with_unit(" ms")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(2)),
|
|
||||||
|
|
||||||
release_ms: FloatParam::new(
|
|
||||||
"Release",
|
|
||||||
100.0,
|
|
||||||
FloatRange::Skewed { min: 1.0, max: 1_000.0, factor: FloatRange::skew_factor(-2.0) },
|
|
||||||
)
|
|
||||||
.with_unit(" ms")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
|
||||||
|
|
||||||
makeup_db: FloatParam::new("Makeup", 0.0, FloatRange::Linear { min: -12.0, max: 24.0 })
|
|
||||||
.with_smoother(SmoothingStyle::Linear(20.0))
|
|
||||||
.with_unit(" dB")
|
|
||||||
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
|
||||||
|
|
||||||
bypass: BoolParam::new("Bypass", false),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build the per-block compressor settings for one channel's params (makeup filled per sample).
|
|
||||||
fn build_settings(p: &CompressorParams, lookahead: usize, sample_rate: f32) -> CompressorSettings {
|
|
||||||
CompressorSettings {
|
|
||||||
threshold_db: p.threshold_db.value(),
|
|
||||||
ratio: p.ratio.value(),
|
|
||||||
knee_db: p.knee_db.value(),
|
|
||||||
attack_coef: Compressor::time_to_coef(p.attack_ms.value(), sample_rate),
|
|
||||||
release_coef: Compressor::time_to_coef(p.release_ms.value(), sample_rate),
|
|
||||||
makeup_db: 0.0,
|
|
||||||
lookahead_samples: lookahead,
|
|
||||||
use_rms: p.detection.value() == DetectionMode::Rms,
|
|
||||||
bypass: p.bypass.value(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Codename206 {
|
impl Codename206 {
|
||||||
fn lookahead_samples(&self) -> usize {
|
fn lookahead_samples(&self) -> usize {
|
||||||
(self.params.look_ahead_ms.value() * 0.001 * self.sample_rate).round() as usize
|
(self.params.look_ahead_ms.value() * 0.001 * self.sample_rate).round() as usize
|
||||||
@@ -269,68 +83,7 @@ impl Plugin for Codename206 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
|
||||||
let params = self.params.clone();
|
editor::create(self.params.clone())
|
||||||
let egui_state = self.params.editor_state.clone();
|
|
||||||
create_egui_editor(
|
|
||||||
self.params.editor_state.clone(),
|
|
||||||
(),
|
|
||||||
|_, _| {},
|
|
||||||
move |egui_ctx, setter, _state| {
|
|
||||||
// One column of controls for a single compressor channel.
|
|
||||||
let band_col = |ui: &mut egui::Ui, title: &str, p: &CompressorParams| {
|
|
||||||
ui.strong(title);
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.detection, setter));
|
|
||||||
ui.label("Threshold");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.threshold_db, setter));
|
|
||||||
ui.label("Ratio");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.ratio, setter));
|
|
||||||
ui.label("Knee");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.knee_db, setter));
|
|
||||||
ui.label("Attack");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.attack_ms, setter));
|
|
||||||
ui.label("Release");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.release_ms, setter));
|
|
||||||
ui.label("Makeup");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.makeup_db, setter));
|
|
||||||
ui.add(widgets::ParamSlider::for_param(&p.bypass, setter));
|
|
||||||
};
|
|
||||||
|
|
||||||
// Resizable window; vertical scroll so every control stays reachable even when the
|
|
||||||
// window is small. (Placeholder layout — Stage 6 will replace it.)
|
|
||||||
ResizableWindow::new("editor")
|
|
||||||
.min_size(Vec2::new(480.0, 320.0))
|
|
||||||
.show(egui_ctx, egui_state.as_ref(), |ui| {
|
|
||||||
egui::ScrollArea::vertical().show(ui, |ui| {
|
|
||||||
ui.heading(Self::NAME);
|
|
||||||
// Global controls stacked vertically so they never overflow sideways.
|
|
||||||
egui::Grid::new("globals").num_columns(2).show(ui, |ui| {
|
|
||||||
ui.label("Xover Lo/Mid");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.crossover_low_hz, setter));
|
|
||||||
ui.end_row();
|
|
||||||
ui.label("Xover Mid/Hi");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.crossover_high_hz, setter));
|
|
||||||
ui.end_row();
|
|
||||||
ui.label("Look-ahead");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.look_ahead_ms, setter));
|
|
||||||
ui.end_row();
|
|
||||||
ui.label("Ceiling");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.output_ceiling_db, setter));
|
|
||||||
ui.end_row();
|
|
||||||
ui.label("Lim Release");
|
|
||||||
ui.add(widgets::ParamSlider::for_param(¶ms.limiter_release_ms, setter));
|
|
||||||
ui.end_row();
|
|
||||||
});
|
|
||||||
ui.separator();
|
|
||||||
ui.columns(4, |cols| {
|
|
||||||
band_col(&mut cols[0], "LOW", ¶ms.low);
|
|
||||||
band_col(&mut cols[1], "MID", ¶ms.mid);
|
|
||||||
band_col(&mut cols[2], "HIGH", ¶ms.high);
|
|
||||||
band_col(&mut cols[3], "ALL", ¶ms.all);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn initialize(
|
fn initialize(
|
||||||
|
|||||||
+199
@@ -0,0 +1,199 @@
|
|||||||
|
//! Plugin parameters and their layout.
|
||||||
|
//!
|
||||||
|
//! Holds the global controls plus four `CompressorParams` blocks (low/mid/high + the 'All'
|
||||||
|
//! aggregate channel). `build_settings` translates a channel's params into the per-block
|
||||||
|
//! [`CompressorSettings`] the DSP consumes.
|
||||||
|
|
||||||
|
use nih_plug::prelude::*;
|
||||||
|
use nih_plug_egui::EguiState;
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
use crate::dsp::compressor::{Compressor, CompressorSettings, MAX_LOOKAHEAD_MS};
|
||||||
|
|
||||||
|
/// Level-detection mode for a compressor's detector.
|
||||||
|
#[derive(Enum, PartialEq, Clone, Copy)]
|
||||||
|
pub enum DetectionMode {
|
||||||
|
#[id = "peak"]
|
||||||
|
#[name = "Peak"]
|
||||||
|
Peak,
|
||||||
|
#[id = "rms"]
|
||||||
|
#[name = "RMS"]
|
||||||
|
Rms,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params)]
|
||||||
|
pub struct Codename206Params {
|
||||||
|
#[persist = "editor-state"]
|
||||||
|
pub editor_state: Arc<EguiState>,
|
||||||
|
|
||||||
|
/// Low/Mid crossover frequency.
|
||||||
|
#[id = "xover_lo"]
|
||||||
|
pub crossover_low_hz: FloatParam,
|
||||||
|
/// Mid/High crossover frequency.
|
||||||
|
#[id = "xover_hi"]
|
||||||
|
pub crossover_high_hz: FloatParam,
|
||||||
|
/// Global look-ahead time (constant reported latency — safe to adjust during playback).
|
||||||
|
#[id = "lookahead"]
|
||||||
|
pub look_ahead_ms: FloatParam,
|
||||||
|
|
||||||
|
/// Output brickwall ceiling (the limiter never lets output exceed this).
|
||||||
|
#[id = "ceiling"]
|
||||||
|
pub output_ceiling_db: FloatParam,
|
||||||
|
/// Output limiter release time.
|
||||||
|
#[id = "lim_rel"]
|
||||||
|
pub limiter_release_ms: FloatParam,
|
||||||
|
|
||||||
|
#[nested(id_prefix = "low", group = "Low")]
|
||||||
|
pub low: CompressorParams,
|
||||||
|
#[nested(id_prefix = "mid", group = "Mid")]
|
||||||
|
pub mid: CompressorParams,
|
||||||
|
#[nested(id_prefix = "high", group = "High")]
|
||||||
|
pub high: CompressorParams,
|
||||||
|
#[nested(id_prefix = "all", group = "All")]
|
||||||
|
pub all: CompressorParams,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Params)]
|
||||||
|
pub struct CompressorParams {
|
||||||
|
#[id = "detect"]
|
||||||
|
pub detection: EnumParam<DetectionMode>,
|
||||||
|
#[id = "thresh"]
|
||||||
|
pub threshold_db: FloatParam,
|
||||||
|
#[id = "ratio"]
|
||||||
|
pub ratio: FloatParam,
|
||||||
|
#[id = "knee"]
|
||||||
|
pub knee_db: FloatParam,
|
||||||
|
#[id = "attack"]
|
||||||
|
pub attack_ms: FloatParam,
|
||||||
|
#[id = "release"]
|
||||||
|
pub release_ms: FloatParam,
|
||||||
|
#[id = "makeup"]
|
||||||
|
pub makeup_db: FloatParam,
|
||||||
|
#[id = "bypass"]
|
||||||
|
pub bypass: BoolParam,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Codename206Params {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
editor_state: EguiState::from_size(760, 520),
|
||||||
|
|
||||||
|
crossover_low_hz: FloatParam::new(
|
||||||
|
"Crossover Lo/Mid",
|
||||||
|
200.0,
|
||||||
|
FloatRange::Skewed { min: 30.0, max: 1_000.0, factor: FloatRange::skew_factor(-1.0) },
|
||||||
|
)
|
||||||
|
.with_value_to_string(formatters::v2s_f32_hz_then_khz(0))
|
||||||
|
.with_string_to_value(formatters::s2v_f32_hz_then_khz()),
|
||||||
|
|
||||||
|
crossover_high_hz: FloatParam::new(
|
||||||
|
"Crossover Mid/Hi",
|
||||||
|
2_500.0,
|
||||||
|
FloatRange::Skewed { min: 500.0, max: 18_000.0, factor: FloatRange::skew_factor(-1.0) },
|
||||||
|
)
|
||||||
|
.with_value_to_string(formatters::v2s_f32_hz_then_khz(0))
|
||||||
|
.with_string_to_value(formatters::s2v_f32_hz_then_khz()),
|
||||||
|
|
||||||
|
look_ahead_ms: FloatParam::new(
|
||||||
|
"Look-ahead",
|
||||||
|
2.0,
|
||||||
|
FloatRange::Linear { min: 0.0, max: MAX_LOOKAHEAD_MS },
|
||||||
|
)
|
||||||
|
.with_unit(" ms")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(2)),
|
||||||
|
|
||||||
|
output_ceiling_db: FloatParam::new(
|
||||||
|
"Ceiling",
|
||||||
|
0.0,
|
||||||
|
FloatRange::Linear { min: -24.0, max: 0.0 },
|
||||||
|
)
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
||||||
|
|
||||||
|
limiter_release_ms: FloatParam::new(
|
||||||
|
"Limiter Release",
|
||||||
|
100.0,
|
||||||
|
FloatRange::Skewed { min: 1.0, max: 1_000.0, factor: FloatRange::skew_factor(-2.0) },
|
||||||
|
)
|
||||||
|
.with_unit(" ms")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
||||||
|
|
||||||
|
low: CompressorParams::default(),
|
||||||
|
mid: CompressorParams::default(),
|
||||||
|
high: CompressorParams::default(),
|
||||||
|
all: CompressorParams::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for CompressorParams {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
detection: EnumParam::new("Detection", DetectionMode::Peak),
|
||||||
|
|
||||||
|
threshold_db: FloatParam::new(
|
||||||
|
"Threshold",
|
||||||
|
-18.0,
|
||||||
|
FloatRange::Linear { min: -60.0, max: 0.0 },
|
||||||
|
)
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
||||||
|
|
||||||
|
ratio: FloatParam::new(
|
||||||
|
"Ratio",
|
||||||
|
2.0,
|
||||||
|
FloatRange::Skewed { min: 1.0, max: 20.0, factor: FloatRange::skew_factor(-1.0) },
|
||||||
|
)
|
||||||
|
.with_value_to_string(Arc::new(|v| format!("{v:.2} : 1")))
|
||||||
|
.with_string_to_value(Arc::new(|s| {
|
||||||
|
s.split(':').next().and_then(|x| x.trim().parse::<f32>().ok())
|
||||||
|
})),
|
||||||
|
|
||||||
|
knee_db: FloatParam::new("Knee", 6.0, FloatRange::Linear { min: 0.0, max: 24.0 })
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
||||||
|
|
||||||
|
attack_ms: FloatParam::new(
|
||||||
|
"Attack",
|
||||||
|
10.0,
|
||||||
|
FloatRange::Skewed { min: 0.0, max: 100.0, factor: FloatRange::skew_factor(-2.0) },
|
||||||
|
)
|
||||||
|
.with_unit(" ms")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(2)),
|
||||||
|
|
||||||
|
release_ms: FloatParam::new(
|
||||||
|
"Release",
|
||||||
|
100.0,
|
||||||
|
FloatRange::Skewed { min: 1.0, max: 1_000.0, factor: FloatRange::skew_factor(-2.0) },
|
||||||
|
)
|
||||||
|
.with_unit(" ms")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
||||||
|
|
||||||
|
makeup_db: FloatParam::new("Makeup", 0.0, FloatRange::Linear { min: -12.0, max: 24.0 })
|
||||||
|
.with_smoother(SmoothingStyle::Linear(20.0))
|
||||||
|
.with_unit(" dB")
|
||||||
|
.with_value_to_string(formatters::v2s_f32_rounded(1)),
|
||||||
|
|
||||||
|
bypass: BoolParam::new("Bypass", false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build the per-block compressor settings for one channel's params (makeup filled per sample).
|
||||||
|
pub fn build_settings(
|
||||||
|
p: &CompressorParams,
|
||||||
|
lookahead: usize,
|
||||||
|
sample_rate: f32,
|
||||||
|
) -> CompressorSettings {
|
||||||
|
CompressorSettings {
|
||||||
|
threshold_db: p.threshold_db.value(),
|
||||||
|
ratio: p.ratio.value(),
|
||||||
|
knee_db: p.knee_db.value(),
|
||||||
|
attack_coef: Compressor::time_to_coef(p.attack_ms.value(), sample_rate),
|
||||||
|
release_coef: Compressor::time_to_coef(p.release_ms.value(), sample_rate),
|
||||||
|
makeup_db: 0.0,
|
||||||
|
lookahead_samples: lookahead,
|
||||||
|
use_rms: p.detection.value() == DetectionMode::Rms,
|
||||||
|
bypass: p.bypass.value(),
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user