#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Smoother {
weight: f32,
value: Option<f32>,
}
impl Smoother {
pub fn new(weight: f32) -> Self {
Self {
weight: unit_interval(weight),
value: None,
}
}
pub fn update(&mut self, sample: f32) -> f32 {
let smoothed = match self.value {
Some(previous) => self.weight * sample + (1.0 - self.weight) * previous,
None => sample,
};
self.value = Some(smoothed);
smoothed
}
pub fn value(&self) -> Option<f32> {
self.value
}
pub fn reset(&mut self) {
self.value = None;
}
}
#[allow(clippy::manual_clamp)]
fn unit_interval(value: f32) -> f32 {
if value < 0.0 {
0.0
} else if value > 1.0 {
1.0
} else {
value
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn first_sample_seeds_the_average() {
let mut smoother = Smoother::new(0.5);
assert_eq!(smoother.update(10.0), 10.0);
assert_eq!(smoother.value(), Some(10.0));
}
#[test]
fn blends_toward_newer_samples() {
let mut smoother = Smoother::new(0.5);
smoother.update(0.0);
assert!((smoother.update(10.0) - 5.0).abs() < 1e-6);
assert!((smoother.update(10.0) - 7.5).abs() < 1e-6);
}
#[test]
fn weight_is_clamped_into_the_unit_interval() {
let mut smoother = Smoother::new(2.0); smoother.update(1.0);
assert!((smoother.update(9.0) - 9.0).abs() < 1e-6);
}
#[test]
fn reset_forgets_the_value() {
let mut smoother = Smoother::new(0.5);
smoother.update(4.0);
smoother.reset();
assert_eq!(smoother.value(), None);
assert_eq!(smoother.update(8.0), 8.0);
}
}