💥 Rework FloatParam and IntParam
They are now two separate types with slightly different options. I had these merged initially because they're 95% the same, and I thought it would be fun to have weird distributions for integer parameters, but that doesn't really work because hosts and the plugin APIs expect the steps to be linear. And if you're going to have an unstepped integer parameter, might as well use FloatParam with rounding. Because non-linear ranges are no longer possible with IntParam, the types have been split up to make everything much more readable instead of adding a parameterizing the range type with another type family.
This commit is contained in:
parent
006dcde313
commit
76369ad1e1
|
@ -23,7 +23,7 @@ use nih_plug::{
|
||||||
formatters, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext, ProcessStatus,
|
formatters, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext, ProcessStatus,
|
||||||
Vst3Plugin,
|
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 nih_plug::{Enum, EnumParam};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::{AtomicBool, Ordering};
|
use std::sync::atomic::{AtomicBool, Ordering};
|
||||||
|
@ -140,7 +140,7 @@ impl DiopserParams {
|
||||||
filter_stages: IntParam::new(
|
filter_stages: IntParam::new(
|
||||||
"Filter Stages",
|
"Filter Stages",
|
||||||
0,
|
0,
|
||||||
Range::Linear {
|
IntRange::Linear {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: MAX_NUM_FILTERS as i32,
|
max: MAX_NUM_FILTERS as i32,
|
||||||
},
|
},
|
||||||
|
@ -155,10 +155,10 @@ impl DiopserParams {
|
||||||
filter_frequency: FloatParam::new(
|
filter_frequency: FloatParam::new(
|
||||||
"Filter Frequency",
|
"Filter Frequency",
|
||||||
200.0,
|
200.0,
|
||||||
Range::Skewed {
|
FloatRange::Skewed {
|
||||||
min: 5.0, // This must never reach 0
|
min: 5.0, // This must never reach 0
|
||||||
max: 20_000.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
|
// 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
|
// The actual default neutral Q-value would be `sqrt(2) / 2`, but this value
|
||||||
// produces slightly less ringing.
|
// produces slightly less ringing.
|
||||||
0.5,
|
0.5,
|
||||||
Range::Skewed {
|
FloatRange::Skewed {
|
||||||
min: 0.01, // This must also never reach 0
|
min: 0.01, // This must also never reach 0
|
||||||
max: 30.0,
|
max: 30.0,
|
||||||
factor: Range::skew_factor(-2.5),
|
factor: FloatRange::skew_factor(-2.5),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_smoother(SmoothingStyle::Logarithmic(100.0))
|
.with_smoother(SmoothingStyle::Logarithmic(100.0))
|
||||||
|
@ -181,10 +181,10 @@ impl DiopserParams {
|
||||||
filter_spread_octaves: FloatParam::new(
|
filter_spread_octaves: FloatParam::new(
|
||||||
"Filter Spread Octaves",
|
"Filter Spread Octaves",
|
||||||
0.0,
|
0.0,
|
||||||
Range::SymmetricalSkewed {
|
FloatRange::SymmetricalSkewed {
|
||||||
min: -5.0,
|
min: -5.0,
|
||||||
max: 5.0,
|
max: 5.0,
|
||||||
factor: Range::skew_factor(-1.0),
|
factor: FloatRange::skew_factor(-1.0),
|
||||||
center: 0.0,
|
center: 0.0,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
@ -202,7 +202,7 @@ impl DiopserParams {
|
||||||
automation_precision: FloatParam::new(
|
automation_precision: FloatParam::new(
|
||||||
"Automation precision",
|
"Automation precision",
|
||||||
normalize_automation_precision(128),
|
normalize_automation_precision(128),
|
||||||
Range::Linear { min: 0.0, max: 1.0 },
|
FloatRange::Linear { min: 0.0, max: 1.0 },
|
||||||
)
|
)
|
||||||
.with_unit("%")
|
.with_unit("%")
|
||||||
.with_value_to_string(Arc::new(|value| format!("{:.0}", value * 100.0))),
|
.with_value_to_string(Arc::new(|value| format!("{:.0}", value * 100.0))),
|
||||||
|
|
|
@ -3,10 +3,10 @@ extern crate nih_plug;
|
||||||
|
|
||||||
use atomic_float::AtomicF32;
|
use atomic_float::AtomicF32;
|
||||||
use nih_plug::{
|
use nih_plug::{
|
||||||
util, Buffer, BufferConfig, BusConfig, ClapPlugin, Editor, IntParam, Plugin, ProcessContext,
|
util, Buffer, BufferConfig, BusConfig, ClapPlugin, Editor, Plugin, ProcessContext,
|
||||||
ProcessStatus, Vst3Plugin,
|
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 nih_plug_egui::{create_egui_editor, egui, widgets, EguiState};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -54,7 +54,7 @@ impl Default for GainParams {
|
||||||
gain: FloatParam::new(
|
gain: FloatParam::new(
|
||||||
"Gain",
|
"Gain",
|
||||||
0.0,
|
0.0,
|
||||||
Range::Linear {
|
FloatRange::Linear {
|
||||||
min: -30.0,
|
min: -30.0,
|
||||||
max: 30.0,
|
max: 30.0,
|
||||||
},
|
},
|
||||||
|
@ -62,15 +62,7 @@ impl Default for GainParams {
|
||||||
.with_smoother(SmoothingStyle::Linear(50.0))
|
.with_smoother(SmoothingStyle::Linear(50.0))
|
||||||
.with_step_size(0.01)
|
.with_step_size(0.01)
|
||||||
.with_unit(" dB"),
|
.with_unit(" dB"),
|
||||||
some_int: IntParam::new(
|
some_int: IntParam::new("Something", 3, IntRange::Linear { min: 0, max: 3 }),
|
||||||
"Something",
|
|
||||||
3,
|
|
||||||
Range::Skewed {
|
|
||||||
min: 0,
|
|
||||||
max: 3,
|
|
||||||
factor: Range::skew_factor(1.0),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +95,7 @@ impl Plugin for Gain {
|
||||||
// This is a fancy widget that can get all the information it needs to properly
|
// This is a fancy widget that can get all the information it needs to properly
|
||||||
// display and modify the parameter from the parametr itself
|
// display and modify the parameter from the parametr itself
|
||||||
// It's not yet fully implemented, as the text is missing.
|
// 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.add(widgets::ParamSlider::for_param(¶ms.some_int, setter));
|
||||||
|
|
||||||
ui.label("Gain");
|
ui.label("Gain");
|
||||||
|
|
|
@ -5,7 +5,7 @@ use nih_plug::{
|
||||||
formatters, util, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext,
|
formatters, util, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext,
|
||||||
ProcessStatus, Vst3Plugin,
|
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 parking_lot::RwLock;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
@ -47,7 +47,7 @@ impl Default for GainParams {
|
||||||
value: 0.0,
|
value: 0.0,
|
||||||
smoothed: Smoother::new(SmoothingStyle::Linear(50.0)),
|
smoothed: Smoother::new(SmoothingStyle::Linear(50.0)),
|
||||||
value_changed: None,
|
value_changed: None,
|
||||||
range: Range::Linear {
|
range: FloatRange::Linear {
|
||||||
min: -30.0,
|
min: -30.0,
|
||||||
max: 30.0,
|
max: 30.0,
|
||||||
},
|
},
|
||||||
|
|
|
@ -5,7 +5,7 @@ use nih_plug::{
|
||||||
formatters, util, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext,
|
formatters, util, Buffer, BufferConfig, BusConfig, ClapPlugin, Plugin, ProcessContext,
|
||||||
ProcessStatus, Vst3Plugin,
|
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::f32::consts;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ impl Default for SineParams {
|
||||||
gain: FloatParam::new(
|
gain: FloatParam::new(
|
||||||
"Gain",
|
"Gain",
|
||||||
-10.0,
|
-10.0,
|
||||||
Range::Linear {
|
FloatRange::Linear {
|
||||||
min: -30.0,
|
min: -30.0,
|
||||||
max: 0.0,
|
max: 0.0,
|
||||||
},
|
},
|
||||||
|
@ -69,10 +69,10 @@ impl Default for SineParams {
|
||||||
frequency: FloatParam::new(
|
frequency: FloatParam::new(
|
||||||
"Frequency",
|
"Frequency",
|
||||||
420.0,
|
420.0,
|
||||||
Range::Skewed {
|
FloatRange::Skewed {
|
||||||
min: 1.0,
|
min: 1.0,
|
||||||
max: 20_000.0,
|
max: 20_000.0,
|
||||||
factor: Range::skew_factor(-2.0),
|
factor: FloatRange::skew_factor(-2.0),
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_smoother(SmoothingStyle::Linear(10.0))
|
.with_smoother(SmoothingStyle::Linear(10.0))
|
||||||
|
|
|
@ -17,7 +17,7 @@ pub use buffer::Buffer;
|
||||||
pub use context::{GuiContext, ParamSetter, ProcessContext};
|
pub use context::{GuiContext, ParamSetter, ProcessContext};
|
||||||
pub use param::enums::{Enum, EnumParam};
|
pub use param::enums::{Enum, EnumParam};
|
||||||
pub use param::internals::Params;
|
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::smoothing::{Smoother, SmoothingStyle};
|
||||||
pub use param::{BoolParam, FloatParam, IntParam, Param};
|
pub use param::{BoolParam, FloatParam, IntParam, Param};
|
||||||
pub use plugin::{
|
pub use plugin::{
|
||||||
|
|
|
@ -7,7 +7,8 @@ use std::fmt::Display;
|
||||||
// Parameter types
|
// Parameter types
|
||||||
mod boolean;
|
mod boolean;
|
||||||
pub mod enums;
|
pub mod enums;
|
||||||
mod plain;
|
mod float;
|
||||||
|
mod integer;
|
||||||
|
|
||||||
pub mod internals;
|
pub mod internals;
|
||||||
pub mod range;
|
pub mod range;
|
||||||
|
@ -15,7 +16,8 @@ pub mod smoothing;
|
||||||
|
|
||||||
pub use boolean::BoolParam;
|
pub use boolean::BoolParam;
|
||||||
pub use enums::EnumParam;
|
pub use enums::EnumParam;
|
||||||
pub use plain::{FloatParam, IntParam};
|
pub use float::FloatParam;
|
||||||
|
pub use integer::IntParam;
|
||||||
|
|
||||||
/// Describes a single parameter of any type.
|
/// Describes a single parameter of any type.
|
||||||
pub trait Param: Display {
|
pub trait Param: Display {
|
||||||
|
|
|
@ -5,7 +5,7 @@ use std::marker::PhantomData;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::internals::ParamPtr;
|
use super::internals::ParamPtr;
|
||||||
use super::range::Range;
|
use super::range::IntRange;
|
||||||
use super::{IntParam, Param};
|
use super::{IntParam, Param};
|
||||||
|
|
||||||
// Re-export the derive macro
|
// Re-export the derive macro
|
||||||
|
@ -71,7 +71,7 @@ impl<T: Enum + Default> Default for EnumParam<T> {
|
||||||
inner: EnumParamInner {
|
inner: EnumParamInner {
|
||||||
inner: IntParam {
|
inner: IntParam {
|
||||||
value: T::default().to_index() as i32,
|
value: T::default().to_index() as i32,
|
||||||
range: Range::Linear {
|
range: IntRange::Linear {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: variants.len() as i32 - 1,
|
max: variants.len() as i32 - 1,
|
||||||
},
|
},
|
||||||
|
@ -245,7 +245,7 @@ impl<T: Enum + 'static> EnumParam<T> {
|
||||||
inner: EnumParamInner {
|
inner: EnumParamInner {
|
||||||
inner: IntParam {
|
inner: IntParam {
|
||||||
value: T::to_index(default) as i32,
|
value: T::to_index(default) as i32,
|
||||||
range: Range::Linear {
|
range: IntRange::Linear {
|
||||||
min: 0,
|
min: 0,
|
||||||
max: variants.len() as i32 - 1,
|
max: variants.len() as i32 - 1,
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,17 +1,14 @@
|
||||||
//! Simple number-backed parameters.
|
//! Continuous (or discrete, with a step size) floating point parameters.
|
||||||
|
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use super::internals::ParamPtr;
|
use super::internals::ParamPtr;
|
||||||
use super::range::{NormalizebleRange, Range};
|
use super::range::FloatRange;
|
||||||
use super::smoothing::{Smoother, SmoothingStyle};
|
use super::smoothing::{Smoother, SmoothingStyle};
|
||||||
use super::Param;
|
use super::Param;
|
||||||
|
|
||||||
pub type FloatParam = PlainParam<f32>;
|
/// A floating point parameter that's stored unnormalized. The range is used for the normalization
|
||||||
pub type IntParam = PlainParam<i32>;
|
|
||||||
|
|
||||||
/// A numerical parameter that's stored unnormalized. The range is used for the normalization
|
|
||||||
/// process.
|
/// process.
|
||||||
///
|
///
|
||||||
/// You can either initialize the struct directly, using `..Default::default()` to fill in the
|
/// You can either initialize the struct directly, using `..Default::default()` to fill in the
|
||||||
|
@ -26,14 +23,14 @@ pub type IntParam = PlainParam<i32>;
|
||||||
// writes to naturally aligned values up to word size are atomic, so there's no risk of reading
|
// 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.
|
// a partially written to value here. We should probably reconsider this at some point though.
|
||||||
#[repr(C, align(4))]
|
#[repr(C, align(4))]
|
||||||
pub struct PlainParam<T> {
|
pub struct FloatParam {
|
||||||
/// The field's current plain, unnormalized value. Should be initialized with the default value.
|
/// 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
|
/// 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.
|
/// 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
|
/// An optional smoother that will automatically interpolate between the new automation values
|
||||||
/// set by the host.
|
/// set by the host.
|
||||||
pub smoothed: Smoother<T>,
|
pub smoothed: Smoother<f32>,
|
||||||
/// Optional callback for listening to value changes. The argument passed to this function is
|
/// 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
|
/// the parameter's new **plain** value. This should not do anything expensive as it may be
|
||||||
/// called multiple times in rapid succession.
|
/// called multiple times in rapid succession.
|
||||||
|
@ -42,13 +39,13 @@ pub struct PlainParam<T> {
|
||||||
/// parmaeters struct, move a clone of that `Arc` into this closure, and then modify that.
|
/// 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.
|
/// TODO: We probably also want to pass the old value to this function.
|
||||||
pub value_changed: Option<Arc<dyn Fn(T) + Send + Sync>>,
|
pub value_changed: Option<Arc<dyn Fn(f32) + Send + Sync>>,
|
||||||
|
|
||||||
/// The distribution of the parameter's values.
|
/// The distribution of the parameter's values.
|
||||||
pub range: Range<T>,
|
pub range: FloatRange,
|
||||||
/// The distance between steps of a [FloatParam]. Ignored for [IntParam]. Mostly useful for
|
/// The distance between discrete steps in this parameter. Mostly useful for quantizing GUI
|
||||||
/// quantizing GUI input. If this is set and if [Self::value_to_string] is not set, then this is
|
/// input. If this is set and if [Self::value_to_string] is not set, then this is also used when
|
||||||
/// also used when formatting the parameter. This must be a positive, nonzero number.
|
/// formatting the parameter. This must be a positive, nonzero number.
|
||||||
pub step_size: Option<f32>,
|
pub step_size: Option<f32>,
|
||||||
/// The parameter's human readable display name.
|
/// The parameter's human readable display name.
|
||||||
pub name: &'static str,
|
pub name: &'static str,
|
||||||
|
@ -56,24 +53,20 @@ pub struct PlainParam<T> {
|
||||||
/// automatically add a space before the unit.
|
/// automatically add a space before the unit.
|
||||||
pub unit: &'static str,
|
pub unit: &'static str,
|
||||||
/// Optional custom conversion function from a plain **unnormalized** value to a string.
|
/// Optional custom conversion function from a plain **unnormalized** value to a string.
|
||||||
pub value_to_string: Option<Arc<dyn Fn(T) -> String + Send + Sync>>,
|
pub value_to_string: Option<Arc<dyn Fn(f32) -> String + Send + Sync>>,
|
||||||
/// Optional custom conversion function from a string to a plain **unnormalized** value. If the
|
/// 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
|
/// 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.
|
/// parameter is being updated then the update will be canceled.
|
||||||
pub string_to_value: Option<Arc<dyn Fn(&str) -> Option<T> + Send + Sync>>,
|
pub string_to_value: Option<Arc<dyn Fn(&str) -> Option<f32> + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Default for PlainParam<T>
|
impl Default for FloatParam {
|
||||||
where
|
|
||||||
T: Default,
|
|
||||||
Range<T>: Default,
|
|
||||||
{
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: T::default(),
|
value: 0.0,
|
||||||
smoothed: Smoother::none(),
|
smoothed: Smoother::none(),
|
||||||
value_changed: None,
|
value_changed: None,
|
||||||
range: Range::default(),
|
range: FloatRange::default(),
|
||||||
step_size: None,
|
step_size: None,
|
||||||
name: "",
|
name: "",
|
||||||
unit: "",
|
unit: "",
|
||||||
|
@ -83,7 +76,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T: Display + Copy> Display for PlainParam<T> {
|
impl Display for FloatParam {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match (&self.value_to_string, &self.step_size) {
|
match (&self.value_to_string, &self.step_size) {
|
||||||
(Some(func), _) => write!(f, "{}{}", func(self.value), self.unit),
|
(Some(func), _) => write!(f, "{}{}", func(self.value), self.unit),
|
||||||
|
@ -96,10 +89,8 @@ impl<T: Display + Copy> Display for PlainParam<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
macro_rules! impl_plainparam {
|
impl Param for FloatParam {
|
||||||
($ty:ident, $plain:ty) => {
|
type Plain = f32;
|
||||||
impl Param for $ty {
|
|
||||||
type Plain = $plain;
|
|
||||||
|
|
||||||
fn name(&self) -> &'static str {
|
fn name(&self) -> &'static str {
|
||||||
self.name
|
self.name
|
||||||
|
@ -110,7 +101,7 @@ macro_rules! impl_plainparam {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step_count(&self) -> Option<usize> {
|
fn step_count(&self) -> Option<usize> {
|
||||||
self.range.step_count()
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn plain_value(&self) -> Self::Plain {
|
fn plain_value(&self) -> Self::Plain {
|
||||||
|
@ -136,7 +127,7 @@ macro_rules! impl_plainparam {
|
||||||
let value = self.preview_plain(normalized);
|
let value = self.preview_plain(normalized);
|
||||||
match (&self.value_to_string, &self.step_size, include_unit) {
|
match (&self.value_to_string, &self.step_size, include_unit) {
|
||||||
(Some(f), _, true) => format!("{}{}", f(value), self.unit),
|
(Some(f), _, true) => format!("{}{}", f(value), self.unit),
|
||||||
(Some(f), _, false) => format!("{}", f(value)),
|
(Some(f), _, false) => f(value),
|
||||||
(None, Some(step_size), true) => {
|
(None, Some(step_size), true) => {
|
||||||
let num_digits = decimals_from_step_size(*step_size);
|
let num_digits = decimals_from_step_size(*step_size);
|
||||||
format!("{:.num_digits$}{}", value, self.unit)
|
format!("{:.num_digits$}{}", value, self.unit)
|
||||||
|
@ -167,8 +158,6 @@ macro_rules! impl_plainparam {
|
||||||
fn preview_plain(&self, normalized: f32) -> Self::Plain {
|
fn preview_plain(&self, normalized: f32) -> Self::Plain {
|
||||||
let value = self.range.unnormalize(normalized);
|
let value = self.range.unnormalize(normalized);
|
||||||
match &self.step_size {
|
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),
|
Some(step_size) => self.range.snap_to_step(value, *step_size as Self::Plain),
|
||||||
None => value,
|
None => value,
|
||||||
}
|
}
|
||||||
|
@ -203,22 +192,14 @@ macro_rules! impl_plainparam {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn as_ptr(&self) -> ParamPtr {
|
fn as_ptr(&self) -> ParamPtr {
|
||||||
ParamPtr::$ty(self as *const $ty as *mut $ty)
|
ParamPtr::FloatParam(self as *const _ as *mut _)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl_plainparam!(FloatParam, f32);
|
impl FloatParam {
|
||||||
impl_plainparam!(IntParam, i32);
|
|
||||||
|
|
||||||
impl<T: Default> PlainParam<T>
|
|
||||||
where
|
|
||||||
Range<T>: Default,
|
|
||||||
{
|
|
||||||
/// Build a new [Self]. Use the other associated functions to modify the behavior of the
|
/// Build a new [Self]. Use the other associated functions to modify the behavior of the
|
||||||
/// parameter.
|
/// parameter.
|
||||||
pub fn new(name: &'static str, default: T, range: Range<T>) -> Self {
|
pub fn new(name: &'static str, default: f32, range: FloatRange) -> Self {
|
||||||
Self {
|
Self {
|
||||||
value: default,
|
value: default,
|
||||||
range,
|
range,
|
||||||
|
@ -227,10 +208,8 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Run a callback whenever this parameter's value changes. The argument passed to this function
|
/// Set up a smoother that can gradually interpolate changes made to this parameter, preventing
|
||||||
/// is the parameter's new value. This should not do anything expensive as it may be called
|
/// clicks and zipper noises.
|
||||||
/// multiple times in rapid succession, and it can be run from both the GUI and the audio
|
|
||||||
/// thread.
|
|
||||||
pub fn with_smoother(mut self, style: SmoothingStyle) -> Self {
|
pub fn with_smoother(mut self, style: SmoothingStyle) -> Self {
|
||||||
self.smoothed = Smoother::new(style);
|
self.smoothed = Smoother::new(style);
|
||||||
self
|
self
|
||||||
|
@ -240,7 +219,7 @@ where
|
||||||
/// is the parameter's new value. This should not do anything expensive as it may be called
|
/// 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
|
/// multiple times in rapid succession, and it can be run from both the GUI and the audio
|
||||||
/// thread.
|
/// thread.
|
||||||
pub fn with_callback(mut self, callback: Arc<dyn Fn(T) + Send + Sync>) -> Self {
|
pub fn with_callback(mut self, callback: Arc<dyn Fn(f32) + Send + Sync>) -> Self {
|
||||||
self.value_changed = Some(callback);
|
self.value_changed = Some(callback);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
@ -253,40 +232,36 @@ where
|
||||||
self
|
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
|
/// Use a custom conversion function to convert the plain, unnormalized value to a
|
||||||
/// string.
|
/// string.
|
||||||
pub fn with_value_to_string(
|
pub fn with_value_to_string(
|
||||||
mut self,
|
mut self,
|
||||||
callback: Arc<dyn Fn(T) -> String + Send + Sync>,
|
callback: Arc<dyn Fn(f32) -> String + Send + Sync>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.value_to_string = Some(callback);
|
self.value_to_string = Some(callback);
|
||||||
self
|
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
|
/// 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
|
/// 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.
|
/// happens while the parameter is being updated then the update will be canceled.
|
||||||
pub fn with_string_to_value<F>(
|
pub fn with_string_to_value<F>(
|
||||||
mut self,
|
mut self,
|
||||||
callback: Arc<dyn Fn(&str) -> Option<T> + Send + Sync>,
|
callback: Arc<dyn Fn(&str) -> Option<f32> + Send + Sync>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
self.string_to_value = Some(callback);
|
self.string_to_value = Some(callback);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PlainParam<f32> {
|
|
||||||
/// 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
|
/// 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
|
/// step size. We'll perform some rounding to ignore spurious extra precision caused by the floating
|
||||||
/// point quantization.
|
/// point quantization.
|
236
src/param/integer.rs
Normal file
236
src/param/integer.rs
Normal file
|
@ -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<i32>,
|
||||||
|
/// 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<Atomic*>` 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<Arc<dyn Fn(i32) + Send + Sync>>,
|
||||||
|
|
||||||
|
/// 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<Arc<dyn Fn(i32) -> 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<Arc<dyn Fn(&str) -> Option<i32> + 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<usize> {
|
||||||
|
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<f32> {
|
||||||
|
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<dyn Fn(i32) + Send + Sync>) -> 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<dyn Fn(i32) -> 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<F>(
|
||||||
|
mut self,
|
||||||
|
callback: Arc<dyn Fn(&str) -> Option<i32> + Send + Sync>,
|
||||||
|
) -> Self {
|
||||||
|
self.string_to_value = Some(callback);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,78 +1,61 @@
|
||||||
//! Different ranges for numeric parameters.
|
//! Different ranges for numeric parameters.
|
||||||
|
|
||||||
/// A distribution for a parameter's range. All range endpoints are inclusive.
|
/// A distribution for a floating point 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.
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Range<T> {
|
pub enum FloatRange {
|
||||||
/// The values are uniformly distributed between `min` and `max`.
|
/// 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,
|
/// 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()]
|
/// 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
|
/// 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.
|
/// 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
|
/// 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
|
/// 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.
|
/// blame this comment to find a version that doesn't do this.
|
||||||
SymmetricalSkewed {
|
SymmetricalSkewed {
|
||||||
min: T,
|
min: f32,
|
||||||
max: T,
|
max: f32,
|
||||||
factor: f32,
|
factor: f32,
|
||||||
center: T,
|
center: f32,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Range<()> {
|
/// A distribution for an integer parameter's range. All range endpoints are inclusive. Only linear
|
||||||
/// Calculate a skew factor for [Range::Skewed] and [Range::SymmetricalSkewed]. Positive values
|
/// ranges are supported for integers since hosts expect discrete parameters to have a fixed step
|
||||||
/// make the end of the range wider while negative make the start of the range wider.
|
/// size.
|
||||||
pub fn skew_factor(factor: f32) -> f32 {
|
#[derive(Debug)]
|
||||||
2.0f32.powf(factor)
|
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<T>`. Higher kinded
|
impl Default for FloatRange {
|
||||||
/// 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<T> {
|
|
||||||
/// 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<usize>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for Range<f32> {
|
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Linear { min: 0.0, max: 1.0 }
|
Self::Linear { min: 0.0, max: 1.0 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Range<i32> {
|
impl Default for IntRange {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::Linear { min: 0, max: 1 }
|
Self::Linear { min: 0, max: 1 }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NormalizebleRange<f32> for Range<f32> {
|
impl FloatRange {
|
||||||
fn normalize(&self, plain: f32) -> f32 {
|
/// 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 {
|
match &self {
|
||||||
Range::Linear { min, max } => (plain - min) / (max - min),
|
FloatRange::Linear { min, max } => (plain - min) / (max - min),
|
||||||
Range::Skewed { min, max, factor } => ((plain - min) / (max - min)).powf(*factor),
|
FloatRange::Skewed { min, max, factor } => ((plain - min) / (max - min)).powf(*factor),
|
||||||
Range::SymmetricalSkewed {
|
FloatRange::SymmetricalSkewed {
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
factor,
|
factor,
|
||||||
|
@ -102,14 +85,16 @@ impl NormalizebleRange<f32> for Range<f32> {
|
||||||
.clamp(0.0, 1.0)
|
.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);
|
let normalized = normalized.clamp(0.0, 1.0);
|
||||||
match &self {
|
match &self {
|
||||||
Range::Linear { min, max } => (normalized * (max - min)) + min,
|
FloatRange::Linear { min, max } => (normalized * (max - min)) + min,
|
||||||
Range::Skewed { min, max, factor } => {
|
FloatRange::Skewed { min, max, factor } => {
|
||||||
(normalized.powf(factor.recip()) * (max - min)) + min
|
(normalized.powf(factor.recip()) * (max - min)) + min
|
||||||
}
|
}
|
||||||
Range::SymmetricalSkewed {
|
FloatRange::SymmetricalSkewed {
|
||||||
min,
|
min,
|
||||||
max,
|
max,
|
||||||
factor,
|
factor,
|
||||||
|
@ -131,90 +116,41 @@ impl NormalizebleRange<f32> for Range<f32> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
let (min, max) = match &self {
|
||||||
Range::Linear { min, max } => (min, max),
|
FloatRange::Linear { min, max } => (min, max),
|
||||||
Range::Skewed { min, max, .. } => (min, max),
|
FloatRange::Skewed { min, max, .. } => (min, max),
|
||||||
Range::SymmetricalSkewed { min, max, .. } => (min, max),
|
FloatRange::SymmetricalSkewed { min, max, .. } => (min, max),
|
||||||
};
|
};
|
||||||
|
|
||||||
((value / step_size).round() * step_size).clamp(*min, *max)
|
((value / step_size).round() * step_size).clamp(*min, *max)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn step_count(&self) -> Option<usize> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NormalizebleRange<i32> for Range<i32> {
|
impl IntRange {
|
||||||
fn normalize(&self, plain: i32) -> f32 {
|
/// 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 {
|
match &self {
|
||||||
Range::Linear { min, max } => (plain - min) as f32 / (max - min) as f32,
|
IntRange::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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.clamp(0.0, 1.0)
|
.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);
|
let normalized = normalized.clamp(0.0, 1.0);
|
||||||
match &self {
|
match &self {
|
||||||
Range::Linear { min, max } => (normalized * (max - min) as f32).round() as i32 + min,
|
IntRange::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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn snap_to_step(&self, value: i32, _step_size: i32) -> i32 {
|
/// The number of steps in this range, if it is stepped. Used for the host's generic UI.
|
||||||
// Integers are already discrete, and we don't allow setting step sizes on them through the
|
pub fn step_count(&self) -> Option<usize> {
|
||||||
// builder interface
|
|
||||||
value
|
|
||||||
}
|
|
||||||
|
|
||||||
fn step_count(&self) -> Option<usize> {
|
|
||||||
match self {
|
match self {
|
||||||
Range::Linear { min, max } => Some((max - min) as usize),
|
IntRange::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),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -223,35 +159,27 @@ impl NormalizebleRange<i32> for Range<i32> {
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn make_linear_float_range() -> Range<f32> {
|
fn make_linear_float_range() -> FloatRange {
|
||||||
Range::Linear {
|
FloatRange::Linear {
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 20.0,
|
max: 20.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_linear_int_range() -> Range<i32> {
|
fn make_linear_int_range() -> IntRange {
|
||||||
Range::Linear { min: -10, max: 10 }
|
IntRange::Linear { min: -10, max: 10 }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_skewed_float_range(factor: f32) -> Range<f32> {
|
fn make_skewed_float_range(factor: f32) -> FloatRange {
|
||||||
Range::Skewed {
|
FloatRange::Skewed {
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 20.0,
|
max: 20.0,
|
||||||
factor,
|
factor,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_skewed_int_range(factor: f32) -> Range<i32> {
|
fn make_symmetrical_skewed_float_range(factor: f32) -> FloatRange {
|
||||||
Range::Skewed {
|
FloatRange::SymmetricalSkewed {
|
||||||
min: -10,
|
|
||||||
max: 10,
|
|
||||||
factor,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_symmetrical_skewed_float_range(factor: f32) -> Range<f32> {
|
|
||||||
Range::SymmetricalSkewed {
|
|
||||||
min: 10.0,
|
min: 10.0,
|
||||||
max: 20.0,
|
max: 20.0,
|
||||||
factor,
|
factor,
|
||||||
|
@ -259,15 +187,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_symmetrical_skewed_int_range(factor: f32) -> Range<i32> {
|
|
||||||
Range::SymmetricalSkewed {
|
|
||||||
min: -10,
|
|
||||||
max: 10,
|
|
||||||
factor,
|
|
||||||
center: -3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn step_size() {
|
fn step_size() {
|
||||||
// These are weird step sizes, but if it works here then it will work for anything
|
// These are weird step sizes, but if it works here then it will work for anything
|
||||||
|
@ -284,7 +203,6 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod linear {
|
mod linear {
|
||||||
use super::super::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
@ -319,33 +237,20 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod skewed {
|
mod skewed {
|
||||||
use super::super::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_normalize_float() {
|
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);
|
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]
|
#[test]
|
||||||
fn range_unnormalize_float() {
|
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);
|
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]
|
#[test]
|
||||||
fn range_normalize_linear_equiv_float() {
|
fn range_normalize_linear_equiv_float() {
|
||||||
let linear_range = make_linear_float_range();
|
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));
|
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]
|
#[test]
|
||||||
fn range_unnormalize_linear_equiv_float() {
|
fn range_unnormalize_linear_equiv_float() {
|
||||||
let linear_range = make_linear_float_range();
|
let linear_range = make_linear_float_range();
|
||||||
|
@ -369,54 +267,21 @@ mod tests {
|
||||||
skewed_range.unnormalize(0.25)
|
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 {
|
mod symmetrical_skewed {
|
||||||
use super::super::*;
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_normalize_float() {
|
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);
|
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]
|
#[test]
|
||||||
fn range_unnormalize_float() {
|
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);
|
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue