caw_modules/
compressor.rs

1use caw_builder_proc_macros::builder;
2use caw_core::{Buf, Filter, SigCtx, SigT};
3use itertools::izip;
4
5builder! {
6    #[constructor = "compressor"]
7    #[constructor_doc = "Soft clamps a signal within a configurable threshold"]
8    #[generic_setter_type_name = "X"]
9    pub struct Props {
10        // If the input signal exceeds the threshold then the excess will be multiplied by the ratio.
11        #[generic_with_constraint = "SigT<Item = f32>"]
12        #[generic_name = "T"]
13        #[default = 1.0]
14        threshold: f32,
15        #[generic_with_constraint = "SigT<Item = f32>"]
16        #[generic_name = "R"]
17        #[default = 0.0]
18        ratio: f32,
19        // Pre-amplify the input by this amount.
20        #[generic_with_constraint = "SigT<Item = f32>"]
21        #[generic_name = "S"]
22        #[default = 1.0]
23        scale: f32,
24    }
25}
26
27impl<T, R, S> Filter for Props<T, R, S>
28where
29    T: SigT<Item = f32>,
30    R: SigT<Item = f32>,
31    S: SigT<Item = f32>,
32{
33    type ItemIn = f32;
34
35    type Out<I>
36        = Compressor<I, T, R, S>
37    where
38        I: SigT<Item = Self::ItemIn>;
39
40    fn into_sig<I>(self, sig: I) -> Self::Out<I>
41    where
42        I: SigT<Item = Self::ItemIn>,
43    {
44        Compressor {
45            props: self,
46            sig,
47            buf: Vec::new(),
48        }
49    }
50}
51
52pub struct Compressor<I, T, R, S>
53where
54    I: SigT<Item = f32>,
55    T: SigT<Item = f32>,
56    R: SigT<Item = f32>,
57    S: SigT<Item = f32>,
58{
59    props: Props<T, R, S>,
60    sig: I,
61    buf: Vec<f32>,
62}
63
64impl<I, T, R, S> SigT for Compressor<I, T, R, S>
65where
66    I: SigT<Item = f32>,
67    T: SigT<Item = f32>,
68    R: SigT<Item = f32>,
69    S: SigT<Item = f32>,
70{
71    type Item = f32;
72
73    fn sample(&mut self, ctx: &SigCtx) -> impl Buf<Self::Item> {
74        self.buf.resize(ctx.num_samples, 0.0);
75        let sig = self.sig.sample(ctx);
76        let threshold = self.props.threshold.sample(ctx);
77        let ratio = self.props.ratio.sample(ctx);
78        let scale = self.props.scale.sample(ctx);
79        for (out, sample, threshold, ratio, scale) in izip! {
80            self.buf.iter_mut(),
81            sig.iter(),
82            threshold.iter(),
83            ratio.iter(),
84            scale.iter(),
85        } {
86            let sample = sample * scale;
87            let sample_abs = sample.abs();
88            *out = if sample_abs > threshold {
89                let delta = sample_abs - threshold;
90                let delta_scaled = delta * ratio;
91                (threshold + delta_scaled) * sample.signum()
92            } else {
93                sample
94            };
95        }
96        &self.buf
97    }
98}