Skip to main content

rill_core_dsp/generators/
fm.rs

1//! FM (Frequency Modulation) synthesis
2//!
3//! This module provides tools for frequency modulation:
4//! - Simple 2-operator FM synthesizer
5//! - Multi-operator FM synthesizer (like Yamaha DX7)
6//! - Support for different waveforms per operator
7//! - Flexible modulation routing
8
9use super::basic::{BasicOscillator, Waveform};
10use crate::generators::{Generator, ModulatableGenerator};
11use crate::vector::prelude::*;
12use rill_core::traits::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
13use rill_core::traits::ProcessResult;
14use rill_core::Transcendental;
15
16// =============================================================================
17// Simple 2-operator FM synthesizer
18// =============================================================================
19
20/// Simple 2-operator FM synthesizer
21///
22/// Basic FM architecture: one modulator modulates one carrier.
23/// Ideal for:
24/// - Creating metallic timbres
25/// - Bell-like sounds
26/// - Complex harmonic structures
27///
28/// # Example
29/// ```
30/// use rill_core::time::ClockTick;
31/// use rill_core::traits::ActionContext;
32/// use rill_core_dsp::generators::*;
33/// use rill_core::traits::algorithm::Algorithm;
34///
35/// let tick = ClockTick::default();
36/// let ctx = ActionContext::new(&tick);
37///
38/// // Create FM synthesizer with 2:1 frequency ratio
39/// let mut fm = SimpleFmSynth::<f32>::new(
40///     440.0,  // carrier frequency (A4)
41///     2.0,    // modulator one octave higher
42///     1.5     // modulation index
43/// );
44/// fm.init(44100.0);
45///
46/// // Generate sample
47/// let mut output = [0.0_f32];
48/// fm.process(None, &mut output).unwrap();
49/// let sample = output[0];
50/// ```
51#[derive(Clone, Copy)]
52pub struct SimpleFmSynth<T: Transcendental> {
53    /// Carrier oscillator - produces the output signal
54    carrier: BasicOscillator<T>,
55    /// Modulator oscillator - modulates carrier frequency
56    modulator: BasicOscillator<T>,
57    /// Modulation index (modulation depth)
58    modulation_index: ScalarVector1<T>,
59    /// Modulator-to-carrier frequency ratio
60    ratio: f32,
61}
62
63impl<T: Transcendental> SimpleFmSynth<T> {
64    /// Create a new FM synthesizer
65    ///
66    /// # Arguments
67    /// * `carrier_freq` - carrier frequency in Hz
68    /// * `modulator_ratio` - frequency ratio (modulator/carrier)
69    /// * `modulation_index` - modulation index (0.0 - 10.0)
70    pub fn new(carrier_freq: f32, modulator_ratio: f32, modulation_index: T) -> Self {
71        let one = T::from_f32(1.0);
72        Self {
73            carrier: BasicOscillator::new(Waveform::Sine, carrier_freq, one),
74            modulator: BasicOscillator::new(Waveform::Sine, carrier_freq * modulator_ratio, one),
75            modulation_index: ScalarVector1::splat(modulation_index),
76            ratio: modulator_ratio,
77        }
78    }
79
80    /// Set carrier waveform
81    ///
82    /// Default is sine wave
83    pub fn with_carrier_waveform(mut self, waveform: Waveform) -> Self {
84        let freq = self.carrier.frequency();
85        self.carrier = BasicOscillator::new(waveform, freq, T::from_f32(1.0));
86        self
87    }
88
89    /// Set modulator waveform
90    ///
91    /// Default is sine wave
92    pub fn with_modulator_waveform(mut self, waveform: Waveform) -> Self {
93        let freq = self.modulator.frequency();
94        self.modulator = BasicOscillator::new(waveform, freq, T::from_f32(1.0));
95        self
96    }
97
98    /// Set carrier frequency
99    pub fn set_carrier_frequency(&mut self, freq: f32) {
100        self.carrier.set_frequency(freq);
101        self.modulator.set_frequency(freq * self.ratio);
102    }
103
104    /// Set modulation index
105    ///
106    /// # Arguments
107    /// * `index` - modulation index (0.0 - 10.0)
108    pub fn set_modulation_index(&mut self, index: T) {
109        self.modulation_index = ScalarVector1::splat(index);
110        self.carrier.set_modulation_index(index);
111    }
112
113    /// Set frequency ratio
114    ///
115    /// # Arguments
116    /// * `ratio` - ratio (modulator/carrier), typically 0.1 - 10.0
117    pub fn set_ratio(&mut self, ratio: f32) {
118        self.ratio = ratio;
119        self.modulator
120            .set_frequency(self.carrier.frequency() * ratio);
121    }
122
123    /// Get current modulation index
124    pub fn modulation_index(&self) -> T {
125        self.modulation_index.extract(0)
126    }
127
128    /// Get current frequency ratio
129    pub fn ratio(&self) -> f32 {
130        self.ratio
131    }
132}
133
134impl<T: Transcendental> Algorithm<T> for SimpleFmSynth<T> {
135    fn init(&mut self, sample_rate: f32) {
136        self.carrier.init(sample_rate);
137        self.modulator.init(sample_rate);
138    }
139
140    fn reset(&mut self) {
141        self.carrier.reset();
142        self.modulator.reset();
143    }
144
145    fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
146        for out in output.iter_mut() {
147            // Get modulator signal
148            let mod_signal = self.modulator.generate().extract(0);
149
150            // Modulate carrier frequency
151            self.carrier
152                .modulate_frequency(mod_signal * self.modulation_index.extract(0));
153
154            // Return carrier signal
155            *out = self.carrier.generate().extract(0);
156        }
157        Ok(())
158    }
159
160    fn metadata(&self) -> AlgorithmMetadata {
161        AlgorithmMetadata {
162            name: "Simple FM Synth",
163            category: AlgorithmCategory::Generator,
164            description: "2-operator FM synthesizer",
165            author: "Rill",
166            version: env!("CARGO_PKG_VERSION"),
167        }
168    }
169}
170
171// ==================== Generator trait implementation for SimpleFmSynth ====================
172
173impl<T: Transcendental> Generator<T> for SimpleFmSynth<T> {
174    fn phase(&self) -> T {
175        self.carrier.phase()
176    }
177
178    fn set_phase(&mut self, phase: T) {
179        self.carrier.set_phase(phase);
180        self.modulator.set_phase(phase);
181    }
182
183    fn frequency(&self) -> f32 {
184        self.carrier.frequency()
185    }
186
187    fn set_frequency(&mut self, freq: f32) {
188        self.set_carrier_frequency(freq);
189    }
190
191    fn amplitude(&self) -> T {
192        self.carrier.amplitude()
193    }
194
195    fn set_amplitude(&mut self, amp: T) {
196        self.carrier.set_amplitude(amp);
197        self.modulator.set_amplitude(amp);
198    }
199}
200
201// ==================== ModulatableGenerator trait implementation for SimpleFmSynth ====================
202
203impl<T: Transcendental> ModulatableGenerator<T> for SimpleFmSynth<T> {
204    fn modulate_frequency(&mut self, amount: T) {
205        self.carrier.modulate_frequency(amount);
206        // Also update modulation_index accordingly
207        self.modulation_index = ScalarVector1::splat(amount);
208    }
209
210    fn modulation_index(&self) -> T {
211        SimpleFmSynth::modulation_index(self)
212    }
213
214    fn set_modulation_index(&mut self, index: T) {
215        SimpleFmSynth::set_modulation_index(self, index);
216    }
217}
218
219// =============================================================================
220// Multi-operator FM synthesizer (like Yamaha DX7)
221// =============================================================================
222
223/// Multi-operator FM synthesizer
224///
225/// Implements architecture similar to the Yamaha DX7:
226/// - N operators (typically 4 or 6)
227/// - Each operator can be a carrier or modulator
228/// - Flexible modulation routing matrix
229/// - Individual modulation indices
230///
231/// # Example
232/// ```
233/// use rill_core_dsp::generators::*;
234/// use rill_core::traits::algorithm::Algorithm;
235///
236/// // 6-operator FM (like DX7)
237/// let frequencies = [440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0];
238/// let algorithm = [
239///     [false, true,  false, false, false, false],
240///     [false, false, true,  false, false, false],
241///     [false, false, false, true,  false, false],
242///     [false, false, false, false, true,  false],
243///     [false, false, false, false, false, true],
244///     [false, false, false, false, false, false],
245/// ];
246///
247/// let mut fm = FmSynth::<f32, 6>::new(frequencies, algorithm);
248/// fm.init(44100.0);
249/// ```
250pub struct FmSynth<T: Transcendental, const N: usize> {
251    /// Operators (all use BasicOscillator)
252    operators: [BasicOscillator<T>; N],
253    /// Connection algorithm (routing matrix)
254    /// matrix[i][j] = true means operator j modulates operator i
255    algorithm: [[bool; N]; N],
256    /// Modulation indices for each operator
257    modulation_indices: [ScalarVector1<T>; N],
258}
259
260impl<T: Transcendental, const N: usize> FmSynth<T, N> {
261    /// Create a new FM synthesizer
262    ///
263    /// # Arguments
264    /// * `frequencies` - array of frequencies for each operator
265    /// * `algorithm` - N x N modulation routing matrix
266    pub fn new(frequencies: [f32; N], algorithm: [[bool; N]; N]) -> Self {
267        let one = T::from_f32(1.0);
268        let mut operators = [BasicOscillator::new(Waveform::Sine, 440.0, one); N];
269        for i in 0..N {
270            operators[i].set_frequency(frequencies[i]);
271        }
272
273        Self {
274            operators,
275            algorithm,
276            modulation_indices: [ScalarVector1::splat(T::ZERO); N],
277        }
278    }
279
280    /// Create a new FM synthesizer with all operators at the same frequency
281    pub fn new_with_freq(frequency: f32, algorithm: [[bool; N]; N]) -> Self {
282        let one = T::from_f32(1.0);
283        let operators = [BasicOscillator::new(Waveform::Sine, frequency, one); N];
284
285        Self {
286            operators,
287            algorithm,
288            modulation_indices: [ScalarVector1::splat(T::ZERO); N],
289        }
290    }
291
292    /// Set operator waveform
293    pub fn set_waveform(&mut self, index: usize, waveform: Waveform) {
294        if index < N {
295            let freq = self.operators[index].frequency();
296            self.operators[index] = BasicOscillator::new(waveform, freq, T::from_f32(1.0));
297        }
298    }
299
300    /// Set operator frequency
301    pub fn set_frequency(&mut self, index: usize, freq: f32) {
302        if index < N {
303            self.operators[index].set_frequency(freq);
304        }
305    }
306
307    /// Set modulation index for operator
308    pub fn set_modulation_index(&mut self, index: usize, idx: T) {
309        if index < N {
310            self.modulation_indices[index] = ScalarVector1::splat(idx);
311        }
312    }
313
314    /// Get current operator value (without processing)
315    pub fn peek_operator(&self, index: usize) -> T {
316        if index < N {
317            self.operators[index].phase()
318        } else {
319            T::ZERO
320        }
321    }
322
323    /// Reset all operators
324    pub fn reset_all(&mut self) {
325        for op in &mut self.operators {
326            op.reset();
327        }
328    }
329}
330
331impl<T: Transcendental, const N: usize> Algorithm<T> for FmSynth<T, N> {
332    fn init(&mut self, sample_rate: f32) {
333        for op in &mut self.operators {
334            op.init(sample_rate);
335        }
336    }
337
338    fn reset(&mut self) {
339        self.reset_all();
340    }
341
342    fn process(&mut self, _input: Option<&[T]>, output: &mut [T]) -> ProcessResult<()> {
343        for out in output.iter_mut() {
344            // Store current values of all operators
345            let values: [_; N] = core::array::from_fn(|i| self.operators[i].generate().extract(0));
346
347            // Apply modulation according to algorithm
348            for (i, op) in self.operators.iter_mut().enumerate() {
349                let mut mod_sum = T::ZERO;
350
351                // Sum all modulations for this operator
352                for (j, &is_mod) in self.algorithm[i].iter().enumerate() {
353                    if is_mod {
354                        mod_sum += values[j] * self.modulation_indices[j].extract(0);
355                    }
356                }
357
358                // Apply modulation if present
359                if mod_sum != T::ZERO {
360                    op.modulate_frequency(mod_sum);
361                }
362            }
363
364            // Last operator produces the output signal
365            // (in classic FM architecture)
366            *out = values[N - 1];
367        }
368        Ok(())
369    }
370
371    fn metadata(&self) -> AlgorithmMetadata {
372        // Create static strings for different sizes
373        match N {
374            2 => AlgorithmMetadata {
375                name: "2-operator FM Synth",
376                category: AlgorithmCategory::Generator,
377                description: "2-operator FM synthesizer",
378                author: "Rill",
379                version: env!("CARGO_PKG_VERSION"),
380            },
381            3 => AlgorithmMetadata {
382                name: "3-operator FM Synth",
383                category: AlgorithmCategory::Generator,
384                description: "3-operator FM synthesizer",
385                author: "Rill",
386                version: env!("CARGO_PKG_VERSION"),
387            },
388            4 => AlgorithmMetadata {
389                name: "4-operator FM Synth",
390                category: AlgorithmCategory::Generator,
391                description: "4-operator FM synthesizer (DX7 style)",
392                author: "Rill",
393                version: env!("CARGO_PKG_VERSION"),
394            },
395            5 => AlgorithmMetadata {
396                name: "5-operator FM Synth",
397                category: AlgorithmCategory::Generator,
398                description: "5-operator FM synthesizer",
399                author: "Rill",
400                version: env!("CARGO_PKG_VERSION"),
401            },
402            6 => AlgorithmMetadata {
403                name: "6-operator FM Synth",
404                category: AlgorithmCategory::Generator,
405                description: "6-operator FM synthesizer (DX7 style)",
406                author: "Rill",
407                version: env!("CARGO_PKG_VERSION"),
408            },
409            _ => AlgorithmMetadata {
410                name: "FM Synth",
411                category: AlgorithmCategory::Generator,
412                description: "Multi-operator FM synthesizer",
413                author: "Rill",
414                version: env!("CARGO_PKG_VERSION"),
415            },
416        }
417    }
418}
419
420// =============================================================================
421// Helper functions and constants
422// =============================================================================
423
424/// Preset algorithms for 4-operator FM
425pub mod algorithms_4op {
426    /// Algorithm 1: all operators in series
427    pub const ALGORITHM_1: [[bool; 4]; 4] = [
428        [false, true, false, false],
429        [false, false, true, false],
430        [false, false, false, true],
431        [false, false, false, false],
432    ];
433
434    /// Algorithm 2: two parallel cascades
435    pub const ALGORITHM_2: [[bool; 4]; 4] = [
436        [false, true, false, false],
437        [false, false, false, false],
438        [false, false, false, true],
439        [false, false, false, false],
440    ];
441
442    /// Algorithm 3: operators 1 and 2 modulate 3 and 4
443    pub const ALGORITHM_3: [[bool; 4]; 4] = [
444        [false, false, false, false],
445        [false, false, false, false],
446        [true, true, false, false],
447        [false, false, false, false],
448    ];
449}
450
451/// Preset algorithms for 6-operator FM (DX7 style)
452pub mod algorithms_6op {
453    /// Algorithm 1: serial chain
454    pub const ALGORITHM_1: [[bool; 6]; 6] = [
455        [false, true, false, false, false, false],
456        [false, false, true, false, false, false],
457        [false, false, false, true, false, false],
458        [false, false, false, false, true, false],
459        [false, false, false, false, false, true],
460        [false, false, false, false, false, false],
461    ];
462
463    /// Algorithm 2: two parallel cascades of 3
464    pub const ALGORITHM_2: [[bool; 6]; 6] = [
465        [false, true, false, false, false, false],
466        [false, false, true, false, false, false],
467        [false, false, false, false, false, false],
468        [false, false, false, false, true, false],
469        [false, false, false, false, false, true],
470        [false, false, false, false, false, false],
471    ];
472
473    /// Algorithm 3: complex structure with feedback
474    pub const ALGORITHM_3: [[bool; 6]; 6] = [
475        [false, true, false, false, false, false],
476        [true, false, true, false, false, false],
477        [false, false, false, true, false, false],
478        [false, false, false, false, true, false],
479        [false, false, false, false, false, true],
480        [false, false, false, false, false, false],
481    ];
482}
483
484// =============================================================================
485// Tests
486// =============================================================================
487
488#[cfg(test)]
489mod tests {
490    use super::*;
491
492    #[test]
493    fn test_simple_fm_synth() {
494        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
495        fm.init(44100.0);
496
497        let mut output = [0.0f32; 1];
498        fm.process(None, &mut output).unwrap();
499        let sample = output[0];
500        assert!((-1.0..=1.0).contains(&sample));
501    }
502
503    #[test]
504    fn test_simple_fm_with_different_waveforms() {
505        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5)
506            .with_carrier_waveform(Waveform::Saw)
507            .with_modulator_waveform(Waveform::Square);
508        fm.init(44100.0);
509
510        let mut output = [0.0f32; 1];
511        fm.process(None, &mut output).unwrap();
512        let sample = output[0];
513        assert!((-1.0..=1.0).contains(&sample));
514    }
515
516    #[test]
517    fn test_simple_fm_parameters() {
518        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
519        fm.init(44100.0);
520
521        assert_eq!(fm.frequency(), 440.0);
522        assert_eq!(fm.ratio(), 2.0);
523        assert_eq!(fm.modulation_index(), 1.5);
524
525        fm.set_carrier_frequency(880.0);
526        assert_eq!(fm.frequency(), 880.0);
527
528        fm.set_ratio(3.0);
529        assert_eq!(fm.ratio(), 3.0);
530
531        fm.set_modulation_index(2.0);
532        assert_eq!(fm.modulation_index(), 2.0);
533    }
534
535    #[test]
536    fn test_fm_synth_4op() {
537        let frequencies = [440.0, 880.0, 1320.0, 1760.0];
538        let mut fm = FmSynth::<f32, 4>::new(frequencies, algorithms_4op::ALGORITHM_1);
539        fm.init(44100.0);
540
541        let mut output = [0.0f32; 1];
542        fm.process(None, &mut output).unwrap();
543        let sample = output[0];
544        assert!((-1.0..=1.0).contains(&sample));
545    }
546
547    #[test]
548    fn test_fm_synth_6op() {
549        let frequencies = [440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0];
550        let mut fm = FmSynth::<f32, 6>::new(frequencies, algorithms_6op::ALGORITHM_1);
551        fm.init(44100.0);
552
553        let mut output = [0.0f32; 1];
554        fm.process(None, &mut output).unwrap();
555        let sample = output[0];
556        assert!((-1.0..=1.0).contains(&sample));
557    }
558
559    #[test]
560    fn test_fm_synth_set_waveform() {
561        let frequencies = [440.0, 880.0];
562        let algorithm = [[false, true], [false, false]];
563
564        let mut fm = FmSynth::<f32, 2>::new(frequencies, algorithm);
565        fm.init(44100.0);
566
567        fm.set_waveform(0, Waveform::Saw);
568        fm.set_waveform(1, Waveform::Square);
569
570        let mut output = [0.0f32; 1];
571        fm.process(None, &mut output).unwrap();
572        let sample = output[0];
573        assert!((-1.0..=1.0).contains(&sample));
574    }
575
576    #[test]
577    fn test_generator_trait() {
578        use crate::generators::Generator;
579
580        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
581        fm.init(44100.0);
582
583        assert_eq!(fm.frequency(), 440.0);
584        fm.set_frequency(880.0);
585        assert_eq!(fm.frequency(), 880.0);
586
587        fm.set_amplitude(0.5);
588        assert_eq!(fm.amplitude(), 0.5);
589
590        let phase = fm.phase();
591        assert!((0.0..=1.0).contains(&phase));
592    }
593
594    #[test]
595    fn test_modulatable_trait() {
596        use crate::generators::ModulatableGenerator;
597
598        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
599        fm.init(44100.0);
600
601        // Check initial value
602        assert_eq!(fm.modulation_index(), 1.5);
603
604        // Modulate frequency
605        fm.modulate_frequency(0.3);
606        assert_eq!(
607            fm.modulation_index(),
608            0.3,
609            "modulation_index should be updated to 0.3"
610        );
611
612        // Set new modulation index
613        fm.set_modulation_index(0.8);
614        assert_eq!(
615            fm.modulation_index(),
616            0.8,
617            "modulation_index should be updated to 0.8"
618        );
619    }
620
621    #[test]
622    fn test_clone_copy() {
623        let fm1 = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
624        let fm2 = fm1; // Copy
625        let fm3 = Clone::clone(&fm1); // Explicit clone
626
627        assert_eq!(fm1.frequency(), fm2.frequency());
628        assert_eq!(fm1.frequency(), fm3.frequency());
629        assert_eq!(fm1.ratio(), fm2.ratio());
630    }
631}