Skip to main content

alpha2_audio/
crossfade.rs

1use core::f32::consts::FRAC_PI_2;
2
3use crate::{ProcessCtx, Processor, StateBlob, StateError};
4
5/// Equal-power crossfade between two planar input buses.
6#[derive(Copy, Clone, Debug, PartialEq)]
7pub struct EqualPowerCrossfade {
8    progress: f32,
9}
10
11impl EqualPowerCrossfade {
12    /// Creates a crossfade with progress clamped to the unit interval.
13    #[must_use]
14    pub fn new(progress: f32) -> Self {
15        Self {
16            progress: if progress.is_finite() {
17                progress.clamp(0.0, 1.0)
18            } else {
19                0.0
20            },
21        }
22    }
23
24    /// Updates crossfade progress, clamped and sanitized as in [`Self::new`].
25    pub fn set_progress(&mut self, progress: f32) {
26        *self = Self::new(progress);
27    }
28}
29
30impl Processor for EqualPowerCrossfade {
31    /// The bus faded from, then the bus faded to.
32    fn inputs_per_output(&self) -> usize {
33        2
34    }
35
36    fn prepare(&mut self, _sample_rate: u32, _max_block: usize) {}
37
38    fn process(&mut self, _ctx: &ProcessCtx, inputs: &[&[f32]], outputs: &mut [&mut [f32]]) {
39        let channels = outputs.len();
40        if inputs.len() < channels.saturating_mul(2) {
41            return;
42        }
43        let (from_gain, to_gain) = match self.progress {
44            0.0 => (1.0, 0.0),
45            1.0 => (0.0, 1.0),
46            progress => (
47                libm::cosf(progress * FRAC_PI_2),
48                libm::sinf(progress * FRAC_PI_2),
49            ),
50        };
51        for channel in 0..channels {
52            let from = inputs[channel];
53            let to = inputs[channel + channels];
54            for ((from, to), output) in from.iter().zip(to).zip(outputs[channel].iter_mut()) {
55                *output = from * from_gain + *to * to_gain;
56            }
57        }
58    }
59
60    fn reset(&mut self) {}
61
62    fn save_state(&self) -> StateBlob {
63        StateBlob::new(Vec::new())
64    }
65
66    fn load_state(&mut self, state: &StateBlob) -> Result<(), StateError> {
67        if state.as_bytes().is_empty() {
68            Ok(())
69        } else {
70            Err(StateError::Mismatch)
71        }
72    }
73}