Skip to main content

maolan_engine/
modulator.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
4pub enum ModulatorController {
5    Volume,
6    Balance,
7}
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize, Default)]
10pub enum ModulatorShape {
11    #[default]
12    Sine,
13    Triangle,
14    Saw,
15    Square,
16    SampleHold,
17}
18
19impl std::fmt::Display for ModulatorShape {
20    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21        match self {
22            Self::Sine => write!(f, "Sine"),
23            Self::Triangle => write!(f, "Triangle"),
24            Self::Saw => write!(f, "Saw"),
25            Self::Square => write!(f, "Square"),
26            Self::SampleHold => write!(f, "Sample & Hold"),
27        }
28    }
29}
30
31#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
32pub enum ModulatorTarget {
33    TrackVolume {
34        track_name: String,
35        min: f32,
36        max: f32,
37    },
38    TrackBalance {
39        track_name: String,
40        min: f32,
41        max: f32,
42    },
43    HwOutVolume {
44        min: f32,
45        max: f32,
46    },
47    HwOutBalance {
48        min: f32,
49        max: f32,
50    },
51}
52
53impl ModulatorTarget {
54    pub fn track_name(&self) -> Option<&str> {
55        match self {
56            Self::TrackVolume { track_name, .. } | Self::TrackBalance { track_name, .. } => {
57                Some(track_name)
58            }
59            Self::HwOutVolume { .. } | Self::HwOutBalance { .. } => None,
60        }
61    }
62}
63
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct Modulator {
66    pub id: usize,
67    pub name: String,
68    pub shape: ModulatorShape,
69    pub rate_hz: f32,
70    pub phase: f32,
71    pub bipolar: bool,
72    pub enabled: bool,
73    pub targets: Vec<ModulatorTarget>,
74}
75
76impl Modulator {
77    pub fn new(id: usize) -> Self {
78        Self {
79            id,
80            name: format!("Modulator {id}"),
81            shape: ModulatorShape::default(),
82            rate_hz: 1.0,
83            phase: 0.0,
84            bipolar: false,
85            enabled: true,
86            targets: Vec::new(),
87        }
88    }
89
90    /// Evaluate the modulator at a given transport sample and sample rate.
91    /// Returns a normalized value in `[0, 1]` regardless of the `bipolar` flag.
92    pub fn value_at(&self, sample: usize, sample_rate: f64) -> f32 {
93        let cycles = sample as f64 / sample_rate * self.rate_hz as f64 + self.phase as f64;
94        let phase = cycles.rem_euclid(1.0) as f32;
95        let raw = match self.shape {
96            ModulatorShape::Sine => (phase * 2.0 * std::f32::consts::PI).sin(),
97            ModulatorShape::Triangle => {
98                if phase < 0.5 {
99                    4.0 * phase - 1.0
100                } else {
101                    3.0 - 4.0 * phase
102                }
103            }
104            ModulatorShape::Saw => 2.0 * phase - 1.0,
105            ModulatorShape::Square => {
106                if phase < 0.5 {
107                    1.0
108                } else {
109                    -1.0
110                }
111            }
112            ModulatorShape::SampleHold => {
113                let step = (phase * 16.0).floor() as i32;
114                let mut hasher = std::collections::hash_map::DefaultHasher::new();
115                use std::hash::{Hash, Hasher};
116                step.hash(&mut hasher);
117                let h = hasher.finish();
118                ((h as f32 / u64::MAX as f32) * 2.0) - 1.0
119            }
120        };
121        ((raw + 1.0) / 2.0).clamp(0.0, 1.0)
122    }
123}