1
0
Fork 0

Add logarithmic smoothing

This commit is contained in:
Robbert van der Helm 2022-02-02 23:00:17 +01:00
parent 1ad477ee4f
commit 2ca54d220d
2 changed files with 57 additions and 4 deletions

View file

@ -57,7 +57,7 @@ impl Default for GainParams {
Self { Self {
gain: FloatParam { gain: FloatParam {
value: 0.0, value: 0.0,
smoothed: Smoother::new(SmoothingStyle::Linear(3.0)), smoothed: Smoother::new(SmoothingStyle::Logarithmic(1000.0)),
value_changed: None, value_changed: None,
// If, for instance, updating this parameter would require other parts of the // If, for instance, updating this parameter would require other parts of the
// plugin's internal state to be updated other values to also be updated, then you // plugin's internal state to be updated other values to also be updated, then you

View file

@ -19,8 +19,14 @@ pub enum 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,
/// Smooth parameter changes so the . /// Smooth parameter changes so the current value approaches the target value at a constant
/// rate.
Linear(f32), Linear(f32),
/// Smooth parameter changes such that the rate matches the curve of a logarithmic function.
/// This is useful for smoothing things like frequencies and decibel gain value. **The caveat is
/// that the value may never reach 0**, or you will end up multiplying and dividing things by
/// zero. Make sure your value ranges don't include 0.
Logarithmic(f32),
// TODO: Sample-accurate modes // TODO: Sample-accurate modes
} }
@ -79,11 +85,19 @@ impl Smoother<f32> {
} else { } else {
self.steps_left = match self.style { self.steps_left = match self.style {
SmoothingStyle::None => 1, SmoothingStyle::None => 1,
SmoothingStyle::Linear(time) => (sample_rate * time / 1000.0).round() as u32, SmoothingStyle::Linear(time) | SmoothingStyle::Logarithmic(time) => {
(sample_rate * time / 1000.0).round() as u32
}
}; };
self.step_size = match self.style { self.step_size = match self.style {
SmoothingStyle::None => 0.0, SmoothingStyle::None => 0.0,
SmoothingStyle::Linear(_) => (self.target - self.current) / self.steps_left as f32, SmoothingStyle::Linear(_) => (self.target - self.current) / self.steps_left as f32,
SmoothingStyle::Logarithmic(_) => {
// We need to solve `current * (step_size ^ steps_left) = target` for
// `step_size`
nih_debug_assert_ne!(self.current, 0.0);
(self.target / self.current).powf((self.steps_left as f32).recip())
}
}; };
} }
} }
@ -101,6 +115,7 @@ impl Smoother<f32> {
match &self.style { match &self.style {
SmoothingStyle::None => self.current = self.target, SmoothingStyle::None => self.current = self.target,
SmoothingStyle::Linear(_) => self.current += self.step_size, SmoothingStyle::Linear(_) => self.current += self.step_size,
SmoothingStyle::Logarithmic(_) => self.current *= self.step_size,
}; };
} }
@ -120,13 +135,19 @@ impl Smoother<i32> {
} else { } else {
self.steps_left = match self.style { self.steps_left = match self.style {
SmoothingStyle::None => 1, SmoothingStyle::None => 1,
SmoothingStyle::Linear(time) => (sample_rate * time / 1000.0).round() as u32, SmoothingStyle::Linear(time) | SmoothingStyle::Logarithmic(time) => {
(sample_rate * time / 1000.0).round() as u32
}
}; };
self.step_size = match self.style { self.step_size = match self.style {
SmoothingStyle::None => 0.0, SmoothingStyle::None => 0.0,
SmoothingStyle::Linear(_) => { SmoothingStyle::Linear(_) => {
(self.target as f32 - self.current) / self.steps_left as f32 (self.target as f32 - self.current) / self.steps_left as f32
} }
SmoothingStyle::Logarithmic(_) => {
nih_debug_assert_ne!(self.current, 0.0);
(self.target as f32 / self.current).powf((self.steps_left as f32).recip())
}
}; };
} }
} }
@ -141,6 +162,7 @@ impl Smoother<i32> {
match &self.style { match &self.style {
SmoothingStyle::None => self.current = self.target as f32, SmoothingStyle::None => self.current = self.target as f32,
SmoothingStyle::Linear(_) => self.current += self.step_size, SmoothingStyle::Linear(_) => self.current += self.step_size,
SmoothingStyle::Logarithmic(_) => self.current *= self.step_size,
}; };
} }
@ -185,4 +207,35 @@ mod tests {
assert_ne!(smoother.next(), 20); assert_ne!(smoother.next(), 20);
assert_eq!(smoother.next(), 20); assert_eq!(smoother.next(), 20);
} }
#[test]
fn logarithmic_f32_smoothing() {
let mut smoother: Smoother<f32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
smoother.set_target(100.0, 10.0, true);
assert_eq!(smoother.next(), 10.0);
// Instead of testing the actual values, we'll make sure that we reach the target values at
// the expected time.
smoother.set_target(100.0, 20.0, false);
for _ in 0..(10 - 2) {
dbg!(smoother.next());
}
assert_ne!(smoother.next(), 20.0);
assert_eq!(smoother.next(), 20.0);
}
#[test]
fn logarithmic_i32_smoothing() {
let mut smoother: Smoother<i32> = Smoother::new(SmoothingStyle::Logarithmic(100.0));
smoother.set_target(100.0, 10, true);
assert_eq!(smoother.next(), 10);
// Integers are rounded, but with these values we can still test this
smoother.set_target(100.0, 20, false);
for _ in 0..(10 - 2) {
dbg!(smoother.next());
}
assert_ne!(smoother.next(), 20);
assert_eq!(smoother.next(), 20);
}
} }