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

1use crate::engine::audio::effects::processors::super_trait::EffectProcessor;
2
3#[derive(Debug, Clone)]
4pub struct StretchProcessor {
5    pub factor: f32, // 0.25 .. 4.0
6    pub pitch: f32,  // semitones
7    pub formant: bool,
8}
9
10impl StretchProcessor {
11    pub fn new(factor: f32, pitch: f32, formant: bool) -> Self {
12        Self {
13            factor: factor.clamp(0.25, 4.0),
14            pitch: pitch.clamp(-48.0, 48.0),
15            formant,
16        }
17    }
18}
19
20impl Default for StretchProcessor {
21    fn default() -> Self {
22        Self::new(1.0, 0.0, false)
23    }
24}
25
26impl EffectProcessor for StretchProcessor {
27    fn process(&mut self, samples: &mut [f32], _sr: u32) {
28        // naive time-stretch by resampling: when factor>1 we speed up (shorter), <1 we stretch
29        let frames = samples.len() / 2;
30        if self.factor == 1.0 {
31            return;
32        }
33        let mut out = vec![0.0f32; samples.len()];
34        for i in 0..frames {
35            let src_f = (i as f32) / self.factor;
36            let idx = src_f.floor() as usize;
37            let frac = src_f - idx as f32;
38
39            let a_l = samples.get(idx * 2).copied().unwrap_or(0.0);
40            let b_l = samples.get((idx + 1) * 2).copied().unwrap_or(a_l);
41            let val_l = a_l * (1.0 - frac) + b_l * frac;
42            out[i * 2] = val_l;
43            let a_r = samples.get(idx * 2 + 1).copied().unwrap_or(a_l);
44            let b_r = samples.get((idx + 1) * 2 + 1).copied().unwrap_or(a_r);
45            let val_r = a_r * (1.0 - frac) + b_r * frac;
46            out[i * 2 + 1] = val_r;
47        }
48        samples.copy_from_slice(&out);
49    }
50    fn reset(&mut self) {}
51    fn name(&self) -> &str {
52        "Stretch"
53    }
54}