oxisynth_chorus/
lib.rs

1const MIN_SPEED_HZ: f32 = 0.29;
2
3// Length of one delay line in samples:
4// Set through MAX_SAMPLES_LN2.
5// For example:
6// MAX_SAMPLES_LN2=12
7// => MAX_SAMPLES=pow(2,12)=4096
8// => MAX_SAMPLES_ANDMASK=4095
9const MAX_SAMPLES_LN2: usize = 12;
10const MAX_SAMPLES: usize = 1 << (MAX_SAMPLES_LN2 - 1);
11const MAX_SAMPLES_ANDMASK: usize = MAX_SAMPLES - 1;
12
13const INTERPOLATION_SUBSAMPLES_LN2: usize = 8;
14const INTERPOLATION_SUBSAMPLES: usize = 1 << (INTERPOLATION_SUBSAMPLES_LN2 - 1);
15const INTERPOLATION_SUBSAMPLES_ANDMASK: usize = INTERPOLATION_SUBSAMPLES - 1;
16
17const INTERPOLATION_SAMPLES: usize = 5;
18
19#[derive(Clone)]
20pub struct Chorus {
21    type_0: ChorusMode,
22    new_type: ChorusMode,
23    depth_ms: f32,
24    new_depth_ms: f32,
25    level: f32,
26    new_level: f32,
27    speed_hz: f32,
28    new_speed_hz: f32,
29    number_blocks: u32,
30    new_number_blocks: u32,
31    chorusbuf: [f32; MAX_SAMPLES],
32    counter: i32,
33    phase: [isize; 99],
34    modulation_period_samples: isize,
35    lookup_tab: Vec<i32>,
36    sample_rate: f32,
37    sinc_table: [[f32; 128]; 5],
38}
39
40impl Chorus {
41    pub fn new(sample_rate: f32) -> Self {
42        let mut chorus = Self {
43            type_0: ChorusMode::Sine,
44            new_type: ChorusMode::Sine,
45            depth_ms: 0f32,
46            new_depth_ms: 0f32,
47            level: 0f32,
48            new_level: 0f32,
49            speed_hz: 0f32,
50            new_speed_hz: 0f32,
51            number_blocks: 0,
52            new_number_blocks: 0,
53            chorusbuf: [0.0; MAX_SAMPLES],
54            counter: 0,
55            phase: [0; 99],
56            modulation_period_samples: 0,
57            lookup_tab: vec![0; (sample_rate / MIN_SPEED_HZ) as usize],
58            sample_rate,
59            sinc_table: [[0f32; 128]; 5],
60        };
61        for i in 0..INTERPOLATION_SAMPLES {
62            for ii in 0..INTERPOLATION_SUBSAMPLES {
63                // Move the origin into the center of the table
64                let i_shifted = i as f32 - INTERPOLATION_SAMPLES as f32 / 2.0
65                    + ii as f32 / INTERPOLATION_SUBSAMPLES as f32;
66
67                if i_shifted.abs() < 0.000001 {
68                    // sinc(0) cannot be calculated straightforward (limit needed for 0/0)
69                    chorus.sinc_table[i][ii] = 1.0;
70                } else {
71                    chorus.sinc_table[i][ii] = (i_shifted * std::f32::consts::PI).sin()
72                        / (std::f32::consts::PI * i_shifted);
73                    // Hamming window
74                    chorus.sinc_table[i][ii] *=
75                        0.5 * (1.0 + (2.0 * std::f32::consts::PI * i_shifted / 5.0).cos());
76                }
77            }
78        }
79        chorus.init();
80
81        chorus
82    }
83
84    fn init(&mut self) {
85        self.chorusbuf.fill(0.0);
86        self.set_params(&Default::default());
87        self.update();
88    }
89
90    fn update(&mut self) {
91        if self.new_number_blocks > 99 {
92            log::warn!(
93                "chorus: number blocks larger than max. allowed! Setting value to {}.",
94                99
95            );
96            self.new_number_blocks = 99;
97        }
98        if self.new_speed_hz < 0.29 {
99            log::warn!(
100                "chorus: speed is too low (min {})! Setting value to min.",
101                0.29
102            );
103            self.new_speed_hz = 0.29;
104        } else if self.new_speed_hz > 5.0 {
105            log::warn!(
106                "chorus: speed must be below {} Hz! Setting value to max.",
107                5
108            );
109            self.new_speed_hz = 5.0;
110        }
111        if self.new_depth_ms < 0.0 {
112            log::warn!("chorus: depth must be positive! Setting value to 0.",);
113            self.new_depth_ms = 0.0;
114        }
115        if self.new_level < 0.0 {
116            log::warn!("chorus: level must be positive! Setting value to 0.",);
117            self.new_level = 0.0;
118        } else if self.new_level > 10.0 {
119            log::warn!(
120                "chorus: level must be < 10. A reasonable level is << 1! Setting it to 0.1.",
121            );
122            self.new_level = 0.1;
123        }
124        self.modulation_period_samples = (self.sample_rate / self.new_speed_hz) as isize;
125
126        let mut modulation_depth_samples = (self.new_depth_ms / 1000.0 * self.sample_rate) as i32;
127        if modulation_depth_samples > MAX_SAMPLES as i32 {
128            log::warn!(
129                "chorus: Too high depth. Setting it to max ({}).",
130                MAX_SAMPLES,
131            );
132            modulation_depth_samples = MAX_SAMPLES as i32;
133        }
134        if self.type_0 == ChorusMode::Sine {
135            modulate_sine(
136                &mut self.lookup_tab,
137                self.modulation_period_samples as usize,
138                modulation_depth_samples,
139            );
140        } else if self.type_0 == ChorusMode::Triangle {
141            modulate_triangle(
142                &mut self.lookup_tab,
143                self.modulation_period_samples as i32,
144                modulation_depth_samples,
145            );
146        } else {
147            log::warn!("chorus: Unknown modulation type. Using sinewave.",);
148            self.type_0 = ChorusMode::Sine;
149            modulate_sine(
150                &mut self.lookup_tab,
151                self.modulation_period_samples as usize,
152                modulation_depth_samples,
153            );
154        }
155        for i in 0..(self.number_blocks as usize) {
156            self.phase[i] = (self.modulation_period_samples as f64 * i as f64
157                / self.number_blocks as f64) as isize;
158        }
159        self.counter = 0;
160        self.type_0 = self.new_type;
161        self.depth_ms = self.new_depth_ms;
162        self.level = self.new_level;
163        self.speed_hz = self.new_speed_hz;
164        self.number_blocks = self.new_number_blocks;
165    }
166
167    pub fn process_mix(
168        &mut self,
169        in_0: &mut [f32; 64],
170        left_out: &mut [f32; 64],
171        right_out: &mut [f32; 64],
172    ) {
173        for sample_index in 0..64 {
174            let d_in = in_0[sample_index];
175            let mut d_out = 0.0;
176            self.chorusbuf[self.counter as usize] = d_in;
177
178            for i in 0..(self.number_blocks as usize) {
179                // Calculate the delay in subsamples for the delay line of chorus block nr. */
180                //
181                // The value in the lookup table is so, that this expression
182                // will always be positive.  It will always include a number of
183                // full periods of MAX_SAMPLES*INTERPOLATION_SUBSAMPLES to
184                // remain positive at all times. */
185                let mut pos_subsamples = INTERPOLATION_SUBSAMPLES as i32 * self.counter
186                    - self.lookup_tab[self.phase[i] as usize];
187
188                let mut pos_samples = pos_subsamples / INTERPOLATION_SUBSAMPLES as i32;
189                pos_subsamples &= INTERPOLATION_SUBSAMPLES_ANDMASK as i32;
190
191                for ii in 0..5 {
192                    d_out += self.chorusbuf[(pos_samples & MAX_SAMPLES_ANDMASK as i32) as usize]
193                        * self.sinc_table[ii as usize][pos_subsamples as usize];
194                    pos_samples -= 1;
195                }
196
197                self.phase[i] += 1;
198                self.phase[i] %= self.modulation_period_samples;
199            }
200
201            d_out *= self.level;
202
203            left_out[sample_index] += d_out;
204            right_out[sample_index] += d_out;
205
206            self.counter += 1;
207            self.counter %= MAX_SAMPLES as i32;
208        }
209    }
210
211    pub fn process_replace(&mut self, left_out: &mut [f32; 64], right_out: &mut [f32; 64]) {
212        for sample_index in 0..64 {
213            // Don't ask me why only left buf is considered an input...
214            let d_in = left_out[sample_index];
215            let mut d_out = 0.0;
216
217            self.chorusbuf[self.counter as usize] = d_in;
218
219            for i in 0..(self.number_blocks as usize) {
220                let mut pos_subsamples = INTERPOLATION_SUBSAMPLES as i32 * self.counter
221                    - self.lookup_tab[self.phase[i] as usize];
222
223                let mut pos_samples = pos_subsamples / INTERPOLATION_SUBSAMPLES as i32;
224                pos_subsamples &= INTERPOLATION_SUBSAMPLES_ANDMASK as i32;
225
226                for ii in 0..INTERPOLATION_SAMPLES {
227                    d_out += self.chorusbuf[(pos_samples & MAX_SAMPLES_ANDMASK as i32) as usize]
228                        * self.sinc_table[ii][pos_subsamples as usize];
229                    pos_samples -= 1;
230                }
231
232                self.phase[i] += 1;
233                self.phase[i] %= self.modulation_period_samples;
234            }
235            d_out *= self.level;
236
237            left_out[sample_index] = d_out;
238            right_out[sample_index] = d_out;
239
240            self.counter += 1;
241            self.counter %= MAX_SAMPLES as i32;
242        }
243    }
244
245    pub fn reset(&mut self) {
246        self.init();
247    }
248}
249
250/// Chorus type
251#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
252#[repr(u32)]
253pub enum ChorusMode {
254    #[default]
255    Sine = 0,
256    Triangle = 1,
257}
258
259#[derive(Debug, Clone, Copy, PartialEq)]
260pub struct ChorusParams {
261    /// Chorus nr
262    pub nr: u32,
263    /// Chorus level
264    pub level: f32,
265    /// Speed in Hz
266    pub speed: f32,
267    /// Depth in mS
268    pub depth: f32,
269    /// Mode
270    pub mode: ChorusMode,
271}
272
273impl Default for ChorusParams {
274    fn default() -> Self {
275        Self {
276            nr: 3,
277            level: 2.0,
278            speed: 0.3,
279            depth: 8.0,
280            mode: ChorusMode::default(),
281        }
282    }
283}
284
285impl Chorus {
286    /// Set the current chorus nr
287    fn set_nr(&mut self, nr: u32) {
288        self.new_number_blocks = nr;
289    }
290
291    /// Query the current chorus nr
292    fn nr(&self) -> u32 {
293        self.number_blocks
294    }
295
296    /// Set the current chorus level
297    fn set_level(&mut self, level: f32) {
298        self.new_level = level;
299    }
300
301    /// Query the current chorus level
302    fn level(&self) -> f32 {
303        self.level
304    }
305
306    /// Set the current chorus speed (Hz)
307    fn set_speed_hz(&mut self, speed_hz: f32) {
308        self.new_speed_hz = speed_hz;
309    }
310
311    /// Query the current chorus speed (Hz)
312    fn speed_hz(&self) -> f32 {
313        self.speed_hz
314    }
315
316    /// Set the current chorus depth (mS)
317    fn set_depth_ms(&mut self, depth_ms: f32) {
318        self.new_depth_ms = depth_ms;
319    }
320
321    /// Query the current chorus depth (mS)
322    fn depth_ms(&self) -> f32 {
323        self.depth_ms
324    }
325
326    /// Set the current chorus mode
327    fn set_mode(&mut self, mode: ChorusMode) {
328        self.new_type = mode;
329    }
330
331    /// Query the current chorus mode
332    fn mode(&self) -> ChorusMode {
333        self.type_0
334    }
335}
336
337impl Chorus {
338    /// Set up the chorus. It should be turned on with Chorus::set_active().
339    /// If faulty parameters are given, all new settings are discarded.
340    /// Keep in mind, that the needed CPU time is proportional to `nr`.
341    pub fn set_params(&mut self, params: &ChorusParams) {
342        self.set_nr(params.nr);
343        self.set_level(params.level);
344        self.set_speed_hz(params.speed);
345        self.set_depth_ms(params.depth);
346        self.set_mode(params.mode);
347        self.update();
348    }
349
350    /// Query the current chorus params
351    pub fn params(&self) -> ChorusParams {
352        ChorusParams {
353            nr: self.nr(),
354            level: self.level(),
355            speed: self.speed_hz(),
356            depth: self.depth_ms(),
357            mode: self.mode(),
358        }
359    }
360}
361
362fn modulate_sine(buf: &mut [i32], len: usize, depth: i32) {
363    for (i, out) in buf.iter_mut().take(len).enumerate() {
364        let val = f64::sin(i as f64 / len as f64 * 2.0 * std::f64::consts::PI);
365        *out = ((1.0 + val) * depth as f64 / 2.0 * INTERPOLATION_SUBSAMPLES as f64) as i32;
366        *out -= 3 * MAX_SAMPLES as i32 * INTERPOLATION_SUBSAMPLES as i32;
367    }
368}
369
370fn modulate_triangle(buf: &mut [i32], len: i32, depth: i32) {
371    let mut i = 0;
372    let mut ii = len - 1;
373    while i <= ii {
374        let val = i as f64 * 2.0 / len as f64 * depth as f64 * INTERPOLATION_SUBSAMPLES as f64;
375        let val2 = (val + 0.5) as i32 - 3 * MAX_SAMPLES as i32 * INTERPOLATION_SUBSAMPLES as i32;
376        let fresh2 = i;
377        i += 1;
378        buf[fresh2 as usize] = val2;
379        let fresh3 = ii;
380        ii -= 1;
381        buf[fresh3 as usize] = val2;
382    }
383}