Files
codename-206/src/lib.rs
T

227 lines
7.7 KiB
Rust
Raw Normal View History

use nih_plug::prelude::*;
use std::sync::Arc;
mod dsp;
mod editor;
mod params;
use dsp::compressor::{Compressor, MAX_LOOKAHEAD_MS};
use dsp::crossover::Crossover;
use dsp::limiter::Limiter;
use params::{build_settings, Codename206Params};
/// Band indices into the compressor array: low, mid, high, then the 'All' aggregate channel.
const LOW: usize = 0;
const MID: usize = 1;
const HIGH: usize = 2;
const ALL: usize = 3;
/// 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 →
/// 'All' compressor → output. Bypassing low+mid+high collapses it to a plain full-band comp
/// driven by the 'All' channel (the crossover sums flat).
struct Codename206 {
params: Arc<Codename206Params>,
sample_rate: f32,
crossover: Crossover,
/// Compressors indexed by [`LOW`], [`MID`], [`HIGH`], [`ALL`].
comps: [Compressor; 4],
/// Output brickwall limiter (final stage).
limiter: Limiter,
}
impl Default for Codename206 {
fn default() -> Self {
Self {
params: Arc::new(Codename206Params::default()),
sample_rate: 48_000.0,
crossover: Crossover::new(),
comps: [Compressor::new(), Compressor::new(), Compressor::new(), Compressor::new()],
limiter: Limiter::new(),
}
}
}
impl Codename206 {
fn lookahead_samples(&self) -> usize {
(self.params.look_ahead_ms.value() * 0.001 * self.sample_rate).round() as usize
}
}
impl Plugin for Codename206 {
2026-06-14 21:34:06 +09:00
const NAME: &'static str = "206 prototype";
const VENDOR: &'static str = "Novoyuuparosk";
const URL: &'static str = env!("CARGO_PKG_HOMEPAGE");
2026-06-14 21:34:06 +09:00
const EMAIL: &'static str = "mikkeli@novoyuuparosk.org";
const VERSION: &'static str = env!("CARGO_PKG_VERSION");
const AUDIO_IO_LAYOUTS: &'static [AudioIOLayout] = &[
AudioIOLayout {
main_input_channels: NonZeroU32::new(2),
main_output_channels: NonZeroU32::new(2),
..AudioIOLayout::const_default()
},
AudioIOLayout {
main_input_channels: NonZeroU32::new(1),
main_output_channels: NonZeroU32::new(1),
..AudioIOLayout::const_default()
},
];
const MIDI_INPUT: MidiConfig = MidiConfig::None;
const MIDI_OUTPUT: MidiConfig = MidiConfig::None;
const SAMPLE_ACCURATE_AUTOMATION: bool = true;
type SysExMessage = ();
type BackgroundTask = ();
fn params(&self) -> Arc<dyn Params> {
self.params.clone()
}
fn editor(&mut self, _async_executor: AsyncExecutor<Self>) -> Option<Box<dyn Editor>> {
editor::create(self.params.clone())
}
fn initialize(
&mut self,
audio_io_layout: &AudioIOLayout,
buffer_config: &BufferConfig,
context: &mut impl InitContext<Self>,
) -> bool {
self.sample_rate = buffer_config.sample_rate;
let channels = audio_io_layout
.main_output_channels
.map(NonZeroU32::get)
.unwrap_or(2) as usize;
for comp in &mut self.comps {
comp.prepare(self.sample_rate, channels, MAX_LOOKAHEAD_MS);
}
self.crossover.prepare(channels);
self.crossover.update(
self.sample_rate,
self.params.crossover_low_hz.value(),
self.params.crossover_high_hz.value(),
);
self.limiter.prepare(self.sample_rate, channels);
// Three series stages each with a fixed look-ahead delay: the bands, the 'All' channel,
// and the output limiter. Reported once as a constant; see the compressor look-ahead note.
let total_latency =
self.comps[LOW].latency() + self.comps[ALL].latency() + self.limiter.latency();
context.set_latency_samples(total_latency);
true
}
fn reset(&mut self) {
self.crossover.reset();
for comp in &mut self.comps {
comp.reset();
}
self.limiter.reset();
}
fn process(
&mut self,
buffer: &mut Buffer,
_aux: &mut AuxiliaryBuffers,
_context: &mut impl ProcessContext<Self>,
) -> ProcessStatus {
let lookahead = self.lookahead_samples();
// Crossover coefficients track the frequency params (recomputed per block — cheap).
self.crossover.update(
self.sample_rate,
self.params.crossover_low_hz.value(),
self.params.crossover_high_hz.value(),
);
// Block-rate settings for the three bands + the 'All' channel.
let band_params = [&self.params.low, &self.params.mid, &self.params.high];
let mut band_set = [
build_settings(&self.params.low, lookahead, self.sample_rate),
build_settings(&self.params.mid, lookahead, self.sample_rate),
build_settings(&self.params.high, lookahead, self.sample_rate),
];
let mut all_set = build_settings(&self.params.all, lookahead, self.sample_rate);
// Output limiter settings (block-rate).
let ceiling = util::db_to_gain(self.params.output_ceiling_db.value());
let limiter_release =
Compressor::time_to_coef(self.params.limiter_release_ms.value(), self.sample_rate);
let mut in_frame = [0.0f32; 2];
let mut band_in = [[0.0f32; 2]; 3];
let mut band_out = [[0.0f32; 2]; 3];
let mut summed = [0.0f32; 2];
let mut out_frame = [0.0f32; 2];
let mut lim_frame = [0.0f32; 2];
for mut frame in buffer.iter_samples() {
let n = frame.len().min(2);
for ch in 0..n {
in_frame[ch] = *frame.get_mut(ch).unwrap();
}
// Split each channel into low/mid/high.
for ch in 0..n {
let [lo, mid, hi] = self.crossover.split(ch, in_frame[ch]);
band_in[LOW][ch] = lo;
band_in[MID][ch] = mid;
band_in[HIGH][ch] = hi;
}
// Compress each band (per-sample smoothed makeup), then sum.
summed[..n].fill(0.0);
for b in 0..3 {
band_set[b].makeup_db = band_params[b].makeup_db.smoothed.next();
self.comps[b].process(&band_in[b][..n], &mut band_out[b][..n], &band_set[b]);
for ch in 0..n {
summed[ch] += band_out[b][ch];
}
}
// 'All' aggregate channel over the summed bands.
all_set.makeup_db = self.params.all.makeup_db.smoothed.next();
self.comps[ALL].process(&summed[..n], &mut out_frame[..n], &all_set);
// Output brickwall limiter.
self.limiter.process(&out_frame[..n], &mut lim_frame[..n], ceiling, limiter_release);
for ch in 0..n {
*frame.get_mut(ch).unwrap() = lim_frame[ch];
}
}
ProcessStatus::Normal
}
}
impl ClapPlugin for Codename206 {
const CLAP_ID: &'static str = "com.mikkeli.codename-206";
const CLAP_DESCRIPTION: Option<&'static str> =
Some("Multiband compressor/limiter (stage 3: 3-band + 'All' channel)");
const CLAP_MANUAL_URL: Option<&'static str> = Some(Self::URL);
const CLAP_SUPPORT_URL: Option<&'static str> = None;
const CLAP_FEATURES: &'static [ClapFeature] = &[
ClapFeature::AudioEffect,
ClapFeature::Stereo,
ClapFeature::Mono,
ClapFeature::Compressor,
ClapFeature::Limiter,
];
}
impl Vst3Plugin for Codename206 {
const VST3_CLASS_ID: [u8; 16] = *b"Codename206Maxi!";
const VST3_SUBCATEGORIES: &'static [Vst3SubCategory] =
&[Vst3SubCategory::Fx, Vst3SubCategory::Dynamics];
}
nih_export_clap!(Codename206);
nih_export_vst3!(Codename206);