Add an OversamplingAware smoothing style
This can be used to have an ergonomic way to do multi-rate smoothing with variable oversampling amounts that only the `Arc<AtomicF32>` to be updated from a parameter callback.
This commit is contained in:
parent
95d7dabcee
commit
8a7100ac3e
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -16,6 +16,23 @@ state is to list breaking changes.
|
||||||
|
|
||||||
- The `nih_debug_assert*!()` macros are now upgraded to regular panicking
|
- The `nih_debug_assert*!()` macros are now upgraded to regular panicking
|
||||||
`debug_assert!()` macros during tests.
|
`debug_assert!()` macros during tests.
|
||||||
|
- `SmoothingStyle::for_oversampling_factor()` has been removed in favor of a new
|
||||||
|
mechanism that allows the smmoothers to be aware of oversampling. A new
|
||||||
|
`Smoothingstyle::OversamplingAware(oversampling_times, style)` can be used to
|
||||||
|
wrap another `Smoothingstyle` to make it aware of an oversampling amount that
|
||||||
|
can change at runtime. The `oversampling_times` is an `Arc<AtomicF32>` that
|
||||||
|
indicates the current oversampling amount. This makes it possible to link
|
||||||
|
multiple parameters to the same oversampling amount, have different sets of
|
||||||
|
parameters run at different effective sample rates, and automatically update
|
||||||
|
those oversampling amounts/sample rate multipliers from a parameter callback.
|
||||||
|
- As a consequence of the above change, `Smoothingstyle` is no longer `Copy`
|
||||||
|
since the `OversamplingAware` smoothing style contain an
|
||||||
|
`Arc<Smoothingstyle>`. It can still be `Clone`d.
|
||||||
|
|
||||||
|
### Changes
|
||||||
|
|
||||||
|
- The prelude module now also re-exports the `AtomicF32` type since it's needed
|
||||||
|
to use the new `Smoothingstyle::OversamplingAware`.
|
||||||
|
|
||||||
## [2023-04-01]
|
## [2023-04-01]
|
||||||
|
|
||||||
|
|
|
@ -114,15 +114,15 @@ impl CrossoverParams {
|
||||||
|
|
||||||
// TODO: More sensible default frequencies
|
// TODO: More sensible default frequencies
|
||||||
crossover_1_freq: FloatParam::new("Crossover 1", 200.0, crossover_range)
|
crossover_1_freq: FloatParam::new("Crossover 1", 200.0, crossover_range)
|
||||||
.with_smoother(crossover_smoothing_style)
|
.with_smoother(crossover_smoothing_style.clone())
|
||||||
.with_value_to_string(crossover_value_to_string.clone())
|
.with_value_to_string(crossover_value_to_string.clone())
|
||||||
.with_string_to_value(crossover_string_to_value.clone()),
|
.with_string_to_value(crossover_string_to_value.clone()),
|
||||||
crossover_2_freq: FloatParam::new("Crossover 2", 1000.0, crossover_range)
|
crossover_2_freq: FloatParam::new("Crossover 2", 1000.0, crossover_range)
|
||||||
.with_smoother(crossover_smoothing_style)
|
.with_smoother(crossover_smoothing_style.clone())
|
||||||
.with_value_to_string(crossover_value_to_string.clone())
|
.with_value_to_string(crossover_value_to_string.clone())
|
||||||
.with_string_to_value(crossover_string_to_value.clone()),
|
.with_string_to_value(crossover_string_to_value.clone()),
|
||||||
crossover_3_freq: FloatParam::new("Crossover 3", 5000.0, crossover_range)
|
crossover_3_freq: FloatParam::new("Crossover 3", 5000.0, crossover_range)
|
||||||
.with_smoother(crossover_smoothing_style)
|
.with_smoother(crossover_smoothing_style.clone())
|
||||||
.with_value_to_string(crossover_value_to_string.clone())
|
.with_value_to_string(crossover_value_to_string.clone())
|
||||||
.with_string_to_value(crossover_string_to_value.clone()),
|
.with_string_to_value(crossover_string_to_value.clone()),
|
||||||
crossover_4_freq: FloatParam::new("Crossover 4", 10000.0, crossover_range)
|
crossover_4_freq: FloatParam::new("Crossover 4", 10000.0, crossover_range)
|
||||||
|
|
|
@ -1,11 +1,21 @@
|
||||||
//! Utilities to handle smoothing parameter changes over time.
|
//! Utilities to handle smoothing parameter changes over time.
|
||||||
|
|
||||||
use atomic_float::AtomicF32;
|
|
||||||
use std::sync::atomic::{AtomicI32, Ordering};
|
use std::sync::atomic::{AtomicI32, Ordering};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
// Re-exported here because it's sued in `SmoothingStyle`.
|
||||||
|
pub use atomic_float::AtomicF32;
|
||||||
|
|
||||||
/// Controls if and how parameters gets smoothed.
|
/// Controls if and how parameters gets smoothed.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum SmoothingStyle {
|
pub enum SmoothingStyle {
|
||||||
|
/// Wraps another smoothing style to create a multi-rate oversampling-aware smoother for a
|
||||||
|
/// parameter that's used in an oversampled part of the plugin. The `Arc<AtomicF32>` indicates
|
||||||
|
/// the oversampling amount, where `1.0` means no oversampling. This value can change at
|
||||||
|
/// runtime, and it effectively scales the sample rate when computing new smoothing coefficients
|
||||||
|
/// when the parameter's value changes.
|
||||||
|
OversamplingAware(Arc<AtomicF32>, Arc<SmoothingStyle>),
|
||||||
|
|
||||||
/// No smoothing is applied. The parameter's `value` field contains the latest sample value
|
/// No smoothing is applied. The parameter's `value` field contains the latest sample value
|
||||||
/// available for the parameters.
|
/// available for the parameters.
|
||||||
None,
|
None,
|
||||||
|
@ -64,18 +74,6 @@ pub struct SmootherIter<'a, T: Smoothable> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SmoothingStyle {
|
impl SmoothingStyle {
|
||||||
/// Utility function to modify the duration to compensate for an oversampling factor. When using
|
|
||||||
/// 4x oversampling, the duration needs to be four times as long to compensate for the four
|
|
||||||
/// times higher effective sample rate.
|
|
||||||
pub fn for_oversampling_factor(self, factor: f32) -> Self {
|
|
||||||
match self {
|
|
||||||
SmoothingStyle::None => SmoothingStyle::None,
|
|
||||||
SmoothingStyle::Linear(time) => SmoothingStyle::Linear(time * factor),
|
|
||||||
SmoothingStyle::Logarithmic(time) => SmoothingStyle::Logarithmic(time * factor),
|
|
||||||
SmoothingStyle::Exponential(time) => SmoothingStyle::Exponential(time * factor),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compute the number of steps to reach the target value based on the sample rate and this
|
/// Compute the number of steps to reach the target value based on the sample rate and this
|
||||||
/// smoothing style's duration.
|
/// smoothing style's duration.
|
||||||
#[inline]
|
#[inline]
|
||||||
|
@ -83,10 +81,12 @@ impl SmoothingStyle {
|
||||||
nih_debug_assert!(sample_rate > 0.0);
|
nih_debug_assert!(sample_rate > 0.0);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
SmoothingStyle::None => 1,
|
Self::OversamplingAware(oversampling_times, style) => {
|
||||||
SmoothingStyle::Linear(time)
|
style.num_steps(sample_rate * oversampling_times.load(Ordering::Relaxed))
|
||||||
| SmoothingStyle::Logarithmic(time)
|
}
|
||||||
| SmoothingStyle::Exponential(time) => {
|
|
||||||
|
Self::None => 1,
|
||||||
|
Self::Linear(time) | Self::Logarithmic(time) | Self::Exponential(time) => {
|
||||||
nih_debug_assert!(*time >= 0.0);
|
nih_debug_assert!(*time >= 0.0);
|
||||||
(sample_rate * time / 1000.0).round() as u32
|
(sample_rate * time / 1000.0).round() as u32
|
||||||
}
|
}
|
||||||
|
@ -101,9 +101,11 @@ impl SmoothingStyle {
|
||||||
nih_debug_assert!(num_steps >= 1);
|
nih_debug_assert!(num_steps >= 1);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
SmoothingStyle::None => 0.0,
|
Self::OversamplingAware(_, style) => style.step_size(start, target, num_steps),
|
||||||
SmoothingStyle::Linear(_) => (target - start) / (num_steps as f32),
|
|
||||||
SmoothingStyle::Logarithmic(_) => {
|
Self::None => 0.0,
|
||||||
|
Self::Linear(_) => (target - start) / (num_steps as f32),
|
||||||
|
Self::Logarithmic(_) => {
|
||||||
// We need to solve `start * (step_size ^ num_steps) = target` for `step_size`
|
// We need to solve `start * (step_size ^ num_steps) = target` for `step_size`
|
||||||
nih_debug_assert_ne!(start, 0.0);
|
nih_debug_assert_ne!(start, 0.0);
|
||||||
((target / start) as f64).powf((num_steps as f64).recip()) as f32
|
((target / start) as f64).powf((num_steps as f64).recip()) as f32
|
||||||
|
@ -112,7 +114,7 @@ impl SmoothingStyle {
|
||||||
// multiplied by, while the target value is multiplied by one minus the coefficient. This
|
// multiplied by, while the target value is multiplied by one minus the coefficient. This
|
||||||
// reaches 99.99% of the target value after `num_steps`. The smoother will snap to the
|
// reaches 99.99% of the target value after `num_steps`. The smoother will snap to the
|
||||||
// target value after that point.
|
// target value after that point.
|
||||||
SmoothingStyle::Exponential(_) => 0.0001f64.powf((num_steps as f64).recip()) as f32,
|
Self::Exponential(_) => 0.0001f64.powf((num_steps as f64).recip()) as f32,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,10 +127,12 @@ impl SmoothingStyle {
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn next(&self, current: f32, target: f32, step_size: f32) -> f32 {
|
pub fn next(&self, current: f32, target: f32, step_size: f32) -> f32 {
|
||||||
match self {
|
match self {
|
||||||
SmoothingStyle::None => target,
|
Self::OversamplingAware(_, style) => style.next(current, target, step_size),
|
||||||
SmoothingStyle::Linear(_) => current + step_size,
|
|
||||||
SmoothingStyle::Logarithmic(_) => current * step_size,
|
Self::None => target,
|
||||||
SmoothingStyle::Exponential(_) => (current * step_size) + (target * (1.0 - step_size)),
|
Self::Linear(_) => current + step_size,
|
||||||
|
Self::Logarithmic(_) => current * step_size,
|
||||||
|
Self::Exponential(_) => (current * step_size) + (target * (1.0 - step_size)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,10 +147,12 @@ impl SmoothingStyle {
|
||||||
nih_debug_assert!(steps >= 1);
|
nih_debug_assert!(steps >= 1);
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
SmoothingStyle::None => target,
|
Self::OversamplingAware(_, style) => style.next_step(current, target, step_size, steps),
|
||||||
SmoothingStyle::Linear(_) => current + (step_size * steps as f32),
|
|
||||||
SmoothingStyle::Logarithmic(_) => current * (step_size.powi(steps as i32)),
|
Self::None => target,
|
||||||
SmoothingStyle::Exponential(_) => {
|
Self::Linear(_) => current + (step_size * steps as f32),
|
||||||
|
Self::Logarithmic(_) => current * (step_size.powi(steps as i32)),
|
||||||
|
Self::Exponential(_) => {
|
||||||
// This is the same as calculating `current = (current * step_size) +
|
// This is the same as calculating `current = (current * step_size) +
|
||||||
// (target * (1 - step_size))` in a loop since the target value won't change
|
// (target * (1 - step_size))` in a loop since the target value won't change
|
||||||
let coefficient = step_size.powi(steps as i32);
|
let coefficient = step_size.powi(steps as i32);
|
||||||
|
@ -198,7 +204,7 @@ impl<T: Smoothable> Clone for Smoother<T> {
|
||||||
// We can't derive clone because of the atomics, but these atomics are only here to allow
|
// We can't derive clone because of the atomics, but these atomics are only here to allow
|
||||||
// Send+Sync interior mutability
|
// Send+Sync interior mutability
|
||||||
Self {
|
Self {
|
||||||
style: self.style,
|
style: self.style.clone(),
|
||||||
steps_left: AtomicI32::new(self.steps_left.load(Ordering::Relaxed)),
|
steps_left: AtomicI32::new(self.steps_left.load(Ordering::Relaxed)),
|
||||||
step_size: AtomicF32::new(self.step_size.load(Ordering::Relaxed)),
|
step_size: AtomicF32::new(self.step_size.load(Ordering::Relaxed)),
|
||||||
current: AtomicF32::new(self.current.load(Ordering::Relaxed)),
|
current: AtomicF32::new(self.current.load(Ordering::Relaxed)),
|
||||||
|
|
|
@ -27,7 +27,7 @@ pub use crate::midi::{control_change, MidiConfig, NoteEvent, PluginNoteEvent};
|
||||||
pub use crate::params::enums::{Enum, EnumParam};
|
pub use crate::params::enums::{Enum, EnumParam};
|
||||||
pub use crate::params::internals::ParamPtr;
|
pub use crate::params::internals::ParamPtr;
|
||||||
pub use crate::params::range::{FloatRange, IntRange};
|
pub use crate::params::range::{FloatRange, IntRange};
|
||||||
pub use crate::params::smoothing::{Smoothable, Smoother, SmoothingStyle};
|
pub use crate::params::smoothing::{AtomicF32, Smoothable, Smoother, SmoothingStyle};
|
||||||
pub use crate::params::Params;
|
pub use crate::params::Params;
|
||||||
pub use crate::params::{BoolParam, FloatParam, IntParam, Param, ParamFlags};
|
pub use crate::params::{BoolParam, FloatParam, IntParam, Param, ParamFlags};
|
||||||
#[cfg(feature = "vst3")]
|
#[cfg(feature = "vst3")]
|
||||||
|
|
Loading…
Reference in a new issue