use std::{any::Any, f64::consts::PI};
use four_cc::FourCC;
use strum::VariantNames;
use crate::{
effect::{Effect, EffectMessage, EffectMessagePayload, EffectTime},
parameter::{
formatters, EnumParameter, EnumParameterValue, FloatParameter, ParameterValueUpdate,
SmoothedParameterValue,
},
utils::{
buffer::InterleavedBufferMut,
dsp::{
delay::InterpolatedDelayLine,
filters::svf::{SvfFilter, SvfFilterCoefficients, SvfFilterType},
lfo::{Lfo, LfoWaveform},
},
smoothing::LinearSmoothedValue,
},
Error, Parameter, ParameterScaling,
};
#[derive(Clone, Debug)]
#[allow(unused)]
pub enum ChorusEffectMessage {
Reset,
}
impl EffectMessage for ChorusEffectMessage {
fn effect_name(&self) -> &'static str {
ChorusEffect::EFFECT_NAME
}
fn payload(&self) -> &dyn Any {
self
}
}
pub type ChorusEffectFilterType = SvfFilterType;
pub struct ChorusEffect {
sample_rate: u32,
channel_count: usize,
rate: SmoothedParameterValue<LinearSmoothedValue>,
phase: SmoothedParameterValue<LinearSmoothedValue>,
depth: SmoothedParameterValue,
feedback: SmoothedParameterValue,
delay: SmoothedParameterValue<LinearSmoothedValue>,
wet_mix: SmoothedParameterValue,
filter_type: EnumParameterValue<ChorusEffectFilterType>,
filter_freq: SmoothedParameterValue,
filter_resonance: SmoothedParameterValue,
lfo_range: f32,
current_phase: f64,
left_osc: Lfo,
right_osc: Lfo,
delay_buffer_left: InterpolatedDelayLine<1>,
delay_buffer_right: InterpolatedDelayLine<1>,
filter_coefficients: SvfFilterCoefficients,
filter_left: SvfFilter,
filter_right: SvfFilter,
}
impl ChorusEffect {
pub const EFFECT_NAME: &str = "Chorus";
pub const RATE: FloatParameter = FloatParameter::new(
FourCC(*b"rate"),
"Rate",
0.01..=10.0,
1.0, )
.with_scaling(ParameterScaling::Exponential(2.0))
.with_unit("Hz");
pub const PHASE: FloatParameter = FloatParameter::new(
FourCC(*b"phas"), "Phase",
0.0..=PI as f32,
PI as f32 / 2.0,
)
.with_formatter(formatters::DEGREES);
pub const DEPTH: FloatParameter = FloatParameter::new(
FourCC(*b"dpth"),
"Depth",
0.0..=1.0,
0.25, )
.with_formatter(formatters::PERCENT);
pub const FEEDBACK: FloatParameter = FloatParameter::new(
FourCC(*b"fdbk"),
"Feedback",
-1.0..=1.0,
0.5, )
.with_formatter(formatters::PERCENT);
pub const DELAY: FloatParameter = FloatParameter::new(
FourCC(*b"dlay"),
"Delay",
0.0..=100.0,
12.0, )
.with_unit("ms");
pub const WET_MIX: FloatParameter = FloatParameter::new(
FourCC(*b"wet_"),
"Wet",
0.0..=1.0,
0.5, )
.with_formatter(formatters::PERCENT);
pub const FILTER_TYPE: EnumParameter = EnumParameter::new(
FourCC(*b"fltt"),
"Filter Type",
ChorusEffectFilterType::VARIANTS,
ChorusEffectFilterType::Lowpass as usize,
);
pub const FILTER_FREQ: FloatParameter = FloatParameter::new(
FourCC(*b"fltf"),
"Filter Freq",
20.0..=20000.0,
20000.0, )
.with_scaling(ParameterScaling::Exponential(2.5))
.with_unit("Hz");
pub const FILTER_RESONANCE: FloatParameter =
FloatParameter::new(FourCC(*b"fltq"), "Filter Resonance", 0.0..=1.0, 0.);
const MAX_APPLIED_RANGE_IN_SAMPLES: f32 = 256.0;
const MAX_APPLIED_DELAY_IN_MS: f32 = 100.0;
pub fn new() -> Self {
Self {
sample_rate: 0,
channel_count: 0,
rate: SmoothedParameterValue::from_description(Self::RATE) .with_smoother(LinearSmoothedValue::default().with_step(0.005)),
phase: SmoothedParameterValue::from_description(Self::PHASE)
.with_smoother(LinearSmoothedValue::default().with_step(0.001)),
depth: SmoothedParameterValue::from_description(Self::DEPTH),
feedback: SmoothedParameterValue::from_description(Self::FEEDBACK),
delay: SmoothedParameterValue::from_description(Self::DELAY)
.with_smoother(LinearSmoothedValue::default().with_step(0.01)),
wet_mix: SmoothedParameterValue::from_description(Self::WET_MIX),
filter_type: EnumParameterValue::from_description(Self::FILTER_TYPE),
filter_freq: SmoothedParameterValue::from_description(Self::FILTER_FREQ),
filter_resonance: SmoothedParameterValue::from_description(Self::FILTER_RESONANCE),
lfo_range: 0.0,
current_phase: 0.0,
left_osc: Lfo::default(),
right_osc: Lfo::default(),
delay_buffer_left: InterpolatedDelayLine::default(),
delay_buffer_right: InterpolatedDelayLine::default(),
filter_coefficients: SvfFilterCoefficients::default(),
filter_left: SvfFilter::default(),
filter_right: SvfFilter::default(),
}
}
#[allow(clippy::too_many_arguments)]
pub fn with_parameters(
rate: f32,
phase: f32,
depth: f32,
feedback: f32,
delay: f32,
wet_mix: f32,
filter_type: ChorusEffectFilterType,
filter_freq: f32,
filter_resonance: f32,
) -> Self {
let mut chorus = Self::default();
chorus.rate.init_value(rate);
chorus.phase.init_value(phase);
chorus.depth.init_value(depth);
chorus.feedback.init_value(feedback);
chorus.delay.init_value(delay);
chorus.wet_mix.init_value(wet_mix);
chorus.filter_type.set_value(filter_type);
chorus.filter_freq.init_value(filter_freq);
chorus.filter_resonance.init_value(filter_resonance);
chorus
}
fn reset(&mut self) {
self.delay_buffer_left.flush();
self.delay_buffer_right.flush();
self.filter_left.reset();
self.filter_right.reset();
self.rate.init_value(self.rate.target_value());
self.phase.init_value(self.phase.target_value());
self.current_phase = 0.0;
self.reset_lfos();
}
fn reset_lfos(&mut self) {
let rate = self.rate.current_value() as f64;
self.left_osc = Lfo::new(self.sample_rate, rate, LfoWaveform::Sine);
self.right_osc = Lfo::new(self.sample_rate, rate, LfoWaveform::Sine);
let phase_offset = self.phase.current_value() as f64;
self.left_osc.set_phase_degrees(self.current_phase as f32);
self.right_osc
.set_phase_degrees((self.current_phase + phase_offset) as f32);
}
fn update_lfos(&mut self) {
let rate = self.rate.next_value() as f64;
self.left_osc.set_rate(self.sample_rate, rate);
self.right_osc.set_rate(self.sample_rate, rate);
let phase_offset = self.phase.next_value() as f64;
self.left_osc.set_phase_degrees(self.current_phase as f32);
self.right_osc
.set_phase_degrees((self.current_phase + phase_offset) as f32);
}
}
impl Default for ChorusEffect {
fn default() -> Self {
Self::new()
}
}
impl Effect for ChorusEffect {
fn name(&self) -> &'static str {
Self::EFFECT_NAME
}
fn weight(&self) -> usize {
3
}
fn parameters(&self) -> Vec<&dyn Parameter> {
vec![
self.rate.description(),
self.depth.description(),
self.feedback.description(),
self.delay.description(),
self.wet_mix.description(),
self.phase.description(),
self.filter_type.description(),
self.filter_freq.description(),
self.filter_resonance.description(),
]
}
fn initialize(
&mut self,
sample_rate: u32,
channel_count: usize,
_max_frames: usize,
) -> Result<(), Error> {
self.sample_rate = sample_rate;
self.channel_count = channel_count;
if channel_count != 2 {
return Err(Error::ParameterError(
"ChorusEffect only supports stereo I/O".to_owned(),
));
}
self.rate.set_sample_rate(sample_rate);
self.phase.set_sample_rate(sample_rate);
self.depth.set_sample_rate(sample_rate);
self.feedback.set_sample_rate(sample_rate);
self.delay.set_sample_rate(sample_rate);
self.wet_mix.set_sample_rate(sample_rate);
self.filter_freq.set_sample_rate(sample_rate);
self.filter_resonance.set_sample_rate(sample_rate);
self.lfo_range = Self::MAX_APPLIED_RANGE_IN_SAMPLES * (self.sample_rate as f32 / 44100.0);
let max_depth_in_samples = self.lfo_range.ceil() as usize;
let max_delay_time_in_samples =
(Self::MAX_APPLIED_DELAY_IN_MS * self.sample_rate as f32 / 1000.0).ceil() as usize;
let max_buffer_size = 2 + max_delay_time_in_samples + 2 * max_depth_in_samples + 1;
self.delay_buffer_left = InterpolatedDelayLine::new(max_buffer_size);
self.delay_buffer_right = InterpolatedDelayLine::new(max_buffer_size);
self.filter_coefficients = SvfFilterCoefficients::new(
self.filter_type.value(),
sample_rate,
self.filter_freq.target_value(),
self.filter_resonance.target_value(),
)?;
self.reset();
Ok(())
}
fn process(&mut self, mut output: &mut [f32], _time: &EffectTime) {
assert!(self.channel_count == 2);
for frame in output.as_frames_mut::<2>() {
let left_input = frame[0];
let right_input = frame[1];
let delay_ms = self.delay.next_value();
let depth = self.depth.next_value();
let feedback = self.feedback.next_value().clamp(-0.999, 0.999);
let wet_mix = self.wet_mix.next_value();
let wet_amount = wet_mix;
let dry_amount = 1.0 - wet_mix;
if self.rate.value_need_ramp() || self.phase.value_need_ramp() {
self.update_lfos();
}
let (filtered_left, filtered_right) =
if self.filter_freq.value_need_ramp() || self.filter_resonance.value_need_ramp() {
let cutoff = self.filter_freq.next_value();
let resonance = self.filter_resonance.next_value();
self.filter_coefficients
.set(
self.filter_type.value(),
self.sample_rate,
cutoff,
resonance,
)
.expect("Failed to set chorus filter parameters");
let filtered_left = self
.filter_left
.process_sample(&self.filter_coefficients, left_input as f64);
let filtered_right = self
.filter_right
.process_sample(&self.filter_coefficients, right_input as f64);
(filtered_left, filtered_right)
} else {
let filtered_left = self
.filter_left
.process_sample(&self.filter_coefficients, left_input as f64);
let filtered_right = self
.filter_right
.process_sample(&self.filter_coefficients, right_input as f64);
(filtered_left, filtered_right)
};
let delay_in_samples = delay_ms * self.sample_rate as f32 * 0.001;
let depth_in_samples = self.lfo_range * depth;
let left_lfo = self.left_osc.run();
let right_lfo = self.right_osc.run();
let left_delay_pos = 2.0 + delay_in_samples + (1.0 + left_lfo) * depth_in_samples;
let right_delay_pos = 2.0 + delay_in_samples + (1.0 + right_lfo) * depth_in_samples;
let left_output =
self.delay_buffer_left
.process([filtered_left as f32], feedback, left_delay_pos)[0];
let right_output =
self.delay_buffer_right
.process([filtered_right as f32], feedback, right_delay_pos)[0];
let out_l = left_input * dry_amount + left_output * wet_amount;
let out_r = right_input * dry_amount + right_output * wet_amount;
frame[0] = out_l;
frame[1] = out_r;
}
let phase_inc = 2.0 * PI * self.rate.current_value() as f64 / self.sample_rate as f64;
self.current_phase += output.len() as f64 / self.channel_count as f64 * phase_inc;
while self.current_phase >= 2.0 * PI {
self.current_phase -= 2.0 * PI;
}
}
fn process_tail(&self) -> Option<usize> {
let delay_ms = self.delay.target_value();
let depth_ms = Self::MAX_APPLIED_RANGE_IN_SAMPLES * 1000.0 / self.sample_rate as f32;
let total_delay_ms = delay_ms + depth_ms;
let feedback = self.feedback.target_value().abs();
if feedback >= 1.0 {
Some(usize::MAX) } else if feedback < 0.001 {
Some((total_delay_ms * self.sample_rate as f32 / 1000.0).ceil() as usize)
} else {
const SILENCE: f64 = 0.001; let total_delay_samples = total_delay_ms * self.sample_rate as f32 / 1000.0;
let decay_time_samples = total_delay_samples
+ (total_delay_samples as f64 * SILENCE.log10() / (feedback as f64).log10()) as f32;
Some(decay_time_samples.ceil() as usize)
}
}
fn process_message(&mut self, message: &EffectMessagePayload) -> Result<(), Error> {
if let Some(message) = message.payload().downcast_ref::<ChorusEffectMessage>() {
match message {
ChorusEffectMessage::Reset => self.reset(),
}
Ok(())
} else {
Err(Error::ParameterError(
"ChorusEffect: Invalid/unknown message payload".to_owned(),
))
}
}
fn process_parameter_update(
&mut self,
id: FourCC,
value: &ParameterValueUpdate,
) -> Result<(), Error> {
match id {
_ if id == Self::RATE.id() => self.rate.apply_update(value),
_ if id == Self::PHASE.id() => self.phase.apply_update(value),
_ if id == Self::DEPTH.id() => self.depth.apply_update(value),
_ if id == Self::FEEDBACK.id() => self.feedback.apply_update(value),
_ if id == Self::DELAY.id() => self.delay.apply_update(value),
_ if id == Self::WET_MIX.id() => self.wet_mix.apply_update(value),
_ if id == Self::FILTER_TYPE.id() => self.filter_type.apply_update(value),
_ if id == Self::FILTER_FREQ.id() => self.filter_freq.apply_update(value),
_ if id == Self::FILTER_RESONANCE.id() => self.filter_resonance.apply_update(value),
_ => {
return Err(Error::ParameterError(format!(
"Unknown parameter: '{id}' for effect '{}'",
self.name()
)))
}
};
match id {
_ if id == Self::FILTER_TYPE.id() => self
.filter_coefficients
.set_filter_type(self.filter_type.value()),
_ => Ok(()),
}
}
}