maolan_engine/
modulator.rs1use 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 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}