Skip to main content

embedded_audio/synth/
wavetable.rs

1use crate::fixed::{Phase, hz_to_phase_inc, lerp_i8, phase_index};
2
3/// Tier A wavetable oscillator (256-byte table, linear interpolation).
4#[derive(Debug, Clone, Copy)]
5pub struct WavetableVoice<'a> {
6    table: &'a [u8],
7    phase: Phase,
8    phase_inc: u32,
9    active: bool,
10}
11
12impl<'a> WavetableVoice<'a> {
13    /// `table` must hold at least 256 samples (unsigned 8-bit, centered at 128).
14    pub const fn new(table: &'a [u8]) -> Self {
15        Self {
16            table,
17            phase: 0,
18            phase_inc: 0,
19            active: false,
20        }
21    }
22
23    pub const fn sine() -> Self {
24        Self::new(&SINE_TABLE)
25    }
26
27    pub const fn triangle() -> Self {
28        Self::new(&TRIANGLE_TABLE)
29    }
30
31    pub const fn saw() -> Self {
32        Self::new(&SAW_TABLE)
33    }
34
35    pub const fn square() -> Self {
36        Self::new(&SQUARE_TABLE)
37    }
38
39    pub fn start(&mut self, freq_hz: u32, sample_rate_hz: u32) {
40        self.phase = 0;
41        self.phase_inc = hz_to_phase_inc(freq_hz, sample_rate_hz);
42        self.active = true;
43    }
44
45    pub fn stop(&mut self) {
46        self.active = false;
47    }
48
49    pub fn is_active(&self) -> bool {
50        self.active
51    }
52
53    pub fn next_sample(&mut self) -> Option<i8> {
54        if !self.active || self.table.len() < 256 {
55            return None;
56        }
57        let idx = phase_index(self.phase);
58        let frac = (self.phase >> 16) as u8;
59        let a = self.table[idx as usize] as i8;
60        let b = self.table[idx.wrapping_add(1) as usize] as i8;
61        let sample = lerp_i8(a, b, frac);
62        self.phase = self.phase.wrapping_add(self.phase_inc);
63        Some(sample)
64    }
65}
66
67/// Built-in 256-sample sine-ish wavetable for FM and defaults.
68pub static SINE_TABLE: [u8; 256] = generate_sine_table();
69/// Built-in 256-sample triangle wavetable.
70pub static TRIANGLE_TABLE: [u8; 256] = generate_triangle_table();
71/// Built-in 256-sample sawtooth wavetable.
72pub static SAW_TABLE: [u8; 256] = generate_saw_table();
73/// Built-in 256-sample 50% square wavetable.
74pub static SQUARE_TABLE: [u8; 256] = generate_square_table();
75/// Built-in 256-sample 25% pulse wavetable.
76pub static PULSE_25_TABLE: [u8; 256] = generate_pulse_25_table();
77
78const fn generate_sine_table() -> [u8; 256] {
79    let mut t = [0u8; 256];
80    let mut i = 0;
81    while i < 256 {
82        let x = i as i32;
83        let y = if x < 128 { x * 2 } else { (255 - x) * 2 };
84        let quad = (y * (255 - y)) >> 8;
85        let s = if x < 128 { quad } else { -quad };
86        let u = s + 128;
87        t[i] = if u < 0 {
88            0
89        } else if u > 255 {
90            255
91        } else {
92            u as u8
93        };
94        i += 1;
95    }
96    t
97}
98
99const fn generate_triangle_table() -> [u8; 256] {
100    let mut t = [0u8; 256];
101    let mut i = 0;
102    while i < 256 {
103        let val = if i < 64 {
104            128 + i * 2
105        } else if i < 192 {
106            255 - (i - 64) * 2
107        } else {
108            (i - 192) * 2
109        };
110        t[i] = val as u8;
111        i += 1;
112    }
113    t
114}
115
116const fn generate_saw_table() -> [u8; 256] {
117    let mut t = [0u8; 256];
118    let mut i = 0;
119    while i < 256 {
120        t[i] = i as u8;
121        i += 1;
122    }
123    t
124}
125
126const fn generate_square_table() -> [u8; 256] {
127    let mut t = [0u8; 256];
128    let mut i = 0;
129    while i < 256 {
130        t[i] = if i < 128 { 255 } else { 0 };
131        i += 1;
132    }
133    t
134}
135
136const fn generate_pulse_25_table() -> [u8; 256] {
137    let mut t = [0u8; 256];
138    let mut i = 0;
139    while i < 256 {
140        t[i] = if i < 64 { 255 } else { 0 };
141        i += 1;
142    }
143    t
144}
145
146/// Generate a 256-sample wavetable from a signed 8-bit sample mapping closure (-128..=127 -> 0..=255).
147pub fn generate_wavetable_fixed<F: Fn(u8) -> i8>(f: F) -> [u8; 256] {
148    let mut table = [0u8; 256];
149    for (i, slot) in table.iter_mut().enumerate() {
150        let sample = f(i as u8);
151        *slot = (sample as i16 + 128).clamp(0, 255) as u8;
152    }
153    table
154}