diff --git a/plugins/gain/src/lib.rs b/plugins/gain/src/lib.rs index 8c1adbf2..0c021f45 100644 --- a/plugins/gain/src/lib.rs +++ b/plugins/gain/src/lib.rs @@ -57,7 +57,7 @@ impl Default for GainParams { Self { gain: FloatParam { value: 0.0, - smoothed: Smoother::new(SmoothingStyle::Linear(3.0)), + smoothed: Smoother::new(SmoothingStyle::Logarithmic(1000.0)), value_changed: None, // 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 diff --git a/src/param/smoothing.rs b/src/param/smoothing.rs index fdc9cf02..6bbfe39b 100644 --- a/src/param/smoothing.rs +++ b/src/param/smoothing.rs @@ -19,8 +19,14 @@ pub enum SmoothingStyle { /// No smoothing is applied. The parameter's `value` field contains the latest sample value /// available for the parameters. None, - /// Smooth parameter changes so the . + /// Smooth parameter changes so the current value approaches the target value at a constant + /// rate. 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 } @@ -79,11 +85,19 @@ impl Smoother { } else { self.steps_left = match self.style { 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 { SmoothingStyle::None => 0.0, 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 { match &self.style { SmoothingStyle::None => self.current = self.target, SmoothingStyle::Linear(_) => self.current += self.step_size, + SmoothingStyle::Logarithmic(_) => self.current *= self.step_size, }; } @@ -120,13 +135,19 @@ impl Smoother { } else { self.steps_left = match self.style { 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 { SmoothingStyle::None => 0.0, SmoothingStyle::Linear(_) => { (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 { match &self.style { SmoothingStyle::None => self.current = self.target as f32, 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_eq!(smoother.next(), 20); } + + #[test] + fn logarithmic_f32_smoothing() { + let mut smoother: Smoother = 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 = 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); + } }