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, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
33pub enum MusicalDivision {
34 Bar,
35 Half,
36 Beat,
37 Eighth,
38 Sixteenth,
39 ThirtySecond,
40 SixtyFourth,
41}
42
43impl MusicalDivision {
44 pub fn to_hz(self, bpm: f64, tsig_num: u16, tsig_denom: u16) -> f64 {
47 let beat_hz = bpm / 60.0;
48 let bar_hz = beat_hz / (tsig_num as f64 * 4.0 / tsig_denom.max(1) as f64);
49 match self {
50 Self::Bar => bar_hz,
51 Self::Half => beat_hz / 2.0,
52 Self::Beat => beat_hz,
53 Self::Eighth => beat_hz * 2.0,
54 Self::Sixteenth => beat_hz * 4.0,
55 Self::ThirtySecond => beat_hz * 8.0,
56 Self::SixtyFourth => beat_hz * 16.0,
57 }
58 }
59}
60
61impl std::fmt::Display for MusicalDivision {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 match self {
64 Self::Bar => write!(f, "1/1"),
65 Self::Half => write!(f, "1/2"),
66 Self::Beat => write!(f, "1/4"),
67 Self::Eighth => write!(f, "1/8"),
68 Self::Sixteenth => write!(f, "1/16"),
69 Self::ThirtySecond => write!(f, "1/32"),
70 Self::SixtyFourth => write!(f, "1/64"),
71 }
72 }
73}
74
75#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
77pub enum ModulatorRate {
78 Hz(f32),
79 Musical(MusicalDivision),
80}
81
82impl Default for ModulatorRate {
83 fn default() -> Self {
84 Self::Hz(1.0)
85 }
86}
87
88impl ModulatorRate {
89 pub fn effective_hz(&self, bpm: f64, tsig_num: u16, tsig_denom: u16) -> f64 {
91 match *self {
92 Self::Hz(hz) => f64::from(hz),
93 Self::Musical(div) => div.to_hz(bpm, tsig_num, tsig_denom),
94 }
95 }
96}
97
98#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
99pub enum ModulatorTarget {
100 TrackVolume {
101 track_name: String,
102 min: f32,
103 max: f32,
104 },
105 TrackBalance {
106 track_name: String,
107 min: f32,
108 max: f32,
109 },
110 HwOutVolume {
111 min: f32,
112 max: f32,
113 },
114 HwOutBalance {
115 min: f32,
116 max: f32,
117 },
118 ClapParameter {
119 track_name: String,
120 instance_id: usize,
121 param_id: u32,
122 min: f64,
123 max: f64,
124 },
125 Vst3Parameter {
126 track_name: String,
127 instance_id: usize,
128 param_id: u32,
129 min: f32,
130 max: f32,
131 },
132 #[cfg(all(unix, not(target_os = "macos")))]
133 Lv2Parameter {
134 track_name: String,
135 instance_id: usize,
136 index: u32,
137 min: f32,
138 max: f32,
139 },
140 MidiCc {
141 track_name: String,
142 channel: u8,
143 cc: u8,
144 },
145}
146
147impl ModulatorTarget {
148 pub fn track_name(&self) -> Option<&str> {
149 match self {
150 Self::TrackVolume { track_name, .. }
151 | Self::TrackBalance { track_name, .. }
152 | Self::ClapParameter { track_name, .. }
153 | Self::Vst3Parameter { track_name, .. }
154 | Self::MidiCc { track_name, .. } => Some(track_name),
155 #[cfg(all(unix, not(target_os = "macos")))]
156 Self::Lv2Parameter { track_name, .. } => Some(track_name),
157 Self::HwOutVolume { .. } | Self::HwOutBalance { .. } => None,
158 }
159 }
160}
161
162#[derive(Debug, Clone, Serialize, Deserialize)]
163pub struct Modulator {
164 pub id: usize,
165 pub name: String,
166 pub shape: ModulatorShape,
167 pub rate: ModulatorRate,
168 pub phase: f32,
169 pub enabled: bool,
170 pub targets: Vec<ModulatorTarget>,
171}
172
173pub fn map_value(value: f32, min: f32, max: f32) -> f32 {
177 if min <= max {
178 (min + value * (max - min)).clamp(min, max)
179 } else {
180 (max + (1.0 - value) * (min - max)).clamp(max, min)
181 }
182}
183
184pub fn map_value_f64(value: f32, min: f64, max: f64) -> f64 {
186 let value = f64::from(value);
187 if min <= max {
188 (min + value * (max - min)).clamp(min, max)
189 } else {
190 (max + (1.0 - value) * (min - max)).clamp(max, min)
191 }
192}
193
194impl Modulator {
195 pub fn new(id: usize) -> Self {
196 Self {
197 id,
198 name: format!("Modulator {id}"),
199 shape: ModulatorShape::default(),
200 rate: ModulatorRate::default(),
201 phase: 0.0,
202 enabled: true,
203 targets: Vec::new(),
204 }
205 }
206
207 pub fn value_at(
210 &self,
211 sample: usize,
212 sample_rate: f64,
213 bpm: f64,
214 tsig_num: u16,
215 tsig_denom: u16,
216 ) -> f32 {
217 let rate_hz = self.rate.effective_hz(bpm, tsig_num, tsig_denom);
218 let cycles = sample as f64 / sample_rate * rate_hz + self.phase as f64;
219 let phase = cycles.rem_euclid(1.0) as f32;
220 let raw = match self.shape {
221 ModulatorShape::Sine => (phase * 2.0 * std::f32::consts::PI).sin(),
222 ModulatorShape::Triangle => {
223 if phase < 0.5 {
224 4.0 * phase - 1.0
225 } else {
226 3.0 - 4.0 * phase
227 }
228 }
229 ModulatorShape::Saw => 2.0 * phase - 1.0,
230 ModulatorShape::Square => {
231 if phase < 0.5 {
232 1.0
233 } else {
234 -1.0
235 }
236 }
237 ModulatorShape::SampleHold => {
238 let step = (phase * 16.0).floor() as i32;
239 let mut hasher = std::collections::hash_map::DefaultHasher::new();
240 use std::hash::{Hash, Hasher};
241 step.hash(&mut hasher);
242 let h = hasher.finish();
243 ((h as f32 / u64::MAX as f32) * 2.0) - 1.0
244 }
245 };
246 ((raw + 1.0) / 2.0).clamp(0.0, 1.0)
247 }
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn map_value_maps_forward_range() {
256 assert!((map_value(0.0, 0.0, 100.0) - 0.0).abs() < f32::EPSILON);
257 assert!((map_value(1.0, 0.0, 100.0) - 100.0).abs() < f32::EPSILON);
258 assert!((map_value(0.5, 0.0, 100.0) - 50.0).abs() < f32::EPSILON);
259 }
260
261 #[test]
262 fn map_value_reverses_when_min_greater_than_max() {
263 assert!((map_value(0.0, 100.0, 0.0) - 100.0).abs() < f32::EPSILON);
264 assert!((map_value(1.0, 100.0, 0.0) - 0.0).abs() < f32::EPSILON);
265 assert!((map_value(0.5, 100.0, 0.0) - 50.0).abs() < f32::EPSILON);
266 }
267
268 #[test]
269 fn map_value_clamps_out_of_range() {
270 assert!((map_value(-0.5, 0.0, 100.0) - 0.0).abs() < f32::EPSILON);
271 assert!((map_value(1.5, 0.0, 100.0) - 100.0).abs() < f32::EPSILON);
272 assert!((map_value(-0.5, 100.0, 0.0) - 100.0).abs() < f32::EPSILON);
273 assert!((map_value(1.5, 100.0, 0.0) - 0.0).abs() < f32::EPSILON);
274 }
275
276 #[test]
277 fn map_value_f64_reverses_when_min_greater_than_max() {
278 assert!((map_value_f64(0.0, 1.0, 0.0) - 1.0).abs() < f64::EPSILON);
279 assert!((map_value_f64(1.0, 1.0, 0.0) - 0.0).abs() < f64::EPSILON);
280 assert!((map_value_f64(0.5, 1.0, 0.0) - 0.5).abs() < f64::EPSILON);
281 }
282}