Skip to main content

embedded_audio/synth/
tone.rs

1use crate::envelope::AdsrSpec;
2use crate::fixed::{Phase, hz_to_phase_inc, sin_table};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Waveform {
6    Square,
7    Sine,
8    Triangle,
9}
10
11#[derive(Debug, Clone, Copy)]
12pub struct ToneParams {
13    pub freq_hz: u32,
14    pub duration_ms: u16,
15    pub waveform: Waveform,
16    pub adsr: AdsrSpec,
17}
18
19/// Tier A tone oscillator with optional auto-stop by duration.
20#[derive(Debug, Clone, Copy)]
21pub struct ToneVoice {
22    phase: Phase,
23    phase_inc: u32,
24    waveform: Waveform,
25    samples_left: u32,
26    active: bool,
27}
28
29impl Default for ToneVoice {
30    fn default() -> Self {
31        Self::new()
32    }
33}
34
35impl ToneVoice {
36    pub const fn new() -> Self {
37        Self {
38            phase: 0,
39            phase_inc: 0,
40            waveform: Waveform::Sine,
41            samples_left: 0,
42            active: false,
43        }
44    }
45
46    pub fn start(&mut self, params: ToneParams, sample_rate_hz: u32) {
47        self.phase = 0;
48        self.phase_inc = hz_to_phase_inc(params.freq_hz, sample_rate_hz);
49        self.waveform = params.waveform;
50        self.samples_left = if params.duration_ms == 0 {
51            u32::MAX
52        } else {
53            (params.duration_ms as u32 * sample_rate_hz) / 1000
54        };
55        self.active = true;
56    }
57
58    pub fn stop(&mut self) {
59        self.active = false;
60        self.samples_left = 0;
61    }
62
63    pub fn is_active(&self) -> bool {
64        self.active
65    }
66
67    /// Carrier in Hz derived from the phase increment (for FM buzzer backends).
68    pub const fn carrier_hz(&self, sample_rate_hz: u32) -> u32 {
69        if sample_rate_hz == 0 || !self.active {
70            0
71        } else {
72            (((self.phase_inc as u64) * (sample_rate_hz as u64)) >> 32) as u32
73        }
74    }
75
76    pub fn next_sample(&mut self) -> Option<i8> {
77        if !self.active {
78            return None;
79        }
80        if self.samples_left == 0 {
81            self.active = false;
82            return None;
83        }
84        if self.samples_left != u32::MAX {
85            self.samples_left -= 1;
86        }
87        let sample = match self.waveform {
88            Waveform::Square => {
89                if self.phase < 0x8000_0000 {
90                    127
91                } else {
92                    -127
93                }
94            }
95            Waveform::Sine => sin_table((self.phase >> 24) as u8),
96            Waveform::Triangle => {
97                let idx = (self.phase >> 24) as u8;
98                let tri = if idx < 128 {
99                    (idx as i32 * 2) - 127
100                } else {
101                    127 - ((idx - 128) as i32 * 2)
102                };
103                tri.clamp(-127, 127) as i8
104            }
105        };
106        self.phase = self.phase.wrapping_add(self.phase_inc);
107        Some(sample)
108    }
109}