alpha2_audio/
automation.rs1#[derive(Copy, Clone, Debug, PartialEq)]
3pub struct AutomationPoint {
4 pub sample: i64,
6 pub value: f32,
8}
9
10#[derive(Clone, Debug)]
12pub struct AutomatedGain {
13 curve: AutomationCurve,
14 fallback: f32,
15}
16
17impl AutomatedGain {
18 #[must_use]
23 pub fn new(curve: AutomationCurve, fallback: f32) -> Self {
24 Self {
25 curve,
26 fallback: if fallback.is_finite() { fallback } else { 0.0 },
27 }
28 }
29}
30
31impl Processor for AutomatedGain {
32 fn prepare(&mut self, _sample_rate: u32, _max_block: usize) {}
33 fn process(&mut self, ctx: &ProcessCtx, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
34 for channel in 0..inputs.len().min(outputs.len()) {
35 for (index, (&input, output)) in inputs[channel]
36 .iter()
37 .zip(outputs[channel].iter_mut())
38 .enumerate()
39 {
40 let sample = ctx
41 .sample_position
42 .saturating_add(i64::try_from(index).unwrap_or(i64::MAX));
43 *output = input * self.curve.value_at(sample, self.fallback);
44 }
45 }
46 }
47 fn reset(&mut self) {}
48 fn save_state(&self) -> StateBlob {
49 StateBlob::default()
50 }
51 fn load_state(&mut self, state: &StateBlob) -> Result<(), StateError> {
52 if state.as_bytes().is_empty() {
53 Ok(())
54 } else {
55 Err(StateError::Mismatch)
56 }
57 }
58}
59
60#[derive(Clone, Debug, Default, PartialEq)]
62pub struct AutomationCurve {
63 points: Vec<AutomationPoint>,
64}
65
66impl AutomationCurve {
67 pub fn new(mut points: Vec<AutomationPoint>) -> Result<Self, &'static str> {
72 if points.iter().any(|point| !point.value.is_finite()) {
73 return Err("automation values must be finite");
74 }
75 points.sort_by_key(|point| point.sample);
76 if points
77 .windows(2)
78 .any(|pair| pair[0].sample == pair[1].sample)
79 {
80 return Err("automation positions must be unique");
81 }
82 Ok(Self { points })
83 }
84
85 #[must_use]
90 pub fn value_at(&self, sample: i64, fallback: f32) -> f32 {
91 let index = self.points.partition_point(|point| point.sample <= sample);
92 match index {
93 0 => self.points.first().map_or(fallback, |point| point.value),
94 index if index == self.points.len() => self.points[index - 1].value,
95 index => {
96 let left = self.points[index - 1];
97 let right = self.points[index];
98 #[allow(clippy::cast_precision_loss)]
99 let t = (i128::from(sample) - i128::from(left.sample)) as f64
100 / (i128::from(right.sample) - i128::from(left.sample)) as f64;
101 #[allow(clippy::cast_possible_truncation)]
102 let value = (f64::from(left.value)
103 + (f64::from(right.value) - f64::from(left.value)) * t)
104 as f32;
105 value
106 }
107 }
108 }
109}
110use crate::{ProcessCtx, Processor, StateBlob, StateError};