embedded_audio/synth/
fm.rs1use crate::fixed::{Phase, hz_to_phase_inc, lerp_i8, phase_index};
2use crate::synth::wavetable::SINE_TABLE;
3
4#[derive(Debug, Clone, Copy)]
6pub struct FmVoice {
7 carrier_phase: Phase,
8 carrier_inc: u32,
9 mod_phase: Phase,
10 mod_inc: u32,
11 mod_depth_q8: u8,
12 active: bool,
13}
14
15impl Default for FmVoice {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl FmVoice {
22 pub const fn new() -> Self {
23 Self {
24 carrier_phase: 0,
25 carrier_inc: 0,
26 mod_phase: 0,
27 mod_inc: 0,
28 mod_depth_q8: 64,
29 active: false,
30 }
31 }
32
33 pub fn start(
35 &mut self,
36 carrier_hz: u32,
37 mod_ratio_cent: u16,
38 mod_depth_q8: u8,
39 sample_rate_hz: u32,
40 ) {
41 self.carrier_phase = 0;
42 self.mod_phase = 0;
43 self.carrier_inc = hz_to_phase_inc(carrier_hz, sample_rate_hz);
44 let mod_hz = (carrier_hz as u64 * mod_ratio_cent as u64) / 100;
45 self.mod_inc = hz_to_phase_inc(mod_hz as u32, sample_rate_hz);
46 self.mod_depth_q8 = mod_depth_q8;
47 self.active = true;
48 }
49
50 pub fn stop(&mut self) {
51 self.active = false;
52 }
53
54 pub fn is_active(&self) -> bool {
55 self.active
56 }
57
58 pub fn next_sample(&mut self) -> Option<i8> {
59 if !self.active {
60 return None;
61 }
62 let mod_idx = phase_index(self.mod_phase);
63 let mod_frac = (self.mod_phase >> 16) as u8;
64 let ma = SINE_TABLE[mod_idx as usize] as i8;
65 let mb = SINE_TABLE[mod_idx.wrapping_add(1) as usize] as i8;
66 let mod_sample = lerp_i8(ma, mb, mod_frac);
67
68 let mod_offset = ((mod_sample as i32 * self.mod_depth_q8 as i32) >> 8) as u8;
69 let car_idx = phase_index(self.carrier_phase.wrapping_add((mod_offset as u32) << 24));
70 let car_frac = (self.carrier_phase >> 16) as u8;
71 let ca = SINE_TABLE[car_idx as usize] as i8;
72 let cb = SINE_TABLE[car_idx.wrapping_add(1) as usize] as i8;
73 let sample = lerp_i8(ca, cb, car_frac);
74
75 self.carrier_phase = self.carrier_phase.wrapping_add(self.carrier_inc);
76 self.mod_phase = self.mod_phase.wrapping_add(self.mod_inc);
77 Some(sample)
78 }
79}