use caw_builder_proc_macros::builder;
use caw_core::{Buf, Filter, SigCtx, SigT};
use itertools::izip;
builder! {
#[constructor = "compressor"]
#[constructor_doc = "Soft clamps a signal within a configurable threshold"]
#[generic_setter_type_name = "X"]
pub struct Props {
#[generic_with_constraint = "SigT<Item = f32>"]
#[generic_name = "T"]
#[default = 1.0]
threshold: f32,
#[generic_with_constraint = "SigT<Item = f32>"]
#[generic_name = "R"]
#[default = 0.0]
ratio: f32,
#[generic_with_constraint = "SigT<Item = f32>"]
#[generic_name = "S"]
#[default = 1.0]
scale: f32,
}
}
impl<T, R, S> Filter for Props<T, R, S>
where
T: SigT<Item = f32>,
R: SigT<Item = f32>,
S: SigT<Item = f32>,
{
type ItemIn = f32;
type Out<I>
= Compressor<I, T, R, S>
where
I: SigT<Item = Self::ItemIn>;
fn into_sig<I>(self, sig: I) -> Self::Out<I>
where
I: SigT<Item = Self::ItemIn>,
{
Compressor {
props: self,
sig,
buf: Vec::new(),
}
}
}
pub struct Compressor<I, T, R, S>
where
I: SigT<Item = f32>,
T: SigT<Item = f32>,
R: SigT<Item = f32>,
S: SigT<Item = f32>,
{
props: Props<T, R, S>,
sig: I,
buf: Vec<f32>,
}
impl<I, T, R, S> SigT for Compressor<I, T, R, S>
where
I: SigT<Item = f32>,
T: SigT<Item = f32>,
R: SigT<Item = f32>,
S: SigT<Item = f32>,
{
type Item = f32;
fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
self.buf.resize(ctx.num_samples, 0.0);
let sig = self.sig.sample(ctx);
let threshold = self.props.threshold.sample(ctx);
let ratio = self.props.ratio.sample(ctx);
let scale = self.props.scale.sample(ctx);
for (out, sample, threshold, ratio, scale) in izip! {
self.buf.iter_mut(),
sig.iter(),
threshold.iter(),
ratio.iter(),
scale.iter(),
} {
let sample = sample * scale;
let sample_abs = sample.abs();
*out = if sample_abs > threshold {
let delta = sample_abs - threshold;
let delta_scaled = delta * ratio;
(threshold + delta_scaled) * sample.signum()
} else {
sample
};
}
&self.buf
}
}