Skip to main content

rill_core_dsp/generators/
fm.rs

1//! FM (Frequency Modulation) синтез
2//!
3//! Этот модуль предоставляет инструменты для частотной модуляции:
4//! - Простой 2-операторный FM синтезатор
5//! - Многооператорный FM синтезатор (как в Yamaha DX7)
6//! - Поддержка различных форм волны для каждого оператора
7//! - Гибкая маршрутизация модуляции
8
9use super::basic::{BasicOscillator, Waveform};
10use crate::algorithm::{Algorithm, AlgorithmCategory, AlgorithmMetadata};
11use crate::generators::{Generator, ModulatableGenerator};
12use crate::vector::prelude::*;
13use rill_core::traits::{ActionContext, ProcessResult};
14use rill_core::Transcendental;
15
16// =============================================================================
17// Простой 2-операторный FM синтезатор
18// =============================================================================
19
20/// Простой FM синтезатор на основе двух операторов
21///
22/// Базовая FM архитектура: один модулятор модулирует один carrier.
23/// Идеально подходит для:
24/// - Создания металлических тембров
25/// - Эмуляции колокольчиков
26/// - Сложных гармонических структур
27///
28/// # Пример
29/// ```
30/// use rill_core::time::ClockTick;
31/// use rill_core::traits::ActionContext;
32/// use rill_core_dsp::generators::*;
33/// use rill_core_dsp::Algorithm;
34///
35/// let tick = ClockTick::default();
36/// let ctx = ActionContext::new(&tick);
37///
38/// // Создаём FM синтезатор с соотношением частот 2:1
39/// let mut fm = SimpleFmSynth::<f32>::new(
40///     440.0,  // частота несущей (A4)
41///     2.0,    // модулятор на октаву выше
42///     1.5     // индекс модуляции
43/// );
44/// fm.init(44100.0);
45///
46/// // Генерируем семпл
47/// let mut output = [0.0_f32];
48/// fm.process(None, &mut output, &ctx).unwrap();
49/// let sample = output[0];
50/// ```
51#[derive(Clone, Copy)]
52pub struct SimpleFmSynth<T: Transcendental> {
53    /// Несущий осциллятор (carrier) - производит выходной сигнал
54    carrier: BasicOscillator<T>,
55    /// Модулирующий осциллятор (modulator) - модулирует частоту carrier
56    modulator: BasicOscillator<T>,
57    /// Индекс модуляции (глубина модуляции)
58    modulation_index: ScalarVector1<T>,
59    /// Соотношение частот модулятора к несущей
60    ratio: f32,
61}
62
63impl<T: Transcendental> SimpleFmSynth<T> {
64    /// Создать новый FM синтезатор
65    ///
66    /// # Arguments
67    /// * `carrier_freq` - частота несущей в Hz
68    /// * `modulator_ratio` - соотношение частот (модулятор/carrier)
69    /// * `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    /// Установить форму волны для несущей
81    ///
82    /// По умолчанию используется синусоида
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    /// Установить форму волны для модулятора
90    ///
91    /// По умолчанию используется синусоида
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    /// Установить частоту несущей
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    /// Установить индекс модуляции
105    ///
106    /// # Arguments
107    /// * `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    /// Установить соотношение частот
114    ///
115    /// # Arguments
116    /// * `ratio` - соотношение (модулятор/carrier), обычно 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    /// Получить текущий индекс модуляции
124    pub fn modulation_index(&self) -> T {
125        self.modulation_index.extract(0)
126    }
127
128    /// Получить текущее соотношение частот
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(
146        &mut self,
147        input: Option<&[T]>,
148        output: &mut [T],
149        _ctx: &ActionContext,
150    ) -> ProcessResult<()> {
151        let input = input.unwrap_or(&[]);
152        for out in output.iter_mut() {
153            // Получаем модулирующий сигнал
154            let mod_signal = self.modulator.generate().extract(0);
155
156            // Модулируем частоту несущей
157            self.carrier
158                .modulate_frequency(mod_signal * self.modulation_index.extract(0));
159
160            // Возвращаем сигнал несущей
161            *out = self.carrier.generate().extract(0);
162        }
163        Ok(())
164    }
165
166    fn metadata(&self) -> AlgorithmMetadata {
167        AlgorithmMetadata {
168            name: "Simple FM Synth",
169            category: AlgorithmCategory::Generator,
170            description: "2-operator FM synthesizer",
171            author: "Rill",
172            version: env!("CARGO_PKG_VERSION"),
173        }
174    }
175}
176
177// ==================== Реализация трейта Generator для SimpleFmSynth ====================
178
179impl<T: Transcendental> Generator<T> for SimpleFmSynth<T> {
180    fn phase(&self) -> T {
181        self.carrier.phase()
182    }
183
184    fn set_phase(&mut self, phase: T) {
185        self.carrier.set_phase(phase);
186        self.modulator.set_phase(phase);
187    }
188
189    fn frequency(&self) -> f32 {
190        self.carrier.frequency()
191    }
192
193    fn set_frequency(&mut self, freq: f32) {
194        self.set_carrier_frequency(freq);
195    }
196
197    fn amplitude(&self) -> T {
198        self.carrier.amplitude()
199    }
200
201    fn set_amplitude(&mut self, amp: T) {
202        self.carrier.set_amplitude(amp);
203        self.modulator.set_amplitude(amp);
204    }
205}
206
207// ==================== Реализация трейта ModulatableGenerator для SimpleFmSynth ====================
208
209impl<T: Transcendental> ModulatableGenerator<T> for SimpleFmSynth<T> {
210    fn modulate_frequency(&mut self, amount: T) {
211        self.carrier.modulate_frequency(amount);
212        // Также обновляем modulation_index, чтобы он соответствовал
213        self.modulation_index = ScalarVector1::splat(amount);
214    }
215
216    fn modulation_index(&self) -> T {
217        SimpleFmSynth::modulation_index(self)
218    }
219
220    fn set_modulation_index(&mut self, index: T) {
221        SimpleFmSynth::set_modulation_index(self, index);
222    }
223}
224
225// =============================================================================
226// Многооператорный FM синтезатор (как в Yamaha DX7)
227// =============================================================================
228
229/// Многооператорный FM синтезатор
230///
231/// Реализует архитектуру, аналогичную Yamaha DX7:
232/// - N операторов (обычно 4 или 6)
233/// - Каждый оператор может быть carrier или modulator
234/// - Гибкая матрица маршрутов модуляции
235/// - Индивидуальные индексы модуляции
236///
237/// # Пример
238/// ```
239/// use rill_core_dsp::generators::*;
240/// use rill_core_dsp::Algorithm;
241///
242/// // 6-операторный FM (как в DX7)
243/// let frequencies = [440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0];
244/// let algorithm = [
245///     [false, true,  false, false, false, false],
246///     [false, false, true,  false, false, false],
247///     [false, false, false, true,  false, false],
248///     [false, false, false, false, true,  false],
249///     [false, false, false, false, false, true],
250///     [false, false, false, false, false, false],
251/// ];
252///
253/// let mut fm = FmSynth::<f32, 6>::new(frequencies, algorithm);
254/// fm.init(44100.0);
255/// ```
256pub struct FmSynth<T: Transcendental, const N: usize> {
257    /// Операторы (все используют BasicOscillator)
258    operators: [BasicOscillator<T>; N],
259    /// Алгоритм соединения (матрица маршрутов)
260    /// matrix[i][j] = true означает, что оператор j модулирует оператор i
261    algorithm: [[bool; N]; N],
262    /// Индексы модуляции для каждого оператора
263    modulation_indices: [ScalarVector1<T>; N],
264}
265
266impl<T: Transcendental, const N: usize> FmSynth<T, N> {
267    /// Создать новый FM синтезатор
268    ///
269    /// # Arguments
270    /// * `frequencies` - массив частот для каждого оператора
271    /// * `algorithm` - матрица маршрутов модуляции N x N
272    pub fn new(frequencies: [f32; N], algorithm: [[bool; N]; N]) -> Self {
273        let one = T::from_f32(1.0);
274        let mut operators = [BasicOscillator::new(Waveform::Sine, 440.0, one); N];
275        for i in 0..N {
276            operators[i].set_frequency(frequencies[i]);
277        }
278
279        Self {
280            operators,
281            algorithm,
282            modulation_indices: [ScalarVector1::splat(T::ZERO); N],
283        }
284    }
285
286    /// Создать новый FM синтезатор со всеми операторами на одной частоте
287    pub fn new_with_freq(frequency: f32, algorithm: [[bool; N]; N]) -> Self {
288        let one = T::from_f32(1.0);
289        let operators = [BasicOscillator::new(Waveform::Sine, frequency, one); N];
290
291        Self {
292            operators,
293            algorithm,
294            modulation_indices: [ScalarVector1::splat(T::ZERO); N],
295        }
296    }
297
298    /// Установить форму волны для оператора
299    pub fn set_waveform(&mut self, index: usize, waveform: Waveform) {
300        if index < N {
301            let freq = self.operators[index].frequency();
302            self.operators[index] = BasicOscillator::new(waveform, freq, T::from_f32(1.0));
303        }
304    }
305
306    /// Установить частоту для оператора
307    pub fn set_frequency(&mut self, index: usize, freq: f32) {
308        if index < N {
309            self.operators[index].set_frequency(freq);
310        }
311    }
312
313    /// Установить индекс модуляции для оператора
314    pub fn set_modulation_index(&mut self, index: usize, idx: T) {
315        if index < N {
316            self.modulation_indices[index] = ScalarVector1::splat(idx);
317        }
318    }
319
320    /// Получить текущее значение оператора (без обработки)
321    pub fn peek_operator(&self, index: usize) -> T {
322        if index < N {
323            self.operators[index].phase()
324        } else {
325            T::ZERO
326        }
327    }
328
329    /// Сбросить все операторы
330    pub fn reset_all(&mut self) {
331        for op in &mut self.operators {
332            op.reset();
333        }
334    }
335}
336
337impl<T: Transcendental, const N: usize> Algorithm<T> for FmSynth<T, N> {
338    fn init(&mut self, sample_rate: f32) {
339        for op in &mut self.operators {
340            op.init(sample_rate);
341        }
342    }
343
344    fn reset(&mut self) {
345        self.reset_all();
346    }
347
348    fn process(
349        &mut self,
350        input: Option<&[T]>,
351        output: &mut [T],
352        _ctx: &ActionContext,
353    ) -> ProcessResult<()> {
354        let input = input.unwrap_or(&[]);
355        for out in output.iter_mut() {
356            // Сохраняем текущие значения всех операторов
357            let mut values = [T::ZERO; N];
358            for i in 0..N {
359                values[i] = self.operators[i].generate().extract(0);
360            }
361
362            // Применяем модуляцию согласно алгоритму
363            for i in 0..N {
364                let mut mod_sum = T::ZERO;
365
366                // Суммируем все модуляции для этого оператора
367                for j in 0..N {
368                    if self.algorithm[i][j] {
369                        mod_sum = mod_sum + values[j] * self.modulation_indices[j].extract(0);
370                    }
371                }
372
373                // Применяем модуляцию, если есть
374                if mod_sum != T::ZERO {
375                    self.operators[i].modulate_frequency(mod_sum);
376                }
377            }
378
379            // Последний оператор даёт выходной сигнал
380            // (в классической FM архитектуре)
381            *out = values[N - 1];
382        }
383        Ok(())
384    }
385
386    fn metadata(&self) -> AlgorithmMetadata {
387        // Создаём статические строки для разных размеров
388        match N {
389            2 => AlgorithmMetadata {
390                name: "2-operator FM Synth",
391                category: AlgorithmCategory::Generator,
392                description: "2-operator FM synthesizer",
393                author: "Rill",
394                version: env!("CARGO_PKG_VERSION"),
395            },
396            3 => AlgorithmMetadata {
397                name: "3-operator FM Synth",
398                category: AlgorithmCategory::Generator,
399                description: "3-operator FM synthesizer",
400                author: "Rill",
401                version: env!("CARGO_PKG_VERSION"),
402            },
403            4 => AlgorithmMetadata {
404                name: "4-operator FM Synth",
405                category: AlgorithmCategory::Generator,
406                description: "4-operator FM synthesizer (DX7 style)",
407                author: "Rill",
408                version: env!("CARGO_PKG_VERSION"),
409            },
410            5 => AlgorithmMetadata {
411                name: "5-operator FM Synth",
412                category: AlgorithmCategory::Generator,
413                description: "5-operator FM synthesizer",
414                author: "Rill",
415                version: env!("CARGO_PKG_VERSION"),
416            },
417            6 => AlgorithmMetadata {
418                name: "6-operator FM Synth",
419                category: AlgorithmCategory::Generator,
420                description: "6-operator FM synthesizer (DX7 style)",
421                author: "Rill",
422                version: env!("CARGO_PKG_VERSION"),
423            },
424            _ => AlgorithmMetadata {
425                name: "FM Synth",
426                category: AlgorithmCategory::Generator,
427                description: "Multi-operator FM synthesizer",
428                author: "Rill",
429                version: env!("CARGO_PKG_VERSION"),
430            },
431        }
432    }
433}
434
435// =============================================================================
436// Вспомогательные функции и константы
437// =============================================================================
438
439/// Предустановленные алгоритмы для 4-операторного FM
440pub mod algorithms_4op {
441    /// Алгоритм 1: все операторы последовательно
442    pub const ALGORITHM_1: [[bool; 4]; 4] = [
443        [false, true, false, false],
444        [false, false, true, false],
445        [false, false, false, true],
446        [false, false, false, false],
447    ];
448
449    /// Алгоритм 2: два параллельных каскада
450    pub const ALGORITHM_2: [[bool; 4]; 4] = [
451        [false, true, false, false],
452        [false, false, false, false],
453        [false, false, false, true],
454        [false, false, false, false],
455    ];
456
457    /// Алгоритм 3: операторы 1 и 2 модулируют 3 и 4
458    pub const ALGORITHM_3: [[bool; 4]; 4] = [
459        [false, false, false, false],
460        [false, false, false, false],
461        [true, true, false, false],
462        [false, false, false, false],
463    ];
464}
465
466/// Предустановленные алгоритмы для 6-операторного FM (DX7 стиль)
467pub mod algorithms_6op {
468    /// Алгоритм 1: последовательная цепочка
469    pub const ALGORITHM_1: [[bool; 6]; 6] = [
470        [false, true, false, false, false, false],
471        [false, false, true, false, false, false],
472        [false, false, false, true, false, false],
473        [false, false, false, false, true, false],
474        [false, false, false, false, false, true],
475        [false, false, false, false, false, false],
476    ];
477
478    /// Алгоритм 2: два параллельных каскада по 3
479    pub const ALGORITHM_2: [[bool; 6]; 6] = [
480        [false, true, false, false, false, false],
481        [false, false, true, false, false, false],
482        [false, false, false, false, false, false],
483        [false, false, false, false, true, false],
484        [false, false, false, false, false, true],
485        [false, false, false, false, false, false],
486    ];
487
488    /// Алгоритм 3: сложная структура с обратными связями
489    pub const ALGORITHM_3: [[bool; 6]; 6] = [
490        [false, true, false, false, false, false],
491        [true, false, true, false, false, false],
492        [false, false, false, true, false, false],
493        [false, false, false, false, true, false],
494        [false, false, false, false, false, true],
495        [false, false, false, false, false, false],
496    ];
497}
498
499// =============================================================================
500// Тесты
501// =============================================================================
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506    use rill_core::time::ClockTick;
507    use rill_core::traits::ActionContext;
508
509    #[test]
510    fn test_simple_fm_synth() {
511        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
512        fm.init(44100.0);
513
514        let mut output = [0.0f32; 1];
515        let tick = ClockTick::default();
516        let ctx = ActionContext::new(&tick);
517        fm.process(None, &mut output, &ctx).unwrap();
518        let sample = output[0];
519        assert!(sample >= -1.0 && sample <= 1.0);
520    }
521
522    #[test]
523    fn test_simple_fm_with_different_waveforms() {
524        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5)
525            .with_carrier_waveform(Waveform::Saw)
526            .with_modulator_waveform(Waveform::Square);
527        fm.init(44100.0);
528
529        let mut output = [0.0f32; 1];
530        let tick = ClockTick::default();
531        let ctx = ActionContext::new(&tick);
532        fm.process(None, &mut output, &ctx).unwrap();
533        let sample = output[0];
534        assert!(sample >= -1.0 && sample <= 1.0);
535    }
536
537    #[test]
538    fn test_simple_fm_parameters() {
539        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
540        fm.init(44100.0);
541
542        assert_eq!(fm.frequency(), 440.0);
543        assert_eq!(fm.ratio(), 2.0);
544        assert_eq!(fm.modulation_index(), 1.5);
545
546        fm.set_carrier_frequency(880.0);
547        assert_eq!(fm.frequency(), 880.0);
548
549        fm.set_ratio(3.0);
550        assert_eq!(fm.ratio(), 3.0);
551
552        fm.set_modulation_index(2.0);
553        assert_eq!(fm.modulation_index(), 2.0);
554    }
555
556    #[test]
557    fn test_fm_synth_4op() {
558        let frequencies = [440.0, 880.0, 1320.0, 1760.0];
559        let mut fm = FmSynth::<f32, 4>::new(frequencies, algorithms_4op::ALGORITHM_1);
560        fm.init(44100.0);
561
562        let mut output = [0.0f32; 1];
563        let tick = ClockTick::default();
564        let ctx = ActionContext::new(&tick);
565        fm.process(None, &mut output, &ctx).unwrap();
566        let sample = output[0];
567        assert!(sample >= -1.0 && sample <= 1.0);
568    }
569
570    #[test]
571    fn test_fm_synth_6op() {
572        let frequencies = [440.0, 880.0, 1320.0, 1760.0, 2200.0, 2640.0];
573        let mut fm = FmSynth::<f32, 6>::new(frequencies, algorithms_6op::ALGORITHM_1);
574        fm.init(44100.0);
575
576        let mut output = [0.0f32; 1];
577        let tick = ClockTick::default();
578        let ctx = ActionContext::new(&tick);
579        fm.process(None, &mut output, &ctx).unwrap();
580        let sample = output[0];
581        assert!(sample >= -1.0 && sample <= 1.0);
582    }
583
584    #[test]
585    fn test_fm_synth_set_waveform() {
586        let frequencies = [440.0, 880.0];
587        let algorithm = [[false, true], [false, false]];
588
589        let mut fm = FmSynth::<f32, 2>::new(frequencies, algorithm);
590        fm.init(44100.0);
591
592        fm.set_waveform(0, Waveform::Saw);
593        fm.set_waveform(1, Waveform::Square);
594
595        let mut output = [0.0f32; 1];
596        let tick = ClockTick::default();
597        let ctx = ActionContext::new(&tick);
598        fm.process(None, &mut output, &ctx).unwrap();
599        let sample = output[0];
600        assert!(sample >= -1.0 && sample <= 1.0);
601    }
602
603    #[test]
604    fn test_generator_trait() {
605        use crate::generators::Generator;
606
607        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
608        fm.init(44100.0);
609
610        assert_eq!(fm.frequency(), 440.0);
611        fm.set_frequency(880.0);
612        assert_eq!(fm.frequency(), 880.0);
613
614        fm.set_amplitude(0.5);
615        assert_eq!(fm.amplitude(), 0.5);
616
617        let phase = fm.phase();
618        assert!(phase >= 0.0 && phase <= 1.0);
619    }
620
621    #[test]
622    fn test_modulatable_trait() {
623        use crate::generators::ModulatableGenerator;
624
625        let mut fm = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
626        fm.init(44100.0);
627
628        // Проверяем начальное значение
629        assert_eq!(fm.modulation_index(), 1.5);
630
631        // Модулируем частоту
632        fm.modulate_frequency(0.3);
633        assert_eq!(
634            fm.modulation_index(),
635            0.3,
636            "modulation_index should be updated to 0.3"
637        );
638
639        // Устанавливаем новый индекс модуляции
640        fm.set_modulation_index(0.8);
641        assert_eq!(
642            fm.modulation_index(),
643            0.8,
644            "modulation_index should be updated to 0.8"
645        );
646    }
647
648    #[test]
649    fn test_clone_copy() {
650        let fm1 = SimpleFmSynth::<f32>::new(440.0, 2.0, 1.5);
651        let fm2 = fm1; // Копирование
652        let fm3 = fm1.clone(); // Явное клонирование
653
654        assert_eq!(fm1.frequency(), fm2.frequency());
655        assert_eq!(fm1.frequency(), fm3.frequency());
656        assert_eq!(fm1.ratio(), fm2.ratio());
657    }
658}