Skip to main content

pfx_sound/
synth.rs

1use crate::channel::Ramp;
2use crate::sound::Rng;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub enum Wave {
6    Sine,
7    Square,
8    Saw,
9    White,
10    Pink,
11}
12
13pub(crate) struct Synth {
14    wave: Wave,
15    pub(crate) frequency: Ramp,
16    phase: f64,
17    rng: Rng,
18    pink: [f32; 7],
19}
20
21impl Synth {
22    pub(crate) fn new(wave: Wave, frequency: f32, seed: u64) -> Self {
23        Self {
24            wave,
25            frequency: Ramp::new(frequency.max(0.0)),
26            phase: 0.0,
27            rng: Rng::new(seed),
28            pink: [0.0; 7],
29        }
30    }
31
32    pub(crate) fn next(&mut self, ratio: f32, rate: u32) -> f32 {
33        let rate = f64::from(rate.max(1));
34        let hz = (f64::from(self.frequency.next()) * f64::from(ratio)).clamp(0.0, rate * 0.45);
35        let dt = hz / rate;
36        let phase = self.phase;
37        let sample = match self.wave {
38            Wave::Sine => f64::from(sine(phase)),
39            Wave::White => f64::from(self.rng.signed()),
40            Wave::Pink => f64::from(self.pink_sample()),
41            Wave::Saw => 2.0 * phase - 1.0 - poly_blep(phase, dt),
42            Wave::Square => {
43                let raw = if phase < 0.5 { 1.0 } else { -1.0 };
44                raw + poly_blep(phase, dt) - poly_blep((phase + 0.5).fract(), dt)
45            }
46        };
47        self.phase += dt;
48        if self.phase >= 1.0 {
49            self.phase -= 1.0;
50        }
51        sample as f32
52    }
53}
54
55impl Synth {
56    fn pink_sample(&mut self) -> f32 {
57        let white = self.rng.signed();
58        let b = &mut self.pink;
59        b[0] = 0.99886 * b[0] + white * 0.055_517_9;
60        b[1] = 0.99332 * b[1] + white * 0.075_075_9;
61        b[2] = 0.96900 * b[2] + white * 0.153_852;
62        b[3] = 0.86650 * b[3] + white * 0.310_485_6;
63        b[4] = 0.55000 * b[4] + white * 0.532_952_2;
64        b[5] = -0.7616 * b[5] - white * 0.016_898;
65        let sum = b[0] + b[1] + b[2] + b[3] + b[4] + b[5] + b[6] + white * 0.5362;
66        b[6] = white * 0.115_926;
67        sum * PINK_SCALE
68    }
69}
70
71const PINK_SCALE: f32 = 0.11;
72
73const SINE_STEPS: usize = 2048;
74
75static SINE: [f32; SINE_STEPS + 1] = sine_table();
76
77const fn sine_table() -> [f32; SINE_STEPS + 1] {
78    let mut table = [0.0f32; SINE_STEPS + 1];
79    let mut i = 0;
80    while i <= SINE_STEPS {
81        table[i] = taylor_sine(std::f64::consts::TAU * i as f64 / SINE_STEPS as f64) as f32;
82        i += 1;
83    }
84    table
85}
86
87const fn taylor_sine(x: f64) -> f64 {
88    let pi = std::f64::consts::PI;
89    let x = if x > 1.5 * pi {
90        x - 2.0 * pi
91    } else if x > 0.5 * pi {
92        pi - x
93    } else {
94        x
95    };
96    let square = x * x;
97    let mut term = x;
98    let mut sum = x;
99    let mut n = 1;
100    while n < 14 {
101        term = -term * square / ((2 * n) * (2 * n + 1)) as f64;
102        sum += term;
103        n += 1;
104    }
105    sum
106}
107
108pub(crate) fn sine(phase: f64) -> f32 {
109    let x = phase * SINE_STEPS as f64;
110    let index = (x as usize).min(SINE_STEPS - 1);
111    let frac = (x - index as f64) as f32;
112    let a = SINE[index];
113    a + (SINE[index + 1] - a) * frac
114}
115
116fn poly_blep(t: f64, dt: f64) -> f64 {
117    if dt <= 0.0 {
118        return 0.0;
119    }
120    if t < dt {
121        let x = t / dt;
122        x + x - x * x - 1.0
123    } else if t > 1.0 - dt {
124        let x = (t - 1.0) / dt;
125        x * x + x + x + 1.0
126    } else {
127        0.0
128    }
129}