caw_modules/
chorus.rs

1use crate::{
2    low_level::linearly_interpolating_ring_buffer::LinearlyInterpolatingRingBuffer,
3    oscillator::{oscillator, Oscillator},
4    Sine, Waveform,
5};
6use caw_builder_proc_macros::builder;
7use caw_core::{
8    sig_shared, Buf, Channel, Filter, Sig, SigCtx, SigShared, SigT,
9};
10use itertools::izip;
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ChorusLfoOffset {
14    None,
15    /// LFOs for voices on one channel will be at offsets interleaved with the voices from the
16    /// other channel.
17    Interleave(Channel),
18}
19
20builder! {
21    #[constructor = "chorus"]
22    #[constructor_doc = "Mix the signal with pitch-shifted copies of itself to produce interference patterns"]
23    #[generic_setter_type_name = "X"]
24    pub struct Props {
25        #[generic_with_constraint = "SigT<Item = f32>"]
26        #[generic_name = "R"]
27        #[default = 1.0]
28        lfo_rate_hz: f32,
29        #[generic_with_constraint = "Waveform"]
30        #[generic_name = "W"]
31        #[default = Sine]
32        lfo_waveform: Sine,
33        #[default = ChorusLfoOffset::None]
34        lfo_offset: ChorusLfoOffset,
35        #[generic_with_constraint = "SigT<Item = bool>"]
36        #[generic_name = "T"]
37        #[default = false]
38        lfo_reset_trig: bool,
39        #[generic_with_constraint = "SigT<Item = f32>"]
40        #[generic_name = "DL"]
41        #[default = 0.02]
42        delay_s: f32,
43        #[generic_with_constraint = "SigT<Item = f32>"]
44        #[generic_name = "DP"]
45        #[default = 0.001]
46        depth_s: f32,
47        #[generic_with_constraint = "SigT<Item = f32>"]
48        #[generic_name = "F"]
49        #[default = 0.5]
50        feedback_ratio: f32,
51        #[generic_with_constraint = "SigT<Item = f32>"]
52        #[generic_name = "M"]
53        #[default = 0.5]
54        mix_01: f32,
55        #[default = 3]
56        num_voices: usize,
57    }
58}
59
60type Osc<W, F, T> = Oscillator<W, Sig<SigShared<F>>, f32, f32, SigShared<T>>;
61
62pub struct Chorus<S, R, W, T, DL, DP, F, M>
63where
64    S: SigT<Item = f32>,
65    R: SigT<Item = f32>,
66    W: Waveform,
67    T: SigT<Item = bool>,
68    DL: SigT<Item = f32>,
69    DP: SigT<Item = f32>,
70    F: SigT<Item = f32>,
71    M: SigT<Item = f32>,
72{
73    delay_s: DL,
74    depth_s: DP,
75    feedback_ratio: F,
76    mix_01: M,
77    buf: Vec<f32>,
78    ring: LinearlyInterpolatingRingBuffer,
79    lfos: Vec<Osc<W, R, T>>,
80    lfo_bufs: Vec<Vec<f32>>,
81    sig: S,
82}
83
84impl<R, W, T, DL, DP, F, M> Filter for Props<R, W, T, DL, DP, F, M>
85where
86    R: SigT<Item = f32>,
87    W: Waveform,
88    T: SigT<Item = bool>,
89    DL: SigT<Item = f32>,
90    DP: SigT<Item = f32>,
91    F: SigT<Item = f32>,
92    M: SigT<Item = f32>,
93{
94    type ItemIn = f32;
95
96    type Out<S>
97        = Chorus<S, R, W, T, DL, DP, F, M>
98    where
99        S: SigT<Item = Self::ItemIn>;
100
101    fn into_sig<S>(self, sig: S) -> Self::Out<S>
102    where
103        S: SigT<Item = Self::ItemIn>,
104    {
105        let lfo_rate_hz = sig_shared(self.lfo_rate_hz);
106        let lfo_reset_trig = sig_shared(self.lfo_reset_trig);
107        let total_offset_01 = match self.lfo_offset {
108            ChorusLfoOffset::None => 0.,
109            ChorusLfoOffset::Interleave(Channel::Left) => 0.,
110            ChorusLfoOffset::Interleave(Channel::Right) => {
111                // half of the step between the offset of two voices
112                0.5 / self.num_voices as f32
113            }
114        };
115        let lfos: Vec<Osc<W, R, T>> = (0..self.num_voices)
116            .map(|i| {
117                let offset_01 =
118                    total_offset_01 + ((i as f32) / self.num_voices as f32);
119                oscillator(self.lfo_waveform, lfo_rate_hz.clone())
120                    .reset_offset_01(offset_01)
121                    .reset_trig(lfo_reset_trig.clone().0)
122                    .build()
123                    .0
124            })
125            .collect::<Vec<_>>();
126        let lfo_bufs: Vec<Vec<f32>> =
127            (0..self.num_voices).map(|_| Vec::new()).collect::<Vec<_>>();
128        Chorus {
129            delay_s: self.delay_s,
130            depth_s: self.depth_s,
131            feedback_ratio: self.feedback_ratio,
132            mix_01: self.mix_01,
133            buf: Vec::new(),
134            ring: LinearlyInterpolatingRingBuffer::new(44100),
135            lfos,
136            lfo_bufs,
137            sig,
138        }
139    }
140}
141
142impl<S, R, W, T, DL, DP, F, M> SigT for Chorus<S, R, W, T, DL, DP, F, M>
143where
144    S: SigT<Item = f32>,
145    R: SigT<Item = f32>,
146    W: Waveform,
147    T: SigT<Item = bool>,
148    DL: SigT<Item = f32>,
149    DP: SigT<Item = f32>,
150    F: SigT<Item = f32>,
151    M: SigT<Item = f32>,
152{
153    type Item = f32;
154
155    fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
156        // Pre-compute all the oscillators values for the current frame. The number of oscillators
157        // is not statically known so it can't be zipped along with the other signals in the next
158        // loop.
159        for (lfo, lfo_buf) in self.lfos.iter_mut().zip(self.lfo_bufs.iter_mut())
160        {
161            lfo.sample(ctx).clone_to_vec(lfo_buf);
162        }
163        self.buf.resize_with(ctx.num_samples, Default::default);
164        let sig = self.sig.sample(ctx);
165        let delay_s = self.delay_s.sample(ctx);
166        let depth_s = self.depth_s.sample(ctx);
167        let mix_01 = self.mix_01.sample(ctx);
168        let feedback_ratio = self.feedback_ratio.sample(ctx);
169        for (i, (out, sample, delay_s, depth_s, mix_01, feedback_ratio)) in
170            izip! {
171                self.buf.iter_mut(),
172                sig.iter(),
173                delay_s.iter(),
174                depth_s.iter(),
175                mix_01.iter(),
176                feedback_ratio.iter(),
177            }
178            .enumerate()
179        {
180            let mut total_output = 0.;
181            for lfo_buf in &self.lfo_bufs {
182                let lfo_sample = lfo_buf[i];
183                // `delay_s` is delay to the peak of the lfo. `lfo_sample * depth_s` will be
184                // between `depth_s` and `-depth_s`. If we added `depth_s` to the lfo then it would
185                // oscillate between 0 and `2 * depth_s`. Hence we need to add `delay_s + depth_s`
186                // to the lfo.
187                let total_delay_s = (lfo_sample * depth_s) + depth_s + delay_s;
188                let index = total_delay_s * ctx.sample_rate_hz;
189                let output_for_current_lfo = self.ring.query_resizing(index);
190                total_output += output_for_current_lfo;
191            }
192            let mean_output = total_output / self.lfos.len() as f32;
193            self.ring
194                .insert((sample * mix_01) + (mean_output * feedback_ratio));
195            *out = mean_output + (sample * (1.0 - mix_01));
196        }
197        &self.buf
198    }
199}