use nih_plug::prelude::*; use nih_plug_egui::EguiState; use resonance_core::{Focus, Settings}; use std::sync::Arc; #[derive(Params)] pub struct FocusParams { #[id = "enabled"] pub enabled: FloatParam, #[id = "frequency"] pub frequency: FloatParam, #[id = "width"] pub width: FloatParam, #[id = "sensitivity"] pub sensitivity: FloatParam, } fn linear(name: &str, v: f32, min: f32, max: f32, unit: &'static str) -> FloatParam { FloatParam::new(name, v, FloatRange::Linear { min, max }).with_unit(unit) } fn log(name: &str, v: f32, min: f32, max: f32, unit: &'static str) -> FloatParam { FloatParam::new( name, v, FloatRange::Skewed { min, max, factor: FloatRange::skew_factor(-2.), }, ) .with_unit(unit) } impl Default for FocusParams { fn default() -> Self { Self { enabled: linear("Enabled", 0., 0., 1., "").with_step_size(1.), frequency: log("Frequency", 2000., 20., 20000., " Hz"), width: linear("Width", 0.5, 0.05, 2., " oct"), sensitivity: linear("Sensitivity", 0., -18., 18., " dB"), } } } #[derive(Params)] pub struct ResonanceParams { #[persist = "editor"] pub editor: Arc, #[id = "amount"] pub amount: FloatParam, #[id = "sensitivity"] pub sensitivity: FloatParam, #[id = "max-cut"] pub max_cut: FloatParam, #[id = "neighborhood"] pub neighborhood: FloatParam, #[id = "cut-width"] pub cut_width: FloatParam, #[id = "knee"] pub knee: FloatParam, #[id = "attack"] pub attack: FloatParam, #[id = "release"] pub release: FloatParam, #[id = "transient-guard"] pub transient_guard: FloatParam, #[id = "low"] pub low: FloatParam, #[id = "high"] pub high: FloatParam, #[id = "mix"] pub mix: FloatParam, #[id = "output"] pub output: FloatParam, #[id = "bypass"] pub bypass: BoolParam, #[nested(array, group = "Focus")] pub focus: [FocusParams; 4], } impl Default for ResonanceParams { fn default() -> Self { Self { editor: EguiState::from_size(1180, 760), amount: linear("Amount", 35., 0., 100., " %"), sensitivity: linear("Sensitivity", 6., 0., 24., " dB"), max_cut: linear("Max cut", 3., 0., 18., " dB"), neighborhood: linear("Neighborhood", 1., 0.25, 2., " oct"), cut_width: linear("Cut width", 0.08, 0.03, 0.5, " oct"), knee: linear("Knee", 3., 0., 12., " dB"), attack: log("Attack", 15., 1., 200., " ms"), release: log("Release", 120., 10., 1000., " ms"), transient_guard: linear("Transient guard", 50., 0., 100., " %"), low: log("Low frequency", 120., 20., 19999., " Hz"), high: log("High frequency", 12000., 21., 20000., " Hz"), mix: linear("Mix", 100., 0., 100., " %"), output: linear("Output", 0., -12., 12., " dB"), bypass: BoolParam::new("Bypass", false).make_bypass(), focus: std::array::from_fn(|_| FocusParams::default()), } } } impl ResonanceParams { pub fn floats(&self) -> [&FloatParam; 29] { [ &self.amount, &self.sensitivity, &self.max_cut, &self.neighborhood, &self.cut_width, &self.knee, &self.attack, &self.release, &self.transient_guard, &self.low, &self.high, &self.mix, &self.output, &self.focus[0].enabled, &self.focus[0].frequency, &self.focus[0].width, &self.focus[0].sensitivity, &self.focus[1].enabled, &self.focus[1].frequency, &self.focus[1].width, &self.focus[1].sensitivity, &self.focus[2].enabled, &self.focus[2].frequency, &self.focus[2].width, &self.focus[2].sensitivity, &self.focus[3].enabled, &self.focus[3].frequency, &self.focus[3].width, &self.focus[3].sensitivity, ] } pub fn settings(&self) -> Settings { let mut s = Settings { amount: self.amount.value(), sensitivity: self.sensitivity.value(), max_cut: self.max_cut.value(), neighborhood: self.neighborhood.value(), cut_width: self.cut_width.value(), knee: self.knee.value(), attack: self.attack.value(), release: self.release.value(), transient_guard: self.transient_guard.value(), low: self.low.value(), high: self.high.value(), mix: self.mix.value(), output: self.output.value(), bypass: self.bypass.value(), focus: std::array::from_fn(|i| Focus { enabled: self.focus[i].enabled.value() >= 0.5, frequency: self.focus[i].frequency.value(), width: self.focus[i].width.value(), sensitivity: self.focus[i].sensitivity.value(), }), }; // Independent automation of bounds may cross them; preserve a valid effective range. if s.low >= s.high { s.low = (s.high - 1.).max(20.); } s } } pub fn values(s: &Settings) -> [f32; 29] { let mut v = [0.; 29]; v[..13].copy_from_slice(&[ s.amount, s.sensitivity, s.max_cut, s.neighborhood, s.cut_width, s.knee, s.attack, s.release, s.transient_guard, s.low, s.high, s.mix, s.output, ]); for i in 0..4 { v[13 + i * 4..17 + i * 4].copy_from_slice(&[ f32::from(s.focus[i].enabled), s.focus[i].frequency, s.focus[i].width, s.focus[i].sensitivity, ]); } v }