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,
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) => {
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> {
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];
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
}
}