devalang_wasm/engine/audio/effects/processors/
speed.rs

1use crate::engine::audio::effects::processors::super_trait::EffectProcessor;
2
3#[derive(Debug, Clone)]
4pub struct SpeedProcessor {
5    speed: f32,
6    buffer: Vec<f32>,
7}
8
9impl SpeedProcessor {
10    pub fn new(speed: f32) -> Self {
11        // Accept any speed value (positive >1 speeds speed up, negative reverses).
12        // Avoid exact zero which would be invalid; clamp tiny values to a small epsilon
13        let clamped = if speed.abs() < 0.0001 {
14            if speed.is_sign_negative() {
15                -0.0001
16            } else {
17                0.0001
18            }
19        } else {
20            speed
21        };
22
23        Self {
24            speed: clamped,
25            buffer: Vec::new(),
26        }
27    }
28}
29
30impl Default for SpeedProcessor {
31    fn default() -> Self {
32        Self::new(1.0)
33    }
34}
35
36impl EffectProcessor for SpeedProcessor {
37    fn process(&mut self, samples: &mut [f32], _sample_rate: u32) {
38        if (self.speed - 1.0).abs() < f32::EPSILON {
39            return; // No speed change needed
40        }
41
42        let original_len = samples.len();
43        let speed_abs = self.speed.abs();
44        let new_len = (original_len as f32 / speed_abs) as usize;
45
46        // Resize buffer if needed
47        self.buffer.resize(new_len, 0.0);
48
49        // Simple linear interpolation for speed change
50        // For positive speeds, map dest -> src forward. For negative speeds, produce reversed playback.
51        if self.speed > 0.0 {
52            for i in 0..new_len {
53                let src_pos = i as f32 * self.speed;
54                let src_idx = src_pos.floor() as isize;
55                let frac = src_pos.fract();
56
57                if src_idx >= 0 && (src_idx as usize) + 1 < original_len {
58                    let idx = src_idx as usize;
59                    self.buffer[i] = samples[idx] * (1.0 - frac) + samples[idx + 1] * frac;
60                } else if src_idx >= 0 && (src_idx as usize) < original_len {
61                    self.buffer[i] = samples[src_idx as usize];
62                } else {
63                    self.buffer[i] = 0.0;
64                }
65            }
66        } else {
67            // speed < 0: reversed playback at magnitude 'speed_abs'
68            for i in 0..new_len {
69                // map destination index i to source position from the end
70                let src_pos_from_end = i as f32 * speed_abs;
71                // compute source position relative to start: original_len - 1 - src_pos_from_end
72                let src_pos = (original_len as f32 - 1.0) - src_pos_from_end;
73                if src_pos < 0.0 {
74                    self.buffer[i] = 0.0;
75                    continue;
76                }
77                let src_idx = src_pos.floor() as usize;
78                let frac = src_pos.fract();
79
80                if src_idx + 1 < original_len {
81                    self.buffer[i] = samples[src_idx] * (1.0 - frac) + samples[src_idx + 1] * frac;
82                } else if src_idx < original_len {
83                    self.buffer[i] = samples[src_idx];
84                } else {
85                    self.buffer[i] = 0.0;
86                }
87            }
88        }
89
90        // Copy processed samples back into the provided slice.
91        // We cannot change the slice length; copy as many as fit and zero the remainder.
92        let copy_len = std::cmp::min(original_len, new_len);
93        samples[..copy_len].copy_from_slice(&self.buffer[..copy_len]);
94        if new_len < original_len {
95            for s in &mut samples[new_len..original_len] {
96                *s = 0.0;
97            }
98        }
99    }
100
101    fn reset(&mut self) {
102        self.buffer.clear();
103    }
104
105    fn name(&self) -> &str {
106        "Speed"
107    }
108}