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

1use crate::engine::audio::effects::processors::super_trait::EffectProcessor;
2
3#[derive(Debug, Clone)]
4pub struct BitcrushProcessor {
5    pub depth: f32,       // bits nominal (1.0..16.0)
6    pub sample_rate: f32, // downsample rate in Hz (e.g. 8000.0)
7    pub mix: f32,
8    sample_pos: usize,
9    hold_step: usize,
10}
11
12impl BitcrushProcessor {
13    pub fn new(depth: f32, sample_rate: f32, mix: f32) -> Self {
14        Self {
15            depth: depth.clamp(1.0, 16.0),
16            sample_rate: sample_rate.max(100.0),
17            mix: mix.clamp(0.0, 1.0),
18            sample_pos: 0,
19            hold_step: 1,
20        }
21    }
22}
23
24impl Default for BitcrushProcessor {
25    fn default() -> Self {
26        Self::new(8.0, 8000.0, 0.5)
27    }
28}
29
30impl EffectProcessor for BitcrushProcessor {
31    fn process(&mut self, samples: &mut [f32], sr: u32) {
32        let sr_f = sr as f32;
33        // compute hold_step in samples
34        let target_rate = self.sample_rate.clamp(100.0, sr_f);
35        let step = (sr_f / target_rate).max(1.0) as usize;
36        self.hold_step = step;
37
38        let levels = (2u32.pow(self.depth as u32)) as f32;
39        let mut hold_l = 0.0f32;
40        let mut hold_r = 0.0f32;
41        for i in (0..samples.len()).step_by(2) {
42            if self.sample_pos % self.hold_step == 0 {
43                // quantize
44                let in_l = samples[i];
45                let ql =
46                    (((in_l + 1.0) * 0.5 * (levels - 1.0)).round() / (levels - 1.0)) * 2.0 - 1.0;
47                hold_l = ql;
48                if i + 1 < samples.len() {
49                    let in_r = samples[i + 1];
50                    let qr = (((in_r + 1.0) * 0.5 * (levels - 1.0)).round() / (levels - 1.0)) * 2.0
51                        - 1.0;
52                    hold_r = qr;
53                }
54            }
55            samples[i] = samples[i] * (1.0 - self.mix) + hold_l * self.mix;
56            if i + 1 < samples.len() {
57                samples[i + 1] = samples[i + 1] * (1.0 - self.mix) + hold_r * self.mix;
58            }
59            self.sample_pos = self.sample_pos.wrapping_add(1);
60        }
61    }
62
63    fn reset(&mut self) {
64        self.sample_pos = 0;
65    }
66
67    fn name(&self) -> &str {
68        "Bitcrush"
69    }
70}