use nih_plug::prelude::*; use nih_plug_egui::EguiState; use std::sync::{Arc, RwLock}; use weld_core::{Fit, Listen, Mode, Settings, MAX_SECTIONS}; #[derive(Enum, Debug, PartialEq, Eq, Clone, Copy)] pub enum ModeParam { #[id = "follow"] Follow, #[id = "learn"] Learn, #[id = "hold"] Hold, } #[derive(Enum, Debug, PartialEq, Eq, Clone, Copy)] pub enum ListenParam { #[id = "layer"] Layer, #[id = "sum"] Sum, #[id = "sum-dry"] #[name = "Sum dry"] SumDry, } /// Continuous controls, in the order of the gesture slots. const FLOATS: usize = 5; #[derive(Params)] pub struct WeldParams { #[persist = "editor"] pub editor: Arc, /// The applied alignment, so a project reopens with it. #[persist = "weld-fit"] pub fit: Arc>, #[id = "mode"] pub mode: EnumParam, #[id = "response"] pub response: FloatParam, #[id = "low"] pub low: FloatParam, #[id = "high"] pub high: FloatParam, #[id = "sections"] pub sections: IntParam, #[id = "gate"] pub gate: FloatParam, #[id = "listen"] pub listen: EnumParam, #[id = "output"] pub output: FloatParam, #[id = "bypass"] pub bypass: BoolParam, } fn hz(value: f32) -> String { if value >= 1000. { format!("{:.2} kHz", value / 1000.) } else { format!("{value:.0} Hz") } } impl Default for WeldParams { fn default() -> Self { let d = Settings::default(); Self { editor: EguiState::from_size(weld_ui::WIDTH, weld_ui::HEIGHT), fit: Arc::new(RwLock::new(Fit::NONE)), mode: EnumParam::new("Mode", ModeParam::Follow), response: FloatParam::new( "Response", d.response, FloatRange::Skewed { min: 0.3, max: 5., factor: FloatRange::skew_factor(-1.), }, ) .with_unit(" s") .with_value_to_string(Arc::new(|v| format!("{v:.2}"))), low: FloatParam::new( "Low", d.low, FloatRange::Skewed { min: 30., max: 400., factor: FloatRange::skew_factor(-1.5), }, ) .with_value_to_string(Arc::new(hz)), high: FloatParam::new( "High", d.high, FloatRange::Skewed { min: 1000., max: 12000., factor: FloatRange::skew_factor(-1.), }, ) .with_value_to_string(Arc::new(hz)), sections: IntParam::new( "Sections", d.sections as i32, IntRange::Linear { min: 1, max: MAX_SECTIONS as i32, }, ), gate: FloatParam::new( "Gate", d.gate, FloatRange::Linear { min: -70., max: -30., }, ) .with_unit(" dB"), listen: EnumParam::new("Listen", ListenParam::Layer), output: FloatParam::new( "Output", d.output, FloatRange::Linear { min: -12., max: 12., }, ) .with_unit(" dB"), bypass: BoolParam::new("Bypass", false).make_bypass(), } } } fn mode_param(mode: Mode) -> ModeParam { match mode { Mode::Follow => ModeParam::Follow, Mode::Learn => ModeParam::Learn, Mode::Hold => ModeParam::Hold, } } fn listen_param(listen: Listen) -> ListenParam { match listen { Listen::Layer => ListenParam::Layer, Listen::Sum => ListenParam::Sum, Listen::SumDry => ListenParam::SumDry, } } impl WeldParams { pub fn settings(&self) -> Settings { Settings { mode: match self.mode.value() { ModeParam::Follow => Mode::Follow, ModeParam::Learn => Mode::Learn, ModeParam::Hold => Mode::Hold, }, response: self.response.value(), low: self.low.value(), high: self.high.value(), sections: self.sections.value().clamp(1, MAX_SECTIONS as i32) as usize, gate: self.gate.value(), listen: match self.listen.value() { ListenParam::Layer => Listen::Layer, ListenParam::Sum => Listen::Sum, ListenParam::SumDry => Listen::SumDry, }, output: self.output.value(), bypass: self.bypass.value(), } } pub fn stored_fit(&self) -> Fit { self.fit.read().map(|f| *f).unwrap_or(Fit::NONE) } fn floats(&self) -> [&FloatParam; FLOATS] { [ &self.response, &self.low, &self.high, &self.gate, &self.output, ] } /// Pushes every changed editor value to the host. Continuous controls hold one gesture /// while the pointer is down; switches are set in a single gesture. pub fn apply( &self, setter: &ParamSetter, old: &Settings, new: &Settings, dragging: bool, open: &mut [bool; FLOATS], ) { let values = [ (old.response, new.response), (old.low, new.low), (old.high, new.high), (old.gate, new.gate), (old.output, new.output), ]; for (i, (p, (a, b))) in self.floats().into_iter().zip(values).enumerate() { if a != b { if !open[i] { open[i] = true; setter.begin_set_parameter(p); } setter.set_parameter(p, b); } if !dragging && open[i] { setter.end_set_parameter(p); open[i] = false; } } fn switch(setter: &ParamSetter, p: &P, v: P::Plain) { setter.begin_set_parameter(p); setter.set_parameter(p, v); setter.end_set_parameter(p); } if new.bypass != old.bypass { switch(setter, &self.bypass, new.bypass); } if new.mode != old.mode { switch(setter, &self.mode, mode_param(new.mode)); } if new.listen != old.listen { switch(setter, &self.listen, listen_param(new.listen)); } if new.sections != old.sections { switch(setter, &self.sections, new.sections as i32); } } } pub const GESTURES: usize = FLOATS;