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 ClapParameter {
52 track_name: String,
53 instance_id: usize,
54 param_id: u32,
55 min: f64,
56 max: f64,
57 },
58 Vst3Parameter {
59 track_name: String,
60 instance_id: usize,
61 param_id: u32,
62 min: f32,
63 max: f32,
64 },
65 #[cfg(all(unix, not(target_os = "macos")))]
66 Lv2Parameter {
67 track_name: String,
68 instance_id: usize,
69 index: u32,
70 min: f32,
71 max: f32,
72 },
73 MidiCc {
74 track_name: String,
75 channel: u8,
76 cc: u8,
77 },
78}
79
80impl ModulatorTarget {
81 pub fn track_name(&self) -> Option<&str> {
82 match self {
83 Self::TrackVolume { track_name, .. }
84 | Self::TrackBalance { track_name, .. }
85 | Self::ClapParameter { track_name, .. }
86 | Self::Vst3Parameter { track_name, .. }
87 | Self::MidiCc { track_name, .. } => Some(track_name),
88 #[cfg(all(unix, not(target_os = "macos")))]
89 Self::Lv2Parameter { track_name, .. } => Some(track_name),
90 Self::HwOutVolume { .. } | Self::HwOutBalance { .. } => None,
91 }
92 }
93}
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
96pub struct Modulator {
97 pub id: usize,
98 pub name: String,
99 pub shape: ModulatorShape,
100 pub rate_hz: f32,
101 pub phase: f32,
102 pub enabled: bool,
103 pub targets: Vec<ModulatorTarget>,
104}
105
106pub fn map_value(value: f32, min: f32, max: f32) -> f32 {
110 if min <= max {
111 (min + value * (max - min)).clamp(min, max)
112 } else {
113 (max + (1.0 - value) * (min - max)).clamp(max, min)
114 }
115}
116
117pub fn map_value_f64(value: f32, min: f64, max: f64) -> f64 {
119 let value = f64::from(value);
120 if min <= max {
121 (min + value * (max - min)).clamp(min, max)
122 } else {
123 (max + (1.0 - value) * (min - max)).clamp(max, min)
124 }
125}
126
127impl Modulator {
128 pub fn new(id: usize) -> Self {
129 Self {
130 id,
131 name: format!("Modulator {id}"),
132 shape: ModulatorShape::default(),
133 rate_hz: 1.0,
134 phase: 0.0,
135 enabled: true,
136 targets: Vec::new(),
137 }
138 }
139
140 pub fn value_at(&self, sample: usize, sample_rate: f64) -> f32 {
143 let cycles = sample as f64 / sample_rate * self.rate_hz as f64 + self.phase as f64;
144 let phase = cycles.rem_euclid(1.0) as f32;
145 let raw = match self.shape {
146 ModulatorShape::Sine => (phase * 2.0 * std::f32::consts::PI).sin(),
147 ModulatorShape::Triangle => {
148 if phase < 0.5 {
149 4.0 * phase - 1.0
150 } else {
151 3.0 - 4.0 * phase
152 }
153 }
154 ModulatorShape::Saw => 2.0 * phase - 1.0,
155 ModulatorShape::Square => {
156 if phase < 0.5 {
157 1.0
158 } else {
159 -1.0
160 }
161 }
162 ModulatorShape::SampleHold => {
163 let step = (phase * 16.0).floor() as i32;
164 let mut hasher = std::collections::hash_map::DefaultHasher::new();
165 use std::hash::{Hash, Hasher};
166 step.hash(&mut hasher);
167 let h = hasher.finish();
168 ((h as f32 / u64::MAX as f32) * 2.0) - 1.0
169 }
170 };
171 ((raw + 1.0) / 2.0).clamp(0.0, 1.0)
172 }
173}
174
175#[cfg(test)]
176mod tests {
177 use super::*;
178
179 #[test]
180 fn map_value_maps_forward_range() {
181 assert!((map_value(0.0, 0.0, 100.0) - 0.0).abs() < f32::EPSILON);
182 assert!((map_value(1.0, 0.0, 100.0) - 100.0).abs() < f32::EPSILON);
183 assert!((map_value(0.5, 0.0, 100.0) - 50.0).abs() < f32::EPSILON);
184 }
185
186 #[test]
187 fn map_value_reverses_when_min_greater_than_max() {
188 assert!((map_value(0.0, 100.0, 0.0) - 100.0).abs() < f32::EPSILON);
189 assert!((map_value(1.0, 100.0, 0.0) - 0.0).abs() < f32::EPSILON);
190 assert!((map_value(0.5, 100.0, 0.0) - 50.0).abs() < f32::EPSILON);
191 }
192
193 #[test]
194 fn map_value_clamps_out_of_range() {
195 assert!((map_value(-0.5, 0.0, 100.0) - 0.0).abs() < f32::EPSILON);
196 assert!((map_value(1.5, 0.0, 100.0) - 100.0).abs() < f32::EPSILON);
197 assert!((map_value(-0.5, 100.0, 0.0) - 100.0).abs() < f32::EPSILON);
198 assert!((map_value(1.5, 100.0, 0.0) - 0.0).abs() < f32::EPSILON);
199 }
200
201 #[test]
202 fn map_value_f64_reverses_when_min_greater_than_max() {
203 assert!((map_value_f64(0.0, 1.0, 0.0) - 1.0).abs() < f64::EPSILON);
204 assert!((map_value_f64(1.0, 1.0, 0.0) - 0.0).abs() < f64::EPSILON);
205 assert!((map_value_f64(0.5, 1.0, 0.0) - 0.5).abs() < f64::EPSILON);
206 }
207}