embedded_audio/synth/
tone.rs1use 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#[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 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}