synthahol_babylon/
effect.rs

1use std::convert::TryFrom;
2use std::fmt::{Display, Formatter};
3
4use strum::IntoEnumIterator;
5use strum_macros::EnumIter;
6use uom::si::f64::Ratio;
7
8use crate::Envelope;
9
10#[derive(Debug)]
11pub struct Chorus {
12    pub enabled: bool,
13    pub depth: f64,
14    pub pre_delay: f64,
15    pub ratio: f64,
16    pub mix: f64,
17}
18
19impl Effect for Chorus {
20    fn is_enabled(&self) -> bool {
21        self.enabled
22    }
23}
24
25/// Mode for the filter built into the delay effect.
26///
27/// The discriminants of the items match the values in the preset file times
28/// 1000 and converted to ints, because Babylon stores enumerations as floating
29/// point values. Listed in the order they appear in the Babylon user interface.
30#[derive(Copy, Clone, Debug, EnumIter, Eq, PartialEq)]
31#[repr(u32)]
32pub enum DelayFilterMode {
33    Off = 0,
34    LowPass5000 = 42,
35    LowPass3800 = 83,
36    LowPass2500 = 125,
37    LowPass1600 = 167,
38    LowPass1000 = 208,
39    LowPass750 = 25,
40    LowPass400 = 292,
41    LowPass200 = 333,
42    HighPass4000 = 375,
43    HighPass2000 = 417,
44    HighPass1200 = 458,
45    HighPass800 = 500,
46    HighPass600 = 542,
47    HighPass400 = 583,
48    HighPass250 = 625,
49    HighPass100 = 667,
50    BandPass3000 = 708,
51    BandPass1800 = 750,
52    BandPass1300 = 792,
53    BandPass1000 = 833,
54    BandPass700 = 875,
55    BandPass500 = 917,
56    BandPass300 = 958,
57    BandPass150 = 1000,
58}
59
60impl DelayFilterMode {
61    pub(crate) fn from_or(mode_id: u32, default: Self) -> Self {
62        Self::iter()
63            .find(|id| *id as u32 == mode_id)
64            .unwrap_or(default)
65    }
66}
67
68impl Display for DelayFilterMode {
69    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
70        use DelayFilterMode::*;
71        let msg = match self {
72            Off => "Filter: Off",
73            LowPass5000 => "LP: 5000 Hz",
74            LowPass3800 => "LP: 3800 Hz",
75            LowPass2500 => "LP: 2500 Hz",
76            LowPass1600 => "LP: 1600 Hz",
77            LowPass1000 => "LP: 1000 Hz",
78            LowPass750 => "LP: 750 Hz",
79            LowPass400 => "LP: 400 Hz",
80            LowPass200 => "LP: 200 Hz",
81            HighPass4000 => "HP: 4000 Hz",
82            HighPass2000 => "HP: 2000 Hz",
83            HighPass1200 => "HP: 1200 Hz",
84            HighPass800 => "HP: 800 Hz",
85            HighPass600 => "HP: 600 Hz",
86            HighPass400 => "HP: 400 Hz",
87            HighPass250 => "HP: 250 Hz",
88            HighPass100 => "HP: 100 Hz",
89            BandPass3000 => "BP: 3000 Hz",
90            BandPass1800 => "BP: 1800 Hz",
91            BandPass1300 => "BP: 1300 Hz",
92            BandPass1000 => "BP: 1000 Hz",
93            BandPass700 => "BP: 700 Hz",
94            BandPass500 => "BP: 500 Hz",
95            BandPass300 => "BP: 300 Hz",
96            BandPass150 => "BP: 150 Hz",
97        };
98        f.write_str(msg)
99    }
100}
101
102#[derive(Debug)]
103pub struct Delay {
104    pub enabled: bool,
105    pub ping_pong: bool,
106    pub feedback: f64,
107    pub filter_mode: DelayFilterMode,
108    pub sync: bool,
109    pub time: f64,
110    pub mix: f64,
111}
112
113impl Effect for Delay {
114    fn is_enabled(&self) -> bool {
115        self.enabled
116    }
117}
118
119#[derive(Debug)]
120pub struct Distortion {
121    pub enabled: bool,
122
123    /// 0.0 to 10.0
124    pub gain: f64,
125}
126
127impl Effect for Distortion {
128    fn is_enabled(&self) -> bool {
129        self.enabled
130    }
131}
132
133pub trait Effect {
134    fn is_enabled(&self) -> bool {
135        false
136    }
137}
138
139#[derive(Debug)]
140pub struct Equalizer {
141    pub enabled: bool,
142    pub high_gain: Ratio,
143    pub low_gain: Ratio,
144    pub mid_gain: Ratio,
145}
146
147impl Effect for Equalizer {
148    fn is_enabled(&self) -> bool {
149        self.enabled
150    }
151}
152
153/// Kinds of effects.
154///
155/// The discriminants of the items match the file format. This is the default
156/// ordering of the effects.
157#[derive(Copy, Clone, Debug, EnumIter, Eq, PartialEq)]
158#[repr(u32)]
159pub enum EffectType {
160    Distortion,
161    LoFi,
162    Filter,
163    Chorus,
164    Equalizer,
165    Delay,
166    Reverb,
167}
168
169impl TryFrom<u32> for EffectType {
170    type Error = String;
171
172    fn try_from(effect_type_id: u32) -> Result<Self, Self::Error> {
173        Self::iter()
174            .find(|id| *id as u32 == effect_type_id)
175            .ok_or(format!("Unknown effect type ID {}", effect_type_id))
176    }
177}
178
179/// The discriminants of the items match the file format.
180#[derive(Copy, Clone, Debug, EnumIter, Eq, PartialEq)]
181#[repr(u32)]
182pub enum FilterMode {
183    LowPass,
184    BandPass,
185    HighPass,
186    Notch,
187    Peak,
188}
189
190impl FilterMode {
191    pub(crate) fn from_or(mode_id: u32, default: Self) -> Self {
192        Self::iter()
193            .find(|id| *id as u32 == mode_id)
194            .unwrap_or(default)
195    }
196}
197
198/// The discriminants of the items match the file format.
199#[derive(Copy, Clone, Debug, EnumIter, Eq, PartialEq)]
200#[repr(u32)]
201pub enum FilterEffectMode {
202    Off,
203    Saturation,
204    Overdrive,
205    Distortion,
206    BitRateReduction,
207    SampleRateReduction,
208}
209
210impl FilterEffectMode {
211    pub(crate) fn from_or(mode_id: u32, default: FilterEffectMode) -> FilterEffectMode {
212        FilterEffectMode::iter()
213            .find(|id| *id as u32 == mode_id)
214            .unwrap_or(default)
215    }
216}
217
218#[derive(Debug)]
219pub struct Filter {
220    pub enabled: bool,
221    pub mode: FilterMode,
222    pub resonance: f64,
223    pub cutoff_frequency: f64,
224    pub key_tracking: f64,
225    pub envelope: Envelope,
226
227    /// How much the envelope affects the cutoff frequency
228    pub envelope_amount: f64,
229
230    /// How the effect is processed.
231    pub effect_mode: FilterEffectMode,
232    pub effect_enabled: bool,
233    pub effect_amount: f64,
234}
235
236impl Effect for Filter {
237    fn is_enabled(&self) -> bool {
238        self.enabled
239    }
240}
241
242#[derive(Debug)]
243pub struct LoFi {
244    pub enabled: bool,
245    pub bitrate: f64,
246
247    // 0 to 10.0 in Babylon interface
248    pub sample_rate: f64,
249
250    // 0 to 10.0 in Babylon interface
251    pub mix: f64,
252}
253
254impl Effect for LoFi {
255    fn is_enabled(&self) -> bool {
256        self.enabled
257    }
258}
259
260#[derive(Debug)]
261pub struct Reverb {
262    pub enabled: bool,
263    pub dampen: f64,
264    pub filter: f64,
265    pub room: f64,
266    pub width: f64,
267    pub mix: f64,
268}
269
270impl Effect for Reverb {
271    fn is_enabled(&self) -> bool {
272        self.enabled
273    }
274}
275
276#[cfg(test)]
277mod test {
278    use std::io::Result;
279    use std::path::Path;
280
281    use approx::assert_relative_eq;
282    use strum::IntoEnumIterator;
283    use uom::si::ratio::percent;
284
285    use crate::{DelayFilterMode, EffectType, FilterMode, Preset};
286
287    fn read_preset(filename: &str) -> Result<Preset> {
288        let path = &Path::new("tests").join("effects").join(&filename);
289        Preset::read_file(path)
290    }
291
292    #[test]
293    fn delay() {
294        let preset = read_preset("delay-ping_pong_off-1.0.2.bab").unwrap();
295        assert!(!preset.delay.ping_pong);
296
297        let preset = read_preset("delay-ping_pong_on-1.0.2.bab").unwrap();
298        assert!(preset.delay.ping_pong);
299
300        let preset = read_preset("delay-time1t-hp100-ping_pong-1.0.3.bab").unwrap();
301        assert!(preset.delay.ping_pong);
302        assert!(preset.delay.sync);
303        assert_eq!(preset.delay.time, 1.0);
304        assert_eq!(preset.delay.filter_mode, DelayFilterMode::HighPass100);
305
306        let preset = read_preset("delay-time504-syncoff-1.0.3.bab").unwrap();
307        assert!(!preset.delay.sync);
308        assert_relative_eq!(preset.delay.time, 0.504, epsilon = 0.00001);
309
310        let preset = read_preset("delay-timehalf-lp200-1.0.3.bab").unwrap();
311        assert_relative_eq!(preset.delay.time, 0.257, epsilon = 0.00001);
312        assert_eq!(preset.delay.filter_mode, DelayFilterMode::LowPass200);
313
314        let preset = read_preset("delay-timesixteenth-bp3000-1.0.3.bab").unwrap();
315        assert_relative_eq!(preset.delay.time, 0.410, epsilon = 0.00001);
316        assert_eq!(preset.delay.filter_mode, DelayFilterMode::BandPass3000);
317    }
318
319    #[test]
320    fn delay_filter_mode() {
321        let preset = read_preset("delay-band_pass_150-1.0.4.bab").unwrap();
322        assert!(preset.delay.enabled);
323        assert_eq!(preset.delay.filter_mode, DelayFilterMode::BandPass150);
324
325        let preset = read_preset("delay-band_pass_1000-1.0.4.bab").unwrap();
326        assert!(preset.delay.enabled);
327        assert_eq!(preset.delay.filter_mode, DelayFilterMode::BandPass1000);
328
329        let preset = read_preset("delay-high_pass_250-1.0.4.bab").unwrap();
330        assert!(preset.delay.enabled);
331        assert_eq!(preset.delay.filter_mode, DelayFilterMode::HighPass250);
332
333        let preset = read_preset("delay-low_pass_200-1.0.4.bab").unwrap();
334        assert!(preset.delay.enabled);
335        assert_eq!(preset.delay.filter_mode, DelayFilterMode::LowPass200);
336    }
337
338    #[test]
339    fn distortion() {
340        let preset = read_preset("distortion-gain5-1.0.3.bab").unwrap();
341        assert!(preset.distortion.enabled);
342        assert_eq!(preset.distortion.gain, 0.5);
343    }
344
345    #[test]
346    fn effect_order() {
347        let preset = read_preset("effect-order-reversed-1.0.2.bab").unwrap();
348        let expected_effect_order: Vec<EffectType> = EffectType::iter().rev().collect();
349        assert_eq!(&preset.effect_order, &expected_effect_order);
350        assert_eq!(preset.effect_position(EffectType::Equalizer).unwrap(), 2);
351    }
352
353    #[test]
354    fn equalizer() {
355        let preset = read_preset("equalizer-l-10-m5-h-10-1.0.3.bab").unwrap();
356        assert!(preset.equalizer.enabled);
357        assert_eq!(preset.equalizer.low_gain.get::<percent>(), 0.5);
358        assert_eq!(preset.equalizer.mid_gain.get::<percent>(), 0.5);
359        assert_eq!(preset.equalizer.high_gain.get::<percent>(), 0.5);
360    }
361
362    #[test]
363    fn filter() {
364        let preset = read_preset("filter-bandpass-1.0.2.bab").unwrap();
365        assert_eq!(preset.filter.mode, FilterMode::BandPass);
366        assert_eq!(preset.filter.cutoff_frequency, 100.0);
367
368        let preset = read_preset("filter-highpass-1.0.2.bab").unwrap();
369        assert_eq!(preset.filter.mode, FilterMode::HighPass);
370
371        let preset = read_preset("filter-notch-1.0.2.bab").unwrap();
372        assert_eq!(preset.filter.mode, FilterMode::Notch);
373
374        let preset = read_preset("filter-peak-1.0.2.bab").unwrap();
375        assert_eq!(preset.filter.mode, FilterMode::Peak);
376    }
377
378    #[test]
379    fn reverb() {
380        let preset = read_preset("reverb-r100-w0-d50-m34-hp400-1.0.3.bab").unwrap();
381        assert!(preset.reverb.enabled);
382        assert_relative_eq!(preset.reverb.room, 1.0, epsilon = 0.0001);
383        assert_relative_eq!(preset.reverb.width, 0.0, epsilon = 0.0001);
384        assert_relative_eq!(preset.reverb.dampen, 0.50, epsilon = 0.0001);
385        assert_relative_eq!(preset.reverb.mix, 0.34, epsilon = 0.0001);
386        assert_relative_eq!(preset.reverb.filter, 0.583, epsilon = 0.0001);
387    }
388}