use four_cc::FourCC;
use crate::{
effect::{Effect, EffectTime},
parameter::{
formatters, FloatParameter, FloatParameterValue, ParameterValueUpdate,
SmoothedParameterValue,
},
utils::{
buffer::{copy_buffers, InterleavedBuffer, InterleavedBufferMut},
db_to_linear,
dsp::{delay::LookupDelayLine, envelope::EnvelopeFollower},
},
Error, Parameter,
};
pub struct CompressorEffect {
sample_rate: u32,
channel_count: usize,
threshold: FloatParameterValue,
ratio: FloatParameterValue,
knee_width: FloatParameterValue,
attack_time: FloatParameterValue,
release_time: FloatParameterValue,
makeup_gain: SmoothedParameterValue,
lookahead_time: FloatParameterValue,
envelope_follower: EnvelopeFollower,
input_buffer: Vec<f32>,
delay_line: LookupDelayLine<2>,
}
impl CompressorEffect {
pub const EFFECT_NAME: &str = "Compressor";
pub const THRESHOLD: FloatParameter = FloatParameter::new(
FourCC(*b"thrs"),
"Threshold",
-60.0..=0.0,
-12.0, )
.with_unit("dB");
pub const RATIO: FloatParameter = FloatParameter::new(
FourCC(*b"rato"),
"Ratio",
1.0..=20.0,
8.0, )
.with_formatter(formatters::RATIO);
pub const KNEE_WIDTH: FloatParameter = FloatParameter::new(
FourCC(*b"knee"), "Knee",
0.0..=12.0,
3.0,
);
pub const ATTACK_TIME: FloatParameter = FloatParameter::new(
FourCC(*b"attk"),
"Attack",
0.001..=0.5,
0.02, )
.with_unit("ms");
pub const RELEASE_TIME: FloatParameter = FloatParameter::new(
FourCC(*b"rels"),
"Release",
0.1..=2.0,
2.0, )
.with_unit("ms");
pub const MAKEUP_GAIN: FloatParameter = FloatParameter::new(
FourCC(*b"gain"),
"Makeup Gain",
-24.0..=24.0,
6.0, )
.with_unit("dB");
pub const LOOKAHEAD_TIME: FloatParameter = FloatParameter::new(
FourCC(*b"look"),
"Lookahead",
0.001..=0.2,
0.04, )
.with_unit("ms");
const DEFAULT_LIMITER_THRESHOLD: f32 = -0.01;
pub fn new_compressor() -> Self {
Self {
sample_rate: 0,
channel_count: 0,
threshold: FloatParameterValue::from_description(Self::THRESHOLD),
ratio: FloatParameterValue::from_description(Self::RATIO),
knee_width: FloatParameterValue::from_description(Self::KNEE_WIDTH),
attack_time: FloatParameterValue::from_description(Self::ATTACK_TIME),
release_time: FloatParameterValue::from_description(Self::RELEASE_TIME),
makeup_gain: SmoothedParameterValue::from_description(Self::MAKEUP_GAIN),
lookahead_time: FloatParameterValue::from_description(Self::LOOKAHEAD_TIME),
envelope_follower: EnvelopeFollower::default(),
input_buffer: Vec::new(),
delay_line: LookupDelayLine::<2>::default(),
}
}
pub fn new_limiter() -> Self {
let effect = Self::default();
let attack = effect.attack_time.description().default_value();
let release = effect.release_time.description().default_value();
Self::with_limiter_parameters(Self::DEFAULT_LIMITER_THRESHOLD, attack, release)
}
pub fn with_compressor_parameters(
threshold: f32,
ratio: f32,
knee_width: f32,
attack_time: f32,
release_time: f32,
makeup_gain: f32,
lookahead_time: f32,
) -> Self {
let mut compressor = Self::default();
compressor.threshold.set_value(threshold);
compressor.ratio.set_value(ratio);
compressor.knee_width.set_value(knee_width);
compressor.attack_time.set_value(attack_time);
compressor.release_time.set_value(release_time);
compressor.makeup_gain.init_value(makeup_gain);
compressor.lookahead_time.set_value(lookahead_time);
compressor
}
pub fn with_limiter_parameters(threshold: f32, attack_time: f32, release_time: f32) -> Self {
let ratio = 20.0;
let knee_width = 0.0;
let makeup_gain = 0.0;
let lookahead_time = attack_time;
Self::with_compressor_parameters(
threshold,
ratio,
knee_width,
attack_time,
release_time,
makeup_gain,
lookahead_time,
)
}
fn update_envelope_follower(&mut self) {
if self.sample_rate > 0 {
self.envelope_follower
.set_attack_time(self.attack_time.value());
self.envelope_follower
.set_release_time(self.release_time.value());
}
}
}
impl Default for CompressorEffect {
fn default() -> Self {
Self::new_compressor()
}
}
impl Effect for CompressorEffect {
fn name(&self) -> &'static str {
Self::EFFECT_NAME
}
fn weight(&self) -> usize {
4
}
fn parameters(&self) -> Vec<&dyn Parameter> {
vec![
self.threshold.description(),
self.ratio.description(),
self.knee_width.description(),
self.attack_time.description(),
self.release_time.description(),
self.makeup_gain.description(),
self.lookahead_time.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(
"CompressorEffect only supports stereo I/O".to_string(),
));
}
self.makeup_gain.set_sample_rate(sample_rate);
self.input_buffer = vec![0.0; max_frames * channel_count];
self.delay_line = LookupDelayLine::new(sample_rate, self.lookahead_time.value());
self.envelope_follower = EnvelopeFollower::new(
sample_rate,
self.attack_time.value(),
self.release_time.value(),
);
let initial_envelope = if self.ratio.value() >= 20.0 {
-120.0
} else {
0.0
};
self.envelope_follower.reset(initial_envelope);
Ok(())
}
fn process(&mut self, mut output: &mut [f32], _time: &EffectTime) {
assert!(self.channel_count == 2);
let input = &mut self.input_buffer[..output.len()];
copy_buffers(input, output);
let input_frames = input.as_frames::<2>();
for (out_frame, in_frame) in output.as_frames_mut::<2>().iter_mut().zip(input_frames) {
let delayed_frame = self.delay_line.process(in_frame);
let input_db = if self.ratio.value() >= 20.0 {
let lookahead_peak = self.delay_line.peak_value();
if lookahead_peak > 1e-6 {
20.0 * lookahead_peak.log10()
} else {
-120.0
}
} else {
let frame_peak = in_frame[0].abs().max(in_frame[1].abs());
if frame_peak > 1e-6 {
20.0 * frame_peak.log10()
} else {
-120.0
}
};
let envelope = self.envelope_follower.run(input_db);
let t = self.threshold.value();
let w = self.knee_width.value();
let slope = if self.ratio.value() >= 20.0 {
1.0
} else {
1.0 - 1.0 / self.ratio.value()
};
let gr_db = if w > 0.0 && envelope > (t - w / 2.0) && envelope < (t + w / 2.0) {
let knee_lower = t - w / 2.0;
let x = (envelope - knee_lower) / w;
x * x * slope * w / 2.0
} else if envelope > (t + w / 2.0) {
(envelope - t) * slope
} else {
0.0
};
let makeup_gain = self.makeup_gain.next_value();
let total_gain_db = makeup_gain - gr_db;
let total_gain = db_to_linear(total_gain_db);
out_frame[0] = delayed_frame[0] * total_gain;
out_frame[1] = delayed_frame[1] * total_gain;
}
}
fn process_tail(&self) -> Option<usize> {
let lookahead_samples =
(self.lookahead_time.value() * self.sample_rate as f32).ceil() as usize;
let release_samples = (self.release_time.value() * self.sample_rate as f32).ceil() as usize;
Some(lookahead_samples + release_samples)
}
fn process_parameter_update(
&mut self,
id: FourCC,
value: &ParameterValueUpdate,
) -> Result<(), Error> {
let old_lookahead = self.lookahead_time.value();
match id {
_ if id == Self::THRESHOLD.id() => self.threshold.apply_update(value),
_ if id == Self::RATIO.id() => self.ratio.apply_update(value),
_ if id == Self::KNEE_WIDTH.id() => self.knee_width.apply_update(value),
_ if id == Self::ATTACK_TIME.id() => self.attack_time.apply_update(value),
_ if id == Self::RELEASE_TIME.id() => self.release_time.apply_update(value),
_ if id == Self::MAKEUP_GAIN.id() => self.makeup_gain.apply_update(value),
_ if id == Self::LOOKAHEAD_TIME.id() => self.lookahead_time.apply_update(value),
_ => {
return Err(Error::ParameterError(format!(
"Unknown parameter: '{id}' for effect '{}'",
self.name()
)))
}
}
self.update_envelope_follower();
if self.lookahead_time.value() != old_lookahead && self.sample_rate > 0 {
self.delay_line = LookupDelayLine::new(self.sample_rate, self.lookahead_time.value());
}
Ok(())
}
}