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 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 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 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 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}