ruffbox_synth/building_blocks/oscillators/
fm_tri.rs

1use crate::building_blocks::{
2    Modulator, MonoSource, SampleBuffer, SynthParameterLabel, SynthParameterValue,
3};
4
5use std::f32::consts::PI;
6
7/**
8 * A quasi-bandlimited triangle oscillator using fm synthesis, following:
9 *
10 * Peter Schoffhauzer - Synthesis of Quasi-Bandliminted Analog Waveforms
11 * Using Frequency Modulation
12 *
13 * http://scp.web.elte.hu/papers/synthesis1.pdf
14 */
15#[derive(Clone)]
16pub struct FMTri<const BUFSIZE: usize> {
17    // user parameters
18    freq: f32,
19    amp: f32,
20
21    // internal parameters
22    samplerate: f32,
23    osc1: f32,    // current output sample
24    osc2: f32,    // current output sample
25    phase: f32,   // phase accumulator
26    w: f32,       // normalized frequency
27    scaling: f32, // scaling amount
28    dc_comp: f32, // DC compensation
29    norm: f32,    // normalization
30
31    // pre-calculated filter constants
32    del: f32, // filter delay
33
34    // modulator slots
35    freq_mod: Option<Modulator<BUFSIZE>>, // allows modulating frequency ..
36    amp_mod: Option<Modulator<BUFSIZE>>,  // and level
37}
38
39impl<const BUFSIZE: usize> FMTri<BUFSIZE> {
40    pub fn new(freq: f32, amp: f32, samplerate: f32) -> Self {
41        let w: f32 = freq / samplerate;
42        let n: f32 = 0.5 - w;
43        FMTri {
44            freq,
45            amp,
46            samplerate,
47            osc1: 0.0,                           // current output sample
48            osc2: 0.0,                           // current output sample
49            phase: 0.0,                          // phase accumulator
50            w,                                   // normalized frequency
51            scaling: 13.0 * n * n * n * n * 0.5, // scaling amount
52            dc_comp: 0.11 + w * 0.2,             // DC compensation
53            norm: 1.0 - 2.0 * w,                 // normalization
54            // pre-calculated filter constants
55            del: 0.0, // filter delay
56            freq_mod: None,
57            amp_mod: None,
58        }
59    }
60
61    #[inline(always)]
62    pub fn update_internals(&mut self, freq: f32) {
63        self.w = freq / self.samplerate;
64        let n: f32 = 0.5 - self.w;
65        self.scaling = 13.0 * n * n * n * n * 0.5;
66        self.dc_comp = 0.11 + self.w * 0.2;
67        self.norm = 1.0 - 2.0 * self.w;
68    }
69}
70
71impl<const BUFSIZE: usize> MonoSource<BUFSIZE> for FMTri<BUFSIZE> {
72    fn reset(&mut self) {}
73
74    fn set_modulator(
75        &mut self,
76        par: SynthParameterLabel,
77        init: f32,
78        modulator: Modulator<BUFSIZE>,
79    ) {
80        match par {
81            SynthParameterLabel::PitchFrequency => {
82                self.freq = init;
83                self.freq_mod = Some(modulator);
84            }
85            SynthParameterLabel::OscillatorAmplitude => {
86                self.amp = init;
87                self.amp_mod = Some(modulator);
88            }
89            _ => {}
90        }
91    }
92
93    // some parameter limits might be nice ...
94    fn set_parameter(&mut self, par: SynthParameterLabel, value: &SynthParameterValue) {
95        match par {
96            SynthParameterLabel::PitchFrequency => {
97                if let SynthParameterValue::ScalarF32(f) = value {
98                    self.freq = *f;
99                    self.update_internals(*f);
100                }
101            }
102            SynthParameterLabel::OscillatorAmplitude => {
103                if let SynthParameterValue::ScalarF32(l) = value {
104                    self.amp = *l;
105                }
106            }
107            _ => (),
108        };
109    }
110
111    fn finish(&mut self) {}
112
113    fn is_finished(&self) -> bool {
114        false
115    }
116
117    fn get_next_block(
118        &mut self,
119        start_sample: usize,
120        in_buffers: &[SampleBuffer],
121    ) -> [f32; BUFSIZE] {
122        let mut out_buf: [f32; BUFSIZE] = [0.0; BUFSIZE];
123
124        if self.freq_mod.is_some() || self.amp_mod.is_some() {
125            let amp_buf = if let Some(m) = self.amp_mod.as_mut() {
126                m.process(self.amp, start_sample, in_buffers)
127            } else {
128                [self.amp; BUFSIZE]
129            };
130
131            let freq_buf = if let Some(m) = self.freq_mod.as_mut() {
132                m.process(self.freq, start_sample, in_buffers)
133            } else {
134                [self.freq; BUFSIZE]
135            };
136
137            for (i, current_sample) in out_buf
138                .iter_mut()
139                .enumerate()
140                .take(BUFSIZE)
141                .skip(start_sample)
142            {
143                self.update_internals(freq_buf[i]);
144
145                self.phase += 2.0 * self.w;
146                if self.phase >= 1.0 {
147                    self.phase -= 2.0;
148                }
149
150                self.osc1 =
151                    (self.osc1 + (PI * (self.phase + self.scaling * self.osc1)).sin()) * 0.5;
152                self.osc2 = (self.osc2
153                    + (PI * ((self.phase + 0.25) + self.scaling * self.osc2)).sin())
154                    * 0.5;
155
156                let min = f32::min(self.osc1, -self.osc2);
157                let o = 2.5 * min - 1.5 * self.del;
158                self.del = min;
159
160                *current_sample = (((o + 0.5) * 2.0) - self.dc_comp) * self.norm * amp_buf[i];
161            }
162        } else {
163            for current_sample in out_buf.iter_mut().take(BUFSIZE).skip(start_sample) {
164                // phase accum
165
166                self.phase += 2.0 * self.w;
167                if self.phase >= 1.0 {
168                    self.phase -= 2.0;
169                }
170
171                self.osc1 =
172                    (self.osc1 + (PI * (self.phase + self.scaling * self.osc1)).sin()) * 0.5;
173                self.osc2 = (self.osc2
174                    + (PI * ((self.phase + 0.25) + self.scaling * self.osc2)).sin())
175                    * 0.5;
176
177                let min = f32::min(self.osc1, -self.osc2);
178                let o = 2.5 * min - 1.5 * self.del;
179                self.del = min;
180
181                *current_sample = (((o + 0.5) * 2.0) - self.dc_comp) * self.norm * self.amp;
182            }
183        }
184
185        out_buf
186    }
187}