diff --git a/plugins/diopser/src/lib.rs b/plugins/diopser/src/lib.rs index 15289108..d3b2c88f 100644 --- a/plugins/diopser/src/lib.rs +++ b/plugins/diopser/src/lib.rs @@ -23,7 +23,7 @@ use nih_plug::{ formatters, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext, ProcessStatus, Vst3Plugin, }; -use nih_plug::{BoolParam, FloatParam, IntParam, Params, Range, SmoothingStyle}; +use nih_plug::{BoolParam, FloatParam, FloatRange, IntParam, IntRange, Params, SmoothingStyle}; use nih_plug::{Enum, EnumParam}; use std::pin::Pin; use std::sync::atomic::{AtomicBool, Ordering}; @@ -140,7 +140,7 @@ impl DiopserParams { filter_stages: IntParam::new( "Filter Stages", 0, - Range::Linear { + IntRange::Linear { min: 0, max: MAX_NUM_FILTERS as i32, }, @@ -155,10 +155,10 @@ impl DiopserParams { filter_frequency: FloatParam::new( "Filter Frequency", 200.0, - Range::Skewed { + FloatRange::Skewed { min: 5.0, // This must never reach 0 max: 20_000.0, - factor: Range::skew_factor(-2.5), + factor: FloatRange::skew_factor(-2.5), }, ) // This needs quite a bit of smoothing to avoid artifacts @@ -170,10 +170,10 @@ impl DiopserParams { // The actual default neutral Q-value would be `sqrt(2) / 2`, but this value // produces slightly less ringing. 0.5, - Range::Skewed { + FloatRange::Skewed { min: 0.01, // This must also never reach 0 max: 30.0, - factor: Range::skew_factor(-2.5), + factor: FloatRange::skew_factor(-2.5), }, ) .with_smoother(SmoothingStyle::Logarithmic(100.0)) @@ -181,10 +181,10 @@ impl DiopserParams { filter_spread_octaves: FloatParam::new( "Filter Spread Octaves", 0.0, - Range::SymmetricalSkewed { + FloatRange::SymmetricalSkewed { min: -5.0, max: 5.0, - factor: Range::skew_factor(-1.0), + factor: FloatRange::skew_factor(-1.0), center: 0.0, }, ) @@ -202,7 +202,7 @@ impl DiopserParams { automation_precision: FloatParam::new( "Automation precision", normalize_automation_precision(128), - Range::Linear { min: 0.0, max: 1.0 }, + FloatRange::Linear { min: 0.0, max: 1.0 }, ) .with_unit("%") .with_value_to_string(Arc::new(|value| format!("{:.0}", value * 100.0))), diff --git a/plugins/examples/gain-gui/src/lib.rs b/plugins/examples/gain-gui/src/lib.rs index e5bb1825..117489aa 100644 --- a/plugins/examples/gain-gui/src/lib.rs +++ b/plugins/examples/gain-gui/src/lib.rs @@ -3,10 +3,10 @@ extern crate nih_plug; use atomic_float::AtomicF32; use nih_plug::{ - util, Buffer, BufferConfig, BusConfig, ClapPlugin, Editor, IntParam, Plugin, ProcessContext, + util, Buffer, BufferConfig, BusConfig, ClapPlugin, Editor, Plugin, ProcessContext, ProcessStatus, Vst3Plugin, }; -use nih_plug::{FloatParam, Params, Range, SmoothingStyle}; +use nih_plug::{FloatParam, FloatRange, IntParam, IntRange, Params, SmoothingStyle}; use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState}; use std::pin::Pin; use std::sync::Arc; @@ -54,7 +54,7 @@ impl Default for GainParams { gain: FloatParam::new( "Gain", 0.0, - Range::Linear { + FloatRange::Linear { min: -30.0, max: 30.0, }, @@ -62,15 +62,7 @@ impl Default for GainParams { .with_smoother(SmoothingStyle::Linear(50.0)) .with_step_size(0.01) .with_unit(" dB"), - some_int: IntParam::new( - "Something", - 3, - Range::Skewed { - min: 0, - max: 3, - factor: Range::skew_factor(1.0), - }, - ), + some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }), } } } @@ -103,7 +95,7 @@ impl Plugin for Gain { // This is a fancy widget that can get all the information it needs to properly // display and modify the parameter from the parametr itself // It's not yet fully implemented, as the text is missing. - ui.label("Some random wierdly distributed integer"); + ui.label("Some random integer"); ui.add(widgets::ParamSlider::for_param(¶ms.some_int, setter)); ui.label("Gain"); diff --git a/plugins/examples/gain/src/lib.rs b/plugins/examples/gain/src/lib.rs index 506e20e7..9cbe58c0 100644 --- a/plugins/examples/gain/src/lib.rs +++ b/plugins/examples/gain/src/lib.rs @@ -5,7 +5,7 @@ use nih_plug::{ formatters, util, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext, ProcessStatus, Vst3Plugin, }; -use nih_plug::{BoolParam, FloatParam, Params, Range, Smoother, SmoothingStyle}; +use nih_plug::{BoolParam, FloatParam, FloatRange, Params, Smoother, SmoothingStyle}; use parking_lot::RwLock; use std::pin::Pin; use std::sync::Arc; @@ -47,7 +47,7 @@ impl Default for GainParams { value: 0.0, smoothed: Smoother::new(SmoothingStyle::Linear(50.0)), value_changed: None, - range: Range::Linear { + range: FloatRange::Linear { min: -30.0, max: 30.0, }, diff --git a/plugins/examples/sine/src/lib.rs b/plugins/examples/sine/src/lib.rs index 0861e3e1..3ef7d6fd 100644 --- a/plugins/examples/sine/src/lib.rs +++ b/plugins/examples/sine/src/lib.rs @@ -5,7 +5,7 @@ use nih_plug::{ formatters, util, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext, ProcessStatus, Vst3Plugin, }; -use nih_plug::{BoolParam, FloatParam, Params, Range, Smoother, SmoothingStyle}; +use nih_plug::{BoolParam, FloatParam, FloatRange, Params, Smoother, SmoothingStyle}; use std::f32::consts; use std::pin::Pin; @@ -58,7 +58,7 @@ impl Default for SineParams { gain: FloatParam::new( "Gain", -10.0, - Range::Linear { + FloatRange::Linear { min: -30.0, max: 0.0, }, @@ -69,10 +69,10 @@ impl Default for SineParams { frequency: FloatParam::new( "Frequency", 420.0, - Range::Skewed { + FloatRange::Skewed { min: 1.0, max: 20_000.0, - factor: Range::skew_factor(-2.0), + factor: FloatRange::skew_factor(-2.0), }, ) .with_smoother(SmoothingStyle::Linear(10.0)) diff --git a/src/lib.rs b/src/lib.rs index e22a982c..f3726bde 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -17,7 +17,7 @@ pub use buffer::Buffer; pub use context::{GuiContext, ParamSetter, ProcessContext}; pub use param::enums::{Enum, EnumParam}; pub use param::internals::Params; -pub use param::range::Range; +pub use param::range::{FloatRange, IntRange}; pub use param::smoothing::{Smoother, SmoothingStyle}; pub use param::{BoolParam, FloatParam, IntParam, Param}; pub use plugin::{ diff --git a/src/param.rs b/src/param.rs index 6ca2255f..2c7b9122 100644 --- a/src/param.rs +++ b/src/param.rs @@ -7,7 +7,8 @@ use std::fmt::Display; // Parameter types mod boolean; pub mod enums; -mod plain; +mod float; +mod integer; pub mod internals; pub mod range; @@ -15,7 +16,8 @@ pub mod smoothing; pub use boolean::BoolParam; pub use enums::EnumParam; -pub use plain::{FloatParam, IntParam}; +pub use float::FloatParam; +pub use integer::IntParam; /// Describes a single parameter of any type. pub trait Param: Display { diff --git a/src/param/enums.rs b/src/param/enums.rs index 8b6c5cc2..f5ec6a66 100644 --- a/src/param/enums.rs +++ b/src/param/enums.rs @@ -5,7 +5,7 @@ use std::marker::PhantomData; use std::sync::Arc; use super::internals::ParamPtr; -use super::range::Range; +use super::range::IntRange; use super::{IntParam, Param}; // Re-export the derive macro @@ -71,7 +71,7 @@ impl Default for EnumParam { inner: EnumParamInner { inner: IntParam { value: T::default().to_index() as i32, - range: Range::Linear { + range: IntRange::Linear { min: 0, max: variants.len() as i32 - 1, }, @@ -245,7 +245,7 @@ impl EnumParam { inner: EnumParamInner { inner: IntParam { value: T::to_index(default) as i32, - range: Range::Linear { + range: IntRange::Linear { min: 0, max: variants.len() as i32 - 1, }, diff --git a/src/param/plain.rs b/src/param/float.rs similarity index 51% rename from src/param/plain.rs rename to src/param/float.rs index f2ef437b..08af21ba 100644 --- a/src/param/plain.rs +++ b/src/param/float.rs @@ -1,17 +1,14 @@ -//! Simple number-backed parameters. +//! Continuous (or discrete, with a step size) floating point parameters. use std::fmt::Display; use std::sync::Arc; use super::internals::ParamPtr; -use super::range::{NormalizebleRange, Range}; +use super::range::FloatRange; use super::smoothing::{Smoother, SmoothingStyle}; use super::Param; -pub type FloatParam = PlainParam; -pub type IntParam = PlainParam; - -/// A numerical parameter that's stored unnormalized. The range is used for the normalization +/// A floating point parameter that's stored unnormalized. The range is used for the normalization /// process. /// /// You can either initialize the struct directly, using `..Default::default()` to fill in the @@ -26,14 +23,14 @@ pub type IntParam = PlainParam; // writes to naturally aligned values up to word size are atomic, so there's no risk of reading // a partially written to value here. We should probably reconsider this at some point though. #[repr(C, align(4))] -pub struct PlainParam { +pub struct FloatParam { /// The field's current plain, unnormalized value. Should be initialized with the default value. /// Storing parameter values like this instead of in a single contiguous array is bad for cache /// locality, but it does allow for a much nicer declarative API. - pub value: T, + pub value: f32, /// An optional smoother that will automatically interpolate between the new automation values /// set by the host. - pub smoothed: Smoother, + pub smoothed: Smoother, /// Optional callback for listening to value changes. The argument passed to this function is /// the parameter's new **plain** value. This should not do anything expensive as it may be /// called multiple times in rapid succession. @@ -42,13 +39,13 @@ pub struct PlainParam { /// parmaeters struct, move a clone of that `Arc` into this closure, and then modify that. /// /// TODO: We probably also want to pass the old value to this function. - pub value_changed: Option>, + pub value_changed: Option>, /// The distribution of the parameter's values. - pub range: Range, - /// The distance between steps of a [FloatParam]. Ignored for [IntParam]. Mostly useful for - /// quantizing GUI input. If this is set and if [Self::value_to_string] is not set, then this is - /// also used when formatting the parameter. This must be a positive, nonzero number. + pub range: FloatRange, + /// The distance between discrete steps in this parameter. Mostly useful for quantizing GUI + /// input. If this is set and if [Self::value_to_string] is not set, then this is also used when + /// formatting the parameter. This must be a positive, nonzero number. pub step_size: Option, /// The parameter's human readable display name. pub name: &'static str, @@ -56,24 +53,20 @@ pub struct PlainParam { /// automatically add a space before the unit. pub unit: &'static str, /// Optional custom conversion function from a plain **unnormalized** value to a string. - pub value_to_string: Option String + Send + Sync>>, + pub value_to_string: Option String + Send + Sync>>, /// Optional custom conversion function from a string to a plain **unnormalized** value. If the /// string cannot be parsed, then this should return a `None`. If this happens while the /// parameter is being updated then the update will be canceled. - pub string_to_value: Option Option + Send + Sync>>, + pub string_to_value: Option Option + Send + Sync>>, } -impl Default for PlainParam -where - T: Default, - Range: Default, -{ +impl Default for FloatParam { fn default() -> Self { Self { - value: T::default(), + value: 0.0, smoothed: Smoother::none(), value_changed: None, - range: Range::default(), + range: FloatRange::default(), step_size: None, name: "", unit: "", @@ -83,7 +76,7 @@ where } } -impl Display for PlainParam { +impl Display for FloatParam { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match (&self.value_to_string, &self.step_size) { (Some(func), _) => write!(f, "{}{}", func(self.value), self.unit), @@ -96,129 +89,117 @@ impl Display for PlainParam { } } -macro_rules! impl_plainparam { - ($ty:ident, $plain:ty) => { - impl Param for $ty { - type Plain = $plain; +impl Param for FloatParam { + type Plain = f32; - fn name(&self) -> &'static str { - self.name - } + fn name(&self) -> &'static str { + self.name + } - fn unit(&self) -> &'static str { - self.unit - } + fn unit(&self) -> &'static str { + self.unit + } - fn step_count(&self) -> Option { - self.range.step_count() - } + fn step_count(&self) -> Option { + None + } - fn plain_value(&self) -> Self::Plain { - self.value - } + fn plain_value(&self) -> Self::Plain { + self.value + } - fn set_plain_value(&mut self, plain: Self::Plain) { - self.value = plain; - if let Some(f) = &self.value_changed { - f(plain); - } - } - - fn normalized_value(&self) -> f32 { - self.preview_normalized(self.value) - } - - fn set_normalized_value(&mut self, normalized: f32) { - self.set_plain_value(self.preview_plain(normalized)); - } - - fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String { - let value = self.preview_plain(normalized); - match (&self.value_to_string, &self.step_size, include_unit) { - (Some(f), _, true) => format!("{}{}", f(value), self.unit), - (Some(f), _, false) => format!("{}", f(value)), - (None, Some(step_size), true) => { - let num_digits = decimals_from_step_size(*step_size); - format!("{:.num_digits$}{}", value, self.unit) - } - (None, Some(step_size), false) => { - let num_digits = decimals_from_step_size(*step_size); - format!("{:.num_digits$}", value) - } - (None, None, true) => format!("{}{}", value, self.unit), - (None, None, false) => format!("{}", value), - } - } - - fn string_to_normalized_value(&self, string: &str) -> Option { - let value = match &self.string_to_value { - Some(f) => f(string), - // TODO: Check how Rust's parse function handles trailing garbage - None => string.parse().ok(), - }?; - - Some(self.preview_normalized(value)) - } - - fn preview_normalized(&self, plain: Self::Plain) -> f32 { - self.range.normalize(plain) - } - - fn preview_plain(&self, normalized: f32) -> Self::Plain { - 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 set_from_string(&mut self, string: &str) -> bool { - let value = match &self.string_to_value { - Some(f) => f(string), - // TODO: Check how Rust's parse function handles trailing garbage - None => string.parse().ok(), - }; - - match value { - Some(plain) => { - self.set_plain_value(plain); - true - } - None => false, - } - } - - fn update_smoother(&mut self, sample_rate: f32, reset: bool) { - if reset { - self.smoothed.reset(self.value); - } else { - self.smoothed.set_target(sample_rate, self.value); - } - } - - fn initialize_block_smoother(&mut self, max_block_size: usize) { - self.smoothed.initialize_block_smoother(max_block_size); - } - - fn as_ptr(&self) -> ParamPtr { - ParamPtr::$ty(self as *const $ty as *mut $ty) - } + fn set_plain_value(&mut self, plain: Self::Plain) { + self.value = plain; + if let Some(f) = &self.value_changed { + f(plain); } - }; + } + + fn normalized_value(&self) -> f32 { + self.preview_normalized(self.value) + } + + fn set_normalized_value(&mut self, normalized: f32) { + self.set_plain_value(self.preview_plain(normalized)); + } + + fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String { + let value = self.preview_plain(normalized); + match (&self.value_to_string, &self.step_size, include_unit) { + (Some(f), _, true) => format!("{}{}", f(value), self.unit), + (Some(f), _, false) => f(value), + (None, Some(step_size), true) => { + let num_digits = decimals_from_step_size(*step_size); + format!("{:.num_digits$}{}", value, self.unit) + } + (None, Some(step_size), false) => { + let num_digits = decimals_from_step_size(*step_size); + format!("{:.num_digits$}", value) + } + (None, None, true) => format!("{}{}", value, self.unit), + (None, None, false) => format!("{}", value), + } + } + + fn string_to_normalized_value(&self, string: &str) -> Option { + let value = match &self.string_to_value { + Some(f) => f(string), + // TODO: Check how Rust's parse function handles trailing garbage + None => string.parse().ok(), + }?; + + Some(self.preview_normalized(value)) + } + + fn preview_normalized(&self, plain: Self::Plain) -> f32 { + self.range.normalize(plain) + } + + fn preview_plain(&self, normalized: f32) -> Self::Plain { + let value = self.range.unnormalize(normalized); + match &self.step_size { + Some(step_size) => self.range.snap_to_step(value, *step_size as Self::Plain), + None => value, + } + } + + fn set_from_string(&mut self, string: &str) -> bool { + let value = match &self.string_to_value { + Some(f) => f(string), + // TODO: Check how Rust's parse function handles trailing garbage + None => string.parse().ok(), + }; + + match value { + Some(plain) => { + self.set_plain_value(plain); + true + } + None => false, + } + } + + fn update_smoother(&mut self, sample_rate: f32, reset: bool) { + if reset { + self.smoothed.reset(self.value); + } else { + self.smoothed.set_target(sample_rate, self.value); + } + } + + fn initialize_block_smoother(&mut self, max_block_size: usize) { + self.smoothed.initialize_block_smoother(max_block_size); + } + + fn as_ptr(&self) -> ParamPtr { + ParamPtr::FloatParam(self as *const _ as *mut _) + } } -impl_plainparam!(FloatParam, f32); -impl_plainparam!(IntParam, i32); - -impl PlainParam -where - Range: Default, -{ +impl FloatParam { /// Build a new [Self]. Use the other associated functions to modify the behavior of the /// parameter. - pub fn new(name: &'static str, default: T, range: Range) -> Self { + pub fn new(name: &'static str, default: f32, range: FloatRange) -> Self { Self { value: default, range, @@ -227,10 +208,8 @@ where } } - /// Run a callback whenever this parameter's value changes. The argument passed to this function - /// is the parameter's new value. This should not do anything expensive as it may be called - /// multiple times in rapid succession, and it can be run from both the GUI and the audio - /// thread. + /// Set up a smoother that can gradually interpolate changes made to this parameter, preventing + /// clicks and zipper noises. pub fn with_smoother(mut self, style: SmoothingStyle) -> Self { self.smoothed = Smoother::new(style); self @@ -240,7 +219,7 @@ where /// is the parameter's new value. This should not do anything expensive as it may be called /// multiple times in rapid succession, and it can be run from both the GUI and the audio /// thread. - pub fn with_callback(mut self, callback: Arc) -> Self { + pub fn with_callback(mut self, callback: Arc) -> Self { self.value_changed = Some(callback); self } @@ -253,40 +232,36 @@ where self } + /// Set the distance between steps of a [FloatParam]. Mostly useful for quantizing GUI input. If + /// this is set and if [Self::value_to_string] is not set, then this is also used when + /// formatting the parameter. This must be a positive, nonzero number. + pub fn with_step_size(mut self, step_size: f32) -> Self { + self.step_size = Some(step_size); + self + } + /// Use a custom conversion function to convert the plain, unnormalized value to a /// string. pub fn with_value_to_string( mut self, - callback: Arc String + Send + Sync>, + callback: Arc String + Send + Sync>, ) -> Self { self.value_to_string = Some(callback); self } - // `with_step_size` is only implemented for the f32 version - /// Use a custom conversion function to convert from a string to a plain, unnormalized /// value. If the string cannot be parsed, then this should return a `None`. If this /// happens while the parameter is being updated then the update will be canceled. pub fn with_string_to_value( mut self, - callback: Arc Option + Send + Sync>, + callback: Arc Option + Send + Sync>, ) -> Self { self.string_to_value = Some(callback); self } } -impl PlainParam { - /// Set the distance between steps of a [FloatParam]. Mostly useful for quantizing GUI input. If - /// this is set and if [Self::value_to_string] is not set, then this is also used when - /// formatting the parameter. This must be a positive, nonzero number. - pub fn with_step_size(mut self, step_size: f32) -> Self { - self.step_size = Some(step_size); - self - } -} - /// Caldculate how many decimals to round to when displaying a floating point value with a specific /// step size. We'll perform some rounding to ignore spurious extra precision caused by the floating /// point quantization. diff --git a/src/param/integer.rs b/src/param/integer.rs new file mode 100644 index 00000000..29bf2f73 --- /dev/null +++ b/src/param/integer.rs @@ -0,0 +1,236 @@ +//! Stepped integer parameters. + +use std::fmt::Display; +use std::sync::Arc; + +use super::internals::ParamPtr; +use super::range::IntRange; +use super::smoothing::{Smoother, SmoothingStyle}; +use super::Param; + +/// A discrete integer parameter that's stored unnormalized. The range is used for the normalization +/// process. +/// +/// You can either initialize the struct directly, using `..Default::default()` to fill in the +/// unused fields, or you can use the builder interface with [Self::new()]. +// +// XXX: To keep the API simple and to allow the optimizer to do its thing, the values are stored as +// plain primitive values that are modified through the `*mut` pointers from the plugin's +// `Params` object. Technically modifying these while the GUI is open is unsound. We could +// remedy this by changing `value` to be an atomic type and adding a function also called +// `value()` to load that value, but in practice that should not be necessary if we don't do +// anything crazy other than modifying this value. On both AArch64 and x86(_64) reads and +// writes to naturally aligned values up to word size are atomic, so there's no risk of reading +// a partially written to value here. We should probably reconsider this at some point though. +#[repr(C, align(4))] +pub struct IntParam { + /// The field's current plain, unnormalized value. Should be initialized with the default value. + /// Storing parameter values like this instead of in a single contiguous array is bad for cache + /// locality, but it does allow for a much nicer declarative API. + pub value: i32, + /// An optional smoother that will automatically interpolate between the new automation values + /// set by the host. + pub smoothed: Smoother, + /// Optional callback for listening to value changes. The argument passed to this function is + /// the parameter's new **plain** value. This should not do anything expensive as it may be + /// called multiple times in rapid succession. + /// + /// To use this, you'll probably want to store an `Arc` alongside the parmater in the + /// parmaeters struct, move a clone of that `Arc` into this closure, and then modify that. + /// + /// TODO: We probably also want to pass the old value to this function. + pub value_changed: Option>, + + /// The distribution of the parameter's values. + pub range: IntRange, + /// The parameter's human readable display name. + pub name: &'static str, + /// The parameter value's unit, added after `value_to_string` if that is set. NIH-plug will not + /// automatically add a space before the unit. + pub unit: &'static str, + /// Optional custom conversion function from a plain **unnormalized** value to a string. + pub value_to_string: Option String + Send + Sync>>, + /// Optional custom conversion function from a string to a plain **unnormalized** value. If the + /// string cannot be parsed, then this should return a `None`. If this happens while the + /// parameter is being updated then the update will be canceled. + pub string_to_value: Option Option + Send + Sync>>, +} + +impl Default for IntParam { + fn default() -> Self { + Self { + value: 0, + smoothed: Smoother::none(), + value_changed: None, + range: IntRange::default(), + name: "", + unit: "", + value_to_string: None, + string_to_value: None, + } + } +} + +impl Display for IntParam { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match &self.value_to_string { + Some(func) => write!(f, "{}{}", func(self.value), self.unit), + _ => write!(f, "{}{}", self.value, self.unit), + } + } +} + +impl Param for IntParam { + type Plain = i32; + + fn name(&self) -> &'static str { + self.name + } + + fn unit(&self) -> &'static str { + self.unit + } + + fn step_count(&self) -> Option { + self.range.step_count() + } + + fn plain_value(&self) -> Self::Plain { + self.value + } + + fn set_plain_value(&mut self, plain: Self::Plain) { + self.value = plain; + if let Some(f) = &self.value_changed { + f(plain); + } + } + + fn normalized_value(&self) -> f32 { + self.preview_normalized(self.value) + } + + fn set_normalized_value(&mut self, normalized: f32) { + self.set_plain_value(self.preview_plain(normalized)); + } + + fn normalized_value_to_string(&self, normalized: f32, include_unit: bool) -> String { + let value = self.preview_plain(normalized); + match (&self.value_to_string, include_unit) { + (Some(f), true) => format!("{}{}", f(value), self.unit), + (Some(f), false) => format!("{}", f(value)), + (None, true) => format!("{}{}", value, self.unit), + (None, false) => format!("{}", value), + } + } + + fn string_to_normalized_value(&self, string: &str) -> Option { + let value = match &self.string_to_value { + Some(f) => f(string), + // TODO: Check how Rust's parse function handles trailing garbage + None => string.parse().ok(), + }?; + + Some(self.preview_normalized(value)) + } + + fn preview_normalized(&self, plain: Self::Plain) -> f32 { + self.range.normalize(plain) + } + + fn preview_plain(&self, normalized: f32) -> Self::Plain { + self.range.unnormalize(normalized) + } + + fn set_from_string(&mut self, string: &str) -> bool { + let value = match &self.string_to_value { + Some(f) => f(string), + // TODO: Check how Rust's parse function handles trailing garbage + None => string.parse().ok(), + }; + + match value { + Some(plain) => { + self.set_plain_value(plain); + true + } + None => false, + } + } + + fn update_smoother(&mut self, sample_rate: f32, reset: bool) { + if reset { + self.smoothed.reset(self.value); + } else { + self.smoothed.set_target(sample_rate, self.value); + } + } + + fn initialize_block_smoother(&mut self, max_block_size: usize) { + self.smoothed.initialize_block_smoother(max_block_size); + } + + fn as_ptr(&self) -> ParamPtr { + ParamPtr::IntParam(self as *const _ as *mut _) + } +} + +impl IntParam { + /// Build a new [Self]. Use the other associated functions to modify the behavior of the + /// parameter. + pub fn new(name: &'static str, default: i32, range: IntRange) -> Self { + Self { + value: default, + range, + name, + ..Default::default() + } + } + + /// Set up a smoother that can gradually interpolate changes made to this parameter, preventing + /// clicks and zipper noises. + pub fn with_smoother(mut self, style: SmoothingStyle) -> Self { + self.smoothed = Smoother::new(style); + self + } + + /// Run a callback whenever this parameter's value changes. The argument passed to this function + /// is the parameter's new value. This should not do anything expensive as it may be called + /// multiple times in rapid succession, and it can be run from both the GUI and the audio + /// thread. + pub fn with_callback(mut self, callback: Arc) -> Self { + self.value_changed = Some(callback); + self + } + + /// Display a unit when rendering this parameter to a string. Appended after the + /// [Self::value_to_string] function if that is also set. NIH-plug will not + /// automatically add a space before the unit. + pub fn with_unit(mut self, unit: &'static str) -> Self { + self.unit = unit; + self + } + + /// Use a custom conversion function to convert the plain, unnormalized value to a + /// string. + pub fn with_value_to_string( + mut self, + callback: Arc String + Send + Sync>, + ) -> Self { + self.value_to_string = Some(callback); + self + } + + // `with_step_size` is only implemented for the f32 version + + /// Use a custom conversion function to convert from a string to a plain, unnormalized + /// value. If the string cannot be parsed, then this should return a `None`. If this + /// happens while the parameter is being updated then the update will be canceled. + pub fn with_string_to_value( + mut self, + callback: Arc Option + Send + Sync>, + ) -> Self { + self.string_to_value = Some(callback); + self + } +} diff --git a/src/param/range.rs b/src/param/range.rs index 19c82f9e..bf06766d 100644 --- a/src/param/range.rs +++ b/src/param/range.rs @@ -1,78 +1,61 @@ //! Different ranges for numeric parameters. -/// A distribution for a parameter's range. All range endpoints are inclusive. -/// -/// TODO: Hosts will do weird things when using skewed ranges for integers because of the steps. -/// Perhaps it would be best to just only allow linear ranges for integers. Bitwig for -/// instance will send a parameter change rounding the value down when restoring a plugin -/// patch. +/// A distribution for a floating point parameter's range. All range endpoints are inclusive. #[derive(Debug)] -pub enum Range { +pub enum FloatRange { /// The values are uniformly distributed between `min` and `max`. - Linear { min: T, max: T }, + Linear { min: f32, max: f32 }, /// The range is skewed by a factor. Values above 1.0 will make the end of the range wider, /// while values between 0 and 1 will skew the range towards the start. Use [Range::skew_factor()] /// for a more intuitively way to calculate the skew factor where positive values skew the range /// towards the end while negative values skew the range toward the start. - Skewed { min: T, max: T, factor: f32 }, + Skewed { min: f32, max: f32, factor: f32 }, /// The same as [Range::Skewed], but with the skewing happening from a central point. This /// central point is rescaled to be at 50% of the parameter's range for convenience of use. Git /// blame this comment to find a version that doesn't do this. SymmetricalSkewed { - min: T, - max: T, + min: f32, + max: f32, factor: f32, - center: T, + center: f32, }, } -impl Range<()> { - /// Calculate a skew factor for [Range::Skewed] and [Range::SymmetricalSkewed]. Positive values - /// make the end of the range wider while negative make the start of the range wider. - pub fn skew_factor(factor: f32) -> f32 { - 2.0f32.powf(factor) - } +/// A distribution for an integer parameter's range. All range endpoints are inclusive. Only linear +/// ranges are supported for integers since hosts expect discrete parameters to have a fixed step +/// size. +#[derive(Debug)] +pub enum IntRange { + /// The values are uniformly distributed between `min` and `max`. + Linear { min: i32, max: i32 }, } -/// 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]`. - fn normalize(&self, plain: T) -> f32; - - /// 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; - - /// The number of steps in this range, if it is stepped. Used for the host's generic UI. - fn step_count(&self) -> Option; -} - -impl Default for Range { +impl Default for FloatRange { fn default() -> Self { Self::Linear { min: 0.0, max: 1.0 } } } -impl Default for Range { +impl Default for IntRange { fn default() -> Self { Self::Linear { min: 0, max: 1 } } } -impl NormalizebleRange for Range { - fn normalize(&self, plain: f32) -> f32 { +impl FloatRange { + /// Calculate a skew factor for [Range::Skewed] and [Range::SymmetricalSkewed]. Positive values + /// make the end of the range wider while negative make the start of the range wider. + pub fn skew_factor(factor: f32) -> f32 { + 2.0f32.powf(factor) + } + + /// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the + /// normalized value exceeds `[0, 1]`. + pub fn normalize(&self, plain: f32) -> f32 { match &self { - Range::Linear { min, max } => (plain - min) / (max - min), - Range::Skewed { min, max, factor } => ((plain - min) / (max - min)).powf(*factor), - Range::SymmetricalSkewed { + FloatRange::Linear { min, max } => (plain - min) / (max - min), + FloatRange::Skewed { min, max, factor } => ((plain - min) / (max - min)).powf(*factor), + FloatRange::SymmetricalSkewed { min, max, factor, @@ -102,14 +85,16 @@ impl NormalizebleRange for Range { .clamp(0.0, 1.0) } - fn unnormalize(&self, normalized: f32) -> f32 { + /// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the plain, unnormalized value + /// would exceed that range. + pub fn unnormalize(&self, normalized: f32) -> f32 { let normalized = normalized.clamp(0.0, 1.0); match &self { - Range::Linear { min, max } => (normalized * (max - min)) + min, - Range::Skewed { min, max, factor } => { + FloatRange::Linear { min, max } => (normalized * (max - min)) + min, + FloatRange::Skewed { min, max, factor } => { (normalized.powf(factor.recip()) * (max - min)) + min } - Range::SymmetricalSkewed { + FloatRange::SymmetricalSkewed { min, max, factor, @@ -131,90 +116,41 @@ impl NormalizebleRange for Range { } } - fn snap_to_step(&self, value: f32, step_size: f32) -> f32 { + /// Snap a vlue to a step size, clamping to the minimum and maximum value of the range. + pub 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), + FloatRange::Linear { min, max } => (min, max), + FloatRange::Skewed { min, max, .. } => (min, max), + FloatRange::SymmetricalSkewed { min, max, .. } => (min, max), }; ((value / step_size).round() * step_size).clamp(*min, *max) } - - fn step_count(&self) -> Option { - None - } } -impl NormalizebleRange for Range { - fn normalize(&self, plain: i32) -> f32 { +impl IntRange { + /// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the + /// normalized value exceeds `[0, 1]`. + pub fn normalize(&self, plain: i32) -> f32 { match &self { - Range::Linear { min, max } => (plain - min) as f32 / (max - min) as f32, - Range::Skewed { min, max, factor } => { - ((plain - min) as f32 / (max - min) as f32).powf(*factor) - } - Range::SymmetricalSkewed { - min, - max, - factor, - center, - } => { - // See the comments in the float version - let unscaled_proportion = (plain - min) as f32 / (max - min) as f32; - let center_proportion = (center - min) as f32 / (max - min) as f32; - if unscaled_proportion > center_proportion { - let scaled_proportion = (unscaled_proportion - center_proportion) - * (1.0 - center_proportion).recip(); - (scaled_proportion.powf(*factor) * 0.5) + 0.5 - } else { - let inverted_scaled_proportion = - (center_proportion - unscaled_proportion) * (center_proportion).recip(); - (1.0 - inverted_scaled_proportion.powf(*factor)) * 0.5 - } - } + IntRange::Linear { min, max } => (plain - min) as f32 / (max - min) as f32, } .clamp(0.0, 1.0) } - fn unnormalize(&self, normalized: f32) -> i32 { + /// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the plain, unnormalized value + /// would exceed that range. + pub fn unnormalize(&self, normalized: f32) -> i32 { let normalized = normalized.clamp(0.0, 1.0); match &self { - Range::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min, - Range::Skewed { min, max, factor } => { - (normalized.powf(factor.recip()) * (max - min) as f32).round() as i32 + min - } - Range::SymmetricalSkewed { - min, - max, - factor, - center, - } => { - let center_proportion = (center - min) as f32 / (max - min) as f32; - let skewed_proportion = if normalized > 0.5 { - let scaled_proportion = (normalized - 0.5) * 2.0; - (scaled_proportion.powf(factor.recip()) * (1.0 - center_proportion)) - + center_proportion - } else { - let inverted_scaled_proportion = (0.5 - normalized) * 2.0; - (1.0 - inverted_scaled_proportion.powf(factor.recip())) * center_proportion - }; - - (skewed_proportion * (max - min) as f32).round() as i32 + min - } + IntRange::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min, } } - 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 - } - - fn step_count(&self) -> Option { + /// The number of steps in this range, if it is stepped. Used for the host's generic UI. + pub fn step_count(&self) -> Option { match self { - Range::Linear { min, max } => Some((max - min) as usize), - Range::Skewed { min, max, .. } => Some((max - min) as usize), - Range::SymmetricalSkewed { min, max, .. } => Some((max - min) as usize), + IntRange::Linear { min, max } => Some((max - min) as usize), } } } @@ -223,35 +159,27 @@ impl NormalizebleRange for Range { mod tests { use super::*; - fn make_linear_float_range() -> Range { - Range::Linear { + fn make_linear_float_range() -> FloatRange { + FloatRange::Linear { min: 10.0, max: 20.0, } } - fn make_linear_int_range() -> Range { - Range::Linear { min: -10, max: 10 } + fn make_linear_int_range() -> IntRange { + IntRange::Linear { min: -10, max: 10 } } - fn make_skewed_float_range(factor: f32) -> Range { - Range::Skewed { + fn make_skewed_float_range(factor: f32) -> FloatRange { + FloatRange::Skewed { min: 10.0, max: 20.0, factor, } } - fn make_skewed_int_range(factor: f32) -> Range { - Range::Skewed { - min: -10, - max: 10, - factor, - } - } - - fn make_symmetrical_skewed_float_range(factor: f32) -> Range { - Range::SymmetricalSkewed { + fn make_symmetrical_skewed_float_range(factor: f32) -> FloatRange { + FloatRange::SymmetricalSkewed { min: 10.0, max: 20.0, factor, @@ -259,15 +187,6 @@ mod tests { } } - fn make_symmetrical_skewed_int_range(factor: f32) -> Range { - Range::SymmetricalSkewed { - min: -10, - max: 10, - factor, - center: -3, - } - } - #[test] fn step_size() { // These are weird step sizes, but if it works here then it will work for anything @@ -284,7 +203,6 @@ mod tests { } mod linear { - use super::super::*; use super::*; #[test] @@ -319,33 +237,20 @@ mod tests { } mod skewed { - use super::super::*; use super::*; #[test] fn range_normalize_float() { - let range = make_skewed_float_range(Range::skew_factor(-2.0)); + let range = make_skewed_float_range(FloatRange::skew_factor(-2.0)); assert_eq!(range.normalize(17.5), 0.9306049); } - #[test] - fn range_normalize_int() { - let range = make_skewed_int_range(Range::skew_factor(-2.0)); - assert_eq!(range.normalize(-5), 0.70710677); - } - #[test] fn range_unnormalize_float() { - let range = make_skewed_float_range(Range::skew_factor(-2.0)); + let range = make_skewed_float_range(FloatRange::skew_factor(-2.0)); assert_eq!(range.unnormalize(0.9306049), 17.5); } - #[test] - fn range_unnormalize_int() { - let range = make_skewed_int_range(Range::skew_factor(-2.0)); - assert_eq!(range.unnormalize(0.70710677), -5); - } - #[test] fn range_normalize_linear_equiv_float() { let linear_range = make_linear_float_range(); @@ -353,13 +258,6 @@ mod tests { assert_eq!(linear_range.normalize(17.5), skewed_range.normalize(17.5)); } - #[test] - fn range_normalize_linear_equiv_int() { - let linear_range = make_linear_int_range(); - let skewed_range = make_skewed_int_range(1.0); - assert_eq!(linear_range.normalize(-5), skewed_range.normalize(-5)); - } - #[test] fn range_unnormalize_linear_equiv_float() { let linear_range = make_linear_float_range(); @@ -369,54 +267,21 @@ mod tests { skewed_range.unnormalize(0.25) ); } - - #[test] - fn range_unnormalize_linear_equiv_int() { - let linear_range = make_linear_int_range(); - let skewed_range = make_skewed_int_range(1.0); - assert_eq!( - linear_range.unnormalize(0.25), - skewed_range.unnormalize(0.25) - ); - } - - #[test] - fn range_unnormalize_linear_equiv_int_rounding() { - let linear_range = make_linear_int_range(); - let skewed_range = make_skewed_int_range(1.0); - assert_eq!( - linear_range.unnormalize(0.73), - skewed_range.unnormalize(0.73) - ); - } } mod symmetrical_skewed { - use super::super::*; use super::*; #[test] fn range_normalize_float() { - let range = make_symmetrical_skewed_float_range(Range::skew_factor(-2.0)); + let range = make_symmetrical_skewed_float_range(FloatRange::skew_factor(-2.0)); assert_eq!(range.normalize(17.5), 0.951801); } - #[test] - fn range_normalize_int() { - let range = make_symmetrical_skewed_int_range(Range::skew_factor(-2.0)); - assert_eq!(range.normalize(-5), 0.13444477); - } - #[test] fn range_unnormalize_float() { - let range = make_symmetrical_skewed_float_range(Range::skew_factor(-2.0)); + let range = make_symmetrical_skewed_float_range(FloatRange::skew_factor(-2.0)); assert_eq!(range.unnormalize(0.951801), 17.5); } - - #[test] - fn range_unnormalize_int() { - let range = make_symmetrical_skewed_int_range(Range::skew_factor(-2.0)); - assert_eq!(range.unnormalize(0.13444477), -5); - } } }