caw_modules/
chorus.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
use crate::{
    low_level::linearly_interpolating_ring_buffer::LinearlyInterpolatingRingBuffer,
    oscillator::{oscillator, Oscillator},
    Sine, Waveform,
};
use caw_builder_proc_macros::builder;
use caw_core::{
    sig_shared, Buf, Channel, Filter, Sig, SigCtx, SigShared, SigT,
};
use itertools::izip;

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ChorusLfoOffset {
    None,
    /// LFOs for voices on one channel will be at offsets interleaved with the voices from the
    /// other channel.
    Interleave(Channel),
}

builder! {
    #[constructor = "chorus"]
    #[constructor_doc = "Mix the signal with pitch-shifted copies of itself to produce interference patterns"]
    #[generic_setter_type_name = "X"]
    pub struct Props {
        #[generic_with_constraint = "SigT<Item = f32>"]
        #[generic_name = "R"]
        #[default = 1.0]
        lfo_rate_hz: f32,
        #[generic_with_constraint = "Waveform"]
        #[generic_name = "W"]
        #[default = Sine]
        lfo_waveform: Sine,
        #[default = ChorusLfoOffset::None]
        lfo_offset: ChorusLfoOffset,
        #[generic_with_constraint = "SigT<Item = bool>"]
        #[generic_name = "T"]
        #[default = false]
        lfo_reset_trig: bool,
        #[generic_with_constraint = "SigT<Item = f32>"]
        #[generic_name = "DL"]
        #[default = 0.02]
        delay_s: f32,
        #[generic_with_constraint = "SigT<Item = f32>"]
        #[generic_name = "DP"]
        #[default = 0.001]
        depth_s: f32,
        #[generic_with_constraint = "SigT<Item = f32>"]
        #[generic_name = "F"]
        #[default = 0.5]
        feedback_ratio: f32,
        #[generic_with_constraint = "SigT<Item = f32>"]
        #[generic_name = "M"]
        #[default = 0.5]
        mix_01: f32,
        #[default = 3]
        num_voices: usize,
    }
}

type Osc<W, F, T> = Oscillator<W, Sig<SigShared<F>>, f32, f32, SigShared<T>>;

pub struct Chorus<S, R, W, T, DL, DP, F, M>
where
    S: SigT<Item = f32>,
    R: SigT<Item = f32>,
    W: Waveform,
    T: SigT<Item = bool>,
    DL: SigT<Item = f32>,
    DP: SigT<Item = f32>,
    F: SigT<Item = f32>,
    M: SigT<Item = f32>,
{
    delay_s: DL,
    depth_s: DP,
    feedback_ratio: F,
    mix_01: M,
    buf: Vec<f32>,
    ring: LinearlyInterpolatingRingBuffer,
    lfos: Vec<Osc<W, R, T>>,
    lfo_bufs: Vec<Vec<f32>>,
    sig: S,
}

impl<R, W, T, DL, DP, F, M> Filter for Props<R, W, T, DL, DP, F, M>
where
    R: SigT<Item = f32>,
    W: Waveform,
    T: SigT<Item = bool>,
    DL: SigT<Item = f32>,
    DP: SigT<Item = f32>,
    F: SigT<Item = f32>,
    M: SigT<Item = f32>,
{
    type ItemIn = f32;

    type Out<S>
        = Chorus<S, R, W, T, DL, DP, F, M>
    where
        S: SigT<Item = Self::ItemIn>;

    fn into_sig<S>(self, sig: S) -> Self::Out<S>
    where
        S: SigT<Item = Self::ItemIn>,
    {
        let lfo_rate_hz = sig_shared(self.lfo_rate_hz);
        let lfo_reset_trig = sig_shared(self.lfo_reset_trig);
        let total_offset_01 = match self.lfo_offset {
            ChorusLfoOffset::None => 0.,
            ChorusLfoOffset::Interleave(Channel::Left) => 0.,
            ChorusLfoOffset::Interleave(Channel::Right) => {
                // half of the step between the offset of two voices
                0.5 / self.num_voices as f32
            }
        };
        let lfos: Vec<Osc<W, R, T>> = (0..self.num_voices)
            .map(|i| {
                let offset_01 =
                    total_offset_01 + ((i as f32) / self.num_voices as f32);
                oscillator(self.lfo_waveform, lfo_rate_hz.clone())
                    .reset_offset_01(offset_01)
                    .reset_trig(lfo_reset_trig.clone().0)
                    .build()
                    .0
            })
            .collect::<Vec<_>>();
        let lfo_bufs: Vec<Vec<f32>> =
            (0..self.num_voices).map(|_| Vec::new()).collect::<Vec<_>>();
        Chorus {
            delay_s: self.delay_s,
            depth_s: self.depth_s,
            feedback_ratio: self.feedback_ratio,
            mix_01: self.mix_01,
            buf: Vec::new(),
            ring: LinearlyInterpolatingRingBuffer::new(44100),
            lfos,
            lfo_bufs,
            sig,
        }
    }
}

impl<S, R, W, T, DL, DP, F, M> SigT for Chorus<S, R, W, T, DL, DP, F, M>
where
    S: SigT<Item = f32>,
    R: SigT<Item = f32>,
    W: Waveform,
    T: SigT<Item = bool>,
    DL: SigT<Item = f32>,
    DP: SigT<Item = f32>,
    F: SigT<Item = f32>,
    M: SigT<Item = f32>,
{
    type Item = f32;

    fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
        // Pre-compute all the oscillators values for the current frame. The number of oscillators
        // is not statically known so it can't be zipped along with the other signals in the next
        // loop.
        for (lfo, lfo_buf) in self.lfos.iter_mut().zip(self.lfo_bufs.iter_mut())
        {
            lfo.sample(ctx).clone_to_vec(lfo_buf);
        }
        self.buf.resize_with(ctx.num_samples, Default::default);
        let sig = self.sig.sample(ctx);
        let delay_s = self.delay_s.sample(ctx);
        let depth_s = self.depth_s.sample(ctx);
        let mix_01 = self.mix_01.sample(ctx);
        let feedback_ratio = self.feedback_ratio.sample(ctx);
        for (i, (out, sample, delay_s, depth_s, mix_01, feedback_ratio)) in
            izip! {
                self.buf.iter_mut(),
                sig.iter(),
                delay_s.iter(),
                depth_s.iter(),
                mix_01.iter(),
                feedback_ratio.iter(),
            }
            .enumerate()
        {
            let mut total_output = 0.;
            for lfo_buf in &self.lfo_bufs {
                let lfo_sample = lfo_buf[i];
                // `delay_s` is delay to the peak of the lfo. `lfo_sample * depth_s` will be
                // between `depth_s` and `-depth_s`. If we added `depth_s` to the lfo then it would
                // oscillate between 0 and `2 * depth_s`. Hence we need to add `delay_s + depth_s`
                // to the lfo.
                let total_delay_s = (lfo_sample * depth_s) + depth_s + delay_s;
                let index = total_delay_s * ctx.sample_rate_hz;
                let output_for_current_lfo = self.ring.query_resizing(index);
                total_output += output_for_current_lfo;
            }
            let mean_output = total_output / self.lfos.len() as f32;
            self.ring
                .insert((sample * mix_01) + (mean_output * feedback_ratio));
            *out = mean_output + (sample * (1.0 - mix_01));
        }
        &self.buf
    }
}