diff --git a/plugins/examples/gain-gui/src/lib.rs b/plugins/examples/gain-gui/src/lib.rs index 992e7686..a78b2b94 100644 --- a/plugins/examples/gain-gui/src/lib.rs +++ b/plugins/examples/gain-gui/src/lib.rs @@ -3,8 +3,8 @@ extern crate nih_plug; use atomic_float::AtomicF32; use nih_plug::{ - formatters, util, Buffer, BufferConfig, BusConfig, Editor, IntParam, Plugin, ProcessContext, - ProcessStatus, Vst3Plugin, + util, Buffer, BufferConfig, BusConfig, Editor, IntParam, Plugin, ProcessContext, ProcessStatus, + Vst3Plugin, }; use nih_plug::{FloatParam, Params, Range, SmoothingStyle}; use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState}; diff --git a/src/param.rs b/src/param.rs index 6c2e5c41..37157716 100644 --- a/src/param.rs +++ b/src/param.rs @@ -34,18 +34,17 @@ pub trait Param: Display { /// Get the unnormalized value for this parameter. fn plain_value(&self) -> Self::Plain; - /// Set this parameter based on a plain, unnormalized value. + /// Set this parameter based on a plain, unnormalized value. This does **not** snap to step + /// sizes for continuous parameters (i.e. [FloatParam]). /// /// This does **not** update the smoother. - /// - /// TDOO: Decide on whether this should update the smoother or not. That wouldn't be compatible - /// with sample accurate automation when we add that. fn set_plain_value(&mut self, plain: Self::Plain); /// Get the normalized `[0, 1]` value for this parameter. fn normalized_value(&self) -> f32; - /// Set this parameter based on a normalized value. + /// Set this parameter based on a normalized value. This **does** snap to step sizes for + /// continuous parameters (i.e. [FloatParam]). /// /// This does **not** update the smoother. fn set_normalized_value(&mut self, normalized: f32); @@ -63,7 +62,7 @@ pub trait Param: Display { fn preview_normalized(&self, plain: Self::Plain) -> f32; /// Get the plain, unnormalized value for a normalized value, as a float. Used as part of the - /// wrappers. + /// wrappers. This **does** snap to step sizes for continuous parameters (i.e. [FloatParam]). fn preview_plain(&self, normalized: f32) -> Self::Plain; /// Internal implementation detail for implementing [internals::Params]. This should not be used @@ -251,7 +250,13 @@ macro_rules! impl_plainparam { } fn preview_plain(&self, normalized: f32) -> Self::Plain { - self.range.unnormalize(normalized) + let value = self.range.unnormalize(normalized); + match &self.step_size { + // Step size snapping is not defined for [IntParam], so this cast is here just + // so we can keep everything in this macro + Some(step_size) => self.range.snap_to_step(value, *step_size as Self::Plain), + None => value, + } } fn as_ptr(&self) -> internals::ParamPtr { diff --git a/src/param/range.rs b/src/param/range.rs index 8a7eefd2..97051942 100644 --- a/src/param/range.rs +++ b/src/param/range.rs @@ -31,6 +31,9 @@ impl Range<()> { /// A normalizable range for type `T`, where `self` is expected to be a type `R`. Higher kinded /// types would have made this trait definition a lot clearer. +/// +/// Floating point rounding to a step size is always done in the conversion from normalized to +/// plain, inside [super::PlainParam::preview_plain]. pub(crate) trait NormalizebleRange { /// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the /// normalized value exceeds `[0, 1]`. @@ -39,6 +42,9 @@ pub(crate) trait NormalizebleRange { /// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the plain, unnormalized value /// would exceed that range. fn unnormalize(&self, normalized: f32) -> T; + + /// Snap a vlue to a step size, clamping to the minimum and maximum value of the range. + fn snap_to_step(&self, value: T, step_size: T) -> T; } impl Default for Range { @@ -116,6 +122,16 @@ impl NormalizebleRange for Range { } } } + + fn snap_to_step(&self, value: f32, step_size: f32) -> f32 { + let (min, max) = match &self { + Range::Linear { min, max } => (min, max), + Range::Skewed { min, max, .. } => (min, max), + Range::SymmetricalSkewed { min, max, .. } => (min, max), + }; + + ((value / step_size).round() * step_size).clamp(*min, *max) + } } impl NormalizebleRange for Range { @@ -175,6 +191,12 @@ impl NormalizebleRange for Range { } } } + + fn snap_to_step(&self, value: i32, _step_size: i32) -> i32 { + // Integers are already discrete, and we don't allow setting step sizes on them through the + // builder interface + value + } } #[cfg(test)] @@ -226,6 +248,20 @@ mod tests { } } + #[test] + fn step_size() { + // These are weird step sizes, but if it works here then it will work for anything + let range = make_linear_float_range(); + assert_eq!(range.snap_to_step(13.0, 4.73), 14.49); + } + + #[test] + fn step_size_clamping() { + let range = make_linear_float_range(); + assert_eq!(range.snap_to_step(10.0, 4.73), 10.0); + assert_eq!(range.snap_to_step(20.0, 6.73), 20.0); + } + mod linear { use super::super::*; use super::*;