use nih_plug::prelude::*; use nih_plug_egui::EguiState; use patina_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 PatinaParams { #[persist = "editor"] pub editor: Arc, #[id = "depth"] pub depth: FloatParam, #[id = "sharpness"] pub sharpness: FloatParam, #[id = "selectivity"] pub selectivity: 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 = "tone-guard"] pub tone_guard: FloatParam, #[id = "bypass"] pub bypass: BoolParam, #[nested(array, group = "Focus")] pub focus: [FocusParams; 4], } impl Default for PatinaParams { fn default() -> Self { let d = Settings::default(); Self { editor: EguiState::from_size(1180, 760), depth: linear("Depth", d.depth, 0., 20., " dB"), sharpness: linear("Sharpness", d.sharpness, 0., 100., " %"), selectivity: linear("Selectivity", d.selectivity, 0., 100., " %"), attack: log("Attack", d.attack, 1., 200., " ms"), release: log("Release", d.release, 10., 1000., " ms"), transient_guard: linear("Transient guard", d.transient_guard, 0., 100., " %"), low: log("Low frequency", d.low, 20., 19999., " Hz"), high: log("High frequency", d.high, 21., 20000., " Hz"), mix: linear("Mix", 100., 0., 100., " %"), output: linear("Output", 0., -12., 12., " dB"), tone_guard: linear("Tone guard", d.tone_guard, 0., 100., " %"), bypass: BoolParam::new("Bypass", false).make_bypass(), focus: std::array::from_fn(|_| FocusParams::default()), } } } impl PatinaParams { pub fn floats(&self) -> [&FloatParam; COUNT] { [ &self.depth, &self.sharpness, &self.selectivity, &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, &self.tone_guard, ] } pub fn settings(&self) -> Settings { let mut s = Settings { depth: self.depth.value(), sharpness: self.sharpness.value(), selectivity: self.selectivity.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(), }), tone_guard: self.tone_guard.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 } } /// Host float parameters: ten main controls, four per focus point, then Tone guard. pub const COUNT: usize = 27; pub fn values(s: &Settings) -> [f32; COUNT] { let mut v = [0.; COUNT]; v[..10].copy_from_slice(&[ s.depth, s.sharpness, s.selectivity, s.attack, s.release, s.transient_guard, s.low, s.high, s.mix, s.output, ]); for i in 0..4 { v[10 + i * 4..14 + i * 4].copy_from_slice(&[ f32::from(s.focus[i].enabled), s.focus[i].frequency, s.focus[i].width, s.focus[i].sensitivity, ]); } v[26] = s.tone_guard; v }