#[derive(Debug, Clone)]
pub struct Automation {
points: Vec<(f32, f32)>,
interpolation: Interpolation,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Interpolation {
Step,
Linear,
Smooth,
}
impl Automation {
pub fn linear(points: &[(f32, f32)]) -> Self {
Self::new(points, Interpolation::Linear)
}
pub fn smooth(points: &[(f32, f32)]) -> Self {
Self::new(points, Interpolation::Smooth)
}
pub fn steps(points: &[(f32, f32)]) -> Self {
Self::new(points, Interpolation::Step)
}
pub fn constant(value: f32) -> Self {
Self::new(&[(0.0, value)], Interpolation::Step)
}
fn new(points: &[(f32, f32)], interpolation: Interpolation) -> Self {
assert!(
!points.is_empty(),
"Automation must have at least one point"
);
let mut sorted_points = points.to_vec();
sorted_points.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
Self {
points: sorted_points,
interpolation,
}
}
#[inline]
pub fn value_at(&self, time: f32) -> f32 {
if self.points.is_empty() {
return 0.0;
}
if self.points.len() == 1 {
return self.points[0].1;
}
if time <= self.points[0].0 {
return self.points[0].1;
}
if time >= self.points[self.points.len() - 1].0 {
return self.points[self.points.len() - 1].1;
}
let idx = self.points.partition_point(|&(t, _)| t < time);
let p1 = self.points[idx - 1];
let p2 = self.points[idx];
let t = (time - p1.0) / (p2.0 - p1.0);
match self.interpolation {
Interpolation::Step => {
if (time - p2.0).abs() < 0.00001 {
p2.1
} else {
p1.1
}
}
Interpolation::Linear => {
p1.1 + (p2.1 - p1.1) * t
}
Interpolation::Smooth => {
let smooth_t = t * t * (3.0 - 2.0 * t);
p1.1 + (p2.1 - p1.1) * smooth_t
}
}
}
pub fn time_range(&self) -> (f32, f32) {
if self.points.is_empty() {
return (0.0, 0.0);
}
(self.points[0].0, self.points[self.points.len() - 1].0)
}
pub fn value_range(&self) -> (f32, f32) {
if self.points.is_empty() {
return (0.0, 0.0);
}
let mut min = self.points[0].1;
let mut max = self.points[0].1;
for &(_, value) in &self.points {
min = min.min(value);
max = max.max(value);
}
(min, max)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_linear_interpolation() {
let auto = Automation::linear(&[(0.0, 0.0), (4.0, 1.0)]);
assert_eq!(auto.value_at(0.0), 0.0);
assert_eq!(auto.value_at(2.0), 0.5);
assert_eq!(auto.value_at(4.0), 1.0);
}
#[test]
fn test_step_interpolation() {
let auto = Automation::steps(&[(0.0, 0.0), (2.0, 1.0), (4.0, 0.5)]);
assert_eq!(auto.value_at(0.0), 0.0);
assert_eq!(auto.value_at(1.9), 0.0); assert_eq!(auto.value_at(2.0), 1.0);
assert_eq!(auto.value_at(3.9), 1.0); }
#[test]
fn test_smooth_interpolation() {
let auto = Automation::smooth(&[(0.0, 0.0), (4.0, 1.0)]);
assert_eq!(auto.value_at(0.0), 0.0);
assert_eq!(auto.value_at(4.0), 1.0);
let _linear_mid = 0.5; let smooth_mid = auto.value_at(2.0);
assert_eq!(smooth_mid, 0.5);
let smooth_quarter = auto.value_at(1.0);
assert!(smooth_quarter < 0.25); }
#[test]
fn test_constant() {
let auto = Automation::constant(0.5);
assert_eq!(auto.value_at(0.0), 0.5);
assert_eq!(auto.value_at(100.0), 0.5);
assert_eq!(auto.value_at(-100.0), 0.5);
}
#[test]
fn test_before_first_point() {
let auto = Automation::linear(&[(2.0, 1.0), (4.0, 2.0)]);
assert_eq!(auto.value_at(0.0), 1.0); assert_eq!(auto.value_at(1.0), 1.0);
}
#[test]
fn test_after_last_point() {
let auto = Automation::linear(&[(0.0, 0.0), (2.0, 1.0)]);
assert_eq!(auto.value_at(3.0), 1.0); assert_eq!(auto.value_at(100.0), 1.0);
}
#[test]
fn test_unsorted_points() {
let auto = Automation::linear(&[(4.0, 1.0), (0.0, 0.0), (2.0, 0.5)]);
assert_eq!(auto.value_at(0.0), 0.0);
assert_eq!(auto.value_at(2.0), 0.5);
assert_eq!(auto.value_at(4.0), 1.0);
}
#[test]
fn test_time_range() {
let auto = Automation::linear(&[(1.0, 0.0), (5.0, 1.0)]);
assert_eq!(auto.time_range(), (1.0, 5.0));
}
#[test]
fn test_value_range() {
let auto = Automation::linear(&[(0.0, 0.5), (2.0, -1.0), (4.0, 2.0)]);
assert_eq!(auto.value_range(), (-1.0, 2.0));
}
#[test]
#[should_panic(expected = "Automation must have at least one point")]
fn test_empty_points() {
Automation::linear(&[]);
}
}