1
0
Fork 0

Implement step snapping for parameters

This commit is contained in:
Robbert van der Helm 2022-02-13 17:52:54 +01:00
parent 4481ef0ae9
commit 19d2dc0a67
3 changed files with 50 additions and 9 deletions

View file

@ -3,8 +3,8 @@ extern crate nih_plug;
use atomic_float::AtomicF32;
use nih_plug::{
formatters, util, Buffer, BufferConfig, BusConfig, Editor, IntParam, Plugin, ProcessContext,
ProcessStatus, Vst3Plugin,
util, Buffer, BufferConfig, BusConfig, Editor, IntParam, Plugin, ProcessContext, ProcessStatus,
Vst3Plugin,
};
use nih_plug::{FloatParam, Params, Range, SmoothingStyle};
use nih_plug_egui::{create_egui_editor, egui, widgets, EguiState};

View file

@ -34,18 +34,17 @@ pub trait Param: Display {
/// Get the unnormalized value for this parameter.
fn plain_value(&self) -> Self::Plain;
/// Set this parameter based on a plain, unnormalized value.
/// Set this parameter based on a plain, unnormalized value. This does **not** snap to step
/// sizes for continuous parameters (i.e. [FloatParam]).
///
/// This does **not** update the smoother.
///
/// TDOO: Decide on whether this should update the smoother or not. That wouldn't be compatible
/// with sample accurate automation when we add that.
fn set_plain_value(&mut self, plain: Self::Plain);
/// Get the normalized `[0, 1]` value for this parameter.
fn normalized_value(&self) -> f32;
/// Set this parameter based on a normalized value.
/// Set this parameter based on a normalized value. This **does** snap to step sizes for
/// continuous parameters (i.e. [FloatParam]).
///
/// This does **not** update the smoother.
fn set_normalized_value(&mut self, normalized: f32);
@ -63,7 +62,7 @@ pub trait Param: Display {
fn preview_normalized(&self, plain: Self::Plain) -> f32;
/// Get the plain, unnormalized value for a normalized value, as a float. Used as part of the
/// wrappers.
/// wrappers. This **does** snap to step sizes for continuous parameters (i.e. [FloatParam]).
fn preview_plain(&self, normalized: f32) -> Self::Plain;
/// Internal implementation detail for implementing [internals::Params]. This should not be used
@ -251,7 +250,13 @@ macro_rules! impl_plainparam {
}
fn preview_plain(&self, normalized: f32) -> Self::Plain {
self.range.unnormalize(normalized)
let value = self.range.unnormalize(normalized);
match &self.step_size {
// Step size snapping is not defined for [IntParam], so this cast is here just
// so we can keep everything in this macro
Some(step_size) => self.range.snap_to_step(value, *step_size as Self::Plain),
None => value,
}
}
fn as_ptr(&self) -> internals::ParamPtr {

View file

@ -31,6 +31,9 @@ impl Range<()> {
/// A normalizable range for type `T`, where `self` is expected to be a type `R<T>`. 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<T> {
/// Normalize a plain, unnormalized value. Will be clamped to the bounds of the range if the
/// normalized value exceeds `[0, 1]`.
@ -39,6 +42,9 @@ pub(crate) trait NormalizebleRange<T> {
/// Unnormalize a normalized value. Will be clamped to `[0, 1]` if the plain, unnormalized value
/// would exceed that range.
fn unnormalize(&self, normalized: f32) -> T;
/// Snap a vlue to a step size, clamping to the minimum and maximum value of the range.
fn snap_to_step(&self, value: T, step_size: T) -> T;
}
impl Default for Range<f32> {
@ -116,6 +122,16 @@ impl NormalizebleRange<f32> for Range<f32> {
}
}
}
fn snap_to_step(&self, value: f32, step_size: f32) -> f32 {
let (min, max) = match &self {
Range::Linear { min, max } => (min, max),
Range::Skewed { min, max, .. } => (min, max),
Range::SymmetricalSkewed { min, max, .. } => (min, max),
};
((value / step_size).round() * step_size).clamp(*min, *max)
}
}
impl NormalizebleRange<i32> for Range<i32> {
@ -175,6 +191,12 @@ impl NormalizebleRange<i32> for Range<i32> {
}
}
}
fn snap_to_step(&self, value: i32, _step_size: i32) -> i32 {
// Integers are already discrete, and we don't allow setting step sizes on them through the
// builder interface
value
}
}
#[cfg(test)]
@ -226,6 +248,20 @@ mod tests {
}
}
#[test]
fn step_size() {
// These are weird step sizes, but if it works here then it will work for anything
let range = make_linear_float_range();
assert_eq!(range.snap_to_step(13.0, 4.73), 14.49);
}
#[test]
fn step_size_clamping() {
let range = make_linear_float_range();
assert_eq!(range.snap_to_step(10.0, 4.73), 10.0);
assert_eq!(range.snap_to_step(20.0, 6.73), 20.0);
}
mod linear {
use super::super::*;
use super::*;