Skip to main content

rill_digital_effects/
distortion.rs

1//! Distortion effect with waveshaping
2
3use rill_core::{
4    math::vector::scalar::ScalarVector4,
5    math::vector::traits::Vector,
6    math::Transcendental,
7    traits::{Node, NodeCategory, NodeMetadata, NodeState, Processor},
8    NodeId, ParamValue, ParameterId, Port, ProcessError, ProcessResult, RenderContext,
9};
10
11/// Distortion type
12#[derive(Debug, Clone, Copy, PartialEq)]
13pub enum DistortionType {
14    /// Hard clipping
15    HardClip,
16    /// Soft clipping (tanh)
17    SoftClip,
18    /// Tube-like saturation
19    Tube,
20    /// Fuzz (asymmetric)
21    Fuzz,
22}
23
24impl DistortionType {
25    /// Get all available types as strings
26    pub fn names() -> Vec<&'static str> {
27        vec!["hard_clip", "soft_clip", "tube", "fuzz"]
28    }
29
30    /// Get type from string
31    #[allow(clippy::should_implement_trait)]
32    pub fn from_str(s: &str) -> Option<Self> {
33        match s {
34            "hard_clip" => Some(DistortionType::HardClip),
35            "soft_clip" => Some(DistortionType::SoftClip),
36            "tube" => Some(DistortionType::Tube),
37            "fuzz" => Some(DistortionType::Fuzz),
38            _ => None,
39        }
40    }
41
42    /// Convert to string
43    pub fn as_str(&self) -> &'static str {
44        match self {
45            DistortionType::HardClip => "hard_clip",
46            DistortionType::SoftClip => "soft_clip",
47            DistortionType::Tube => "tube",
48            DistortionType::Fuzz => "fuzz",
49        }
50    }
51}
52
53/// Distortion effect
54///
55/// Parameters:
56/// - drive: input gain (1.0 - 100.0)
57/// - type: distortion type
58/// - output_gain: output level (0.0 - 2.0)
59pub struct Distortion<T: Transcendental, const BUF_SIZE: usize> {
60    /// Node identifier
61    id: NodeId,
62    /// Node metadata
63    metadata: NodeMetadata,
64    /// Input ports
65    inputs: Vec<Port<T, BUF_SIZE>>,
66    /// Output ports
67    outputs: Vec<Port<T, BUF_SIZE>>,
68    /// Control ports
69    controls: Vec<Port<T, BUF_SIZE>>,
70    /// Node state
71    state: NodeState<T, BUF_SIZE>,
72    /// Distortion type
73    pub distortion_type: DistortionType,
74    /// Drive (input gain)
75    pub drive: f32,
76    /// Output gain
77    pub output_gain: f32,
78    /// Sample rate (unused but required for Processor)
79    sample_rate: f32,
80}
81
82impl<T: Transcendental, const BUF_SIZE: usize> Distortion<T, BUF_SIZE> {
83    /// Create a new distortion effect with default parameters
84    pub fn new(sample_rate: f32) -> Self {
85        let metadata = NodeMetadata::new("Distortion", NodeCategory::Processor);
86
87        let mut inputs = Vec::new();
88        let mut outputs = Vec::new();
89
90        // Create one audio input and one audio output
91        inputs.push(Port::input(NodeId(0), 0, "signal_in"));
92        outputs.push(Port::output(NodeId(0), 0, "signal_out"));
93
94        Self {
95            id: NodeId(0),
96            metadata,
97            inputs,
98            outputs,
99            controls: Vec::new(),
100            state: NodeState::new(sample_rate),
101            distortion_type: DistortionType::SoftClip,
102            drive: 1.0,
103            output_gain: 1.0,
104            sample_rate,
105        }
106    }
107
108    /// Create a new distortion effect with custom parameters
109    pub fn with_params(
110        sample_rate: f32,
111        distortion_type: DistortionType,
112        drive: f32,
113        output_gain: f32,
114    ) -> Self {
115        let mut instance = Self::new(sample_rate);
116        instance.set_type(distortion_type);
117        instance.set_drive(drive);
118        instance.set_output_gain(output_gain);
119        instance
120    }
121
122    /// Set distortion type
123    pub fn set_type(&mut self, distortion_type: DistortionType) {
124        self.distortion_type = distortion_type;
125    }
126
127    /// Set drive
128    pub fn set_drive(&mut self, drive: f32) {
129        self.drive = drive.clamp(1.0, 100.0);
130    }
131
132    /// Set output gain
133    pub fn set_output_gain(&mut self, gain: f32) {
134        self.output_gain = gain.clamp(0.0, 2.0);
135    }
136
137    /// Process a single sample
138    pub fn process_sample(&self, input: T) -> T {
139        let driven = input.mul(T::from_f32(self.drive));
140
141        let distorted = match self.distortion_type {
142            DistortionType::HardClip => driven.clamp(T::MIN, T::MAX),
143            DistortionType::SoftClip => T::from_f32(driven.to_f32().tanh()),
144            DistortionType::Tube => {
145                // Tube-like saturation
146                if driven > T::ZERO {
147                    T::ONE - (-driven).exp()
148                } else {
149                    -T::ONE + driven.exp()
150                }
151            }
152            DistortionType::Fuzz => {
153                // Asymmetric fuzz
154                if driven > T::ZERO {
155                    T::ONE - T::ONE.div(T::ONE + driven)
156                } else {
157                    driven
158                }
159            }
160        };
161
162        distorted.mul(T::from_f32(self.output_gain))
163    }
164}
165
166impl<T: Transcendental, const BUF_SIZE: usize> Node<T, BUF_SIZE> for Distortion<T, BUF_SIZE> {
167    fn node_type_id(&self) -> rill_core::NodeTypeId
168    where
169        Self: 'static + Sized,
170    {
171        rill_core::NodeTypeId::of::<Self>()
172    }
173
174    fn id(&self) -> NodeId {
175        self.id
176    }
177
178    fn set_id(&mut self, id: NodeId) {
179        self.id = id;
180    }
181
182    fn metadata(&self) -> NodeMetadata {
183        self.metadata.clone()
184    }
185
186    fn init(&mut self, sample_rate: f32) {
187        self.sample_rate = sample_rate;
188    }
189
190    fn reset(&mut self) {
191        self.state.sample_pos = 0;
192        self.state.blocks_processed = 0;
193    }
194
195    fn get_parameter(&self, id: &ParameterId) -> Option<ParamValue> {
196        let name = id.as_str();
197        match name {
198            "type" => Some(ParamValue::Choice(
199                self.distortion_type.as_str().to_string(),
200            )),
201            "drive" => Some(ParamValue::Float(self.drive)),
202            "output_gain" => Some(ParamValue::Float(self.output_gain)),
203            _ => None,
204        }
205    }
206
207    fn set_parameter(&mut self, id: &ParameterId, value: ParamValue) -> ProcessResult<()> {
208        let name = id.as_str();
209        match name {
210            "type" => {
211                if let ParamValue::Choice(t) = value {
212                    if let Some(dt) = DistortionType::from_str(&t) {
213                        self.set_type(dt);
214                        Ok(())
215                    } else {
216                        Err(ProcessError::parameter("unknown distortion type"))
217                    }
218                } else {
219                    Err(ProcessError::parameter("expected Choice value"))
220                }
221            }
222            "drive" => {
223                if let Some(v) = value.as_f32() {
224                    self.set_drive(v);
225                    Ok(())
226                } else {
227                    Err(ProcessError::parameter("expected float value"))
228                }
229            }
230            "output_gain" => {
231                if let Some(v) = value.as_f32() {
232                    self.set_output_gain(v);
233                    Ok(())
234                } else {
235                    Err(ProcessError::parameter("expected float value"))
236                }
237            }
238            _ => Err(ProcessError::parameter("unknown parameter")),
239        }
240    }
241
242    fn input_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
243        self.inputs.get(index)
244    }
245
246    fn input_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
247        self.inputs.get_mut(index)
248    }
249
250    fn output_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
251        self.outputs.get(index)
252    }
253
254    fn output_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
255        self.outputs.get_mut(index)
256    }
257
258    fn control_port(&self, index: usize) -> Option<&Port<T, BUF_SIZE>> {
259        self.controls.get(index)
260    }
261
262    fn control_port_mut(&mut self, index: usize) -> Option<&mut Port<T, BUF_SIZE>> {
263        self.controls.get_mut(index)
264    }
265
266    fn num_inputs(&self) -> usize {
267        self.inputs.len()
268    }
269
270    fn num_outputs(&self) -> usize {
271        self.outputs.len()
272    }
273
274    fn num_signal_inputs(&self) -> usize {
275        self.inputs.len()
276    }
277
278    fn num_signal_outputs(&self) -> usize {
279        self.outputs.len()
280    }
281
282    fn state(&self) -> &NodeState<T, BUF_SIZE> {
283        &self.state
284    }
285
286    fn state_mut(&mut self) -> &mut NodeState<T, BUF_SIZE> {
287        &mut self.state
288    }
289}
290
291impl<T: Transcendental, const BUF_SIZE: usize> Processor<T, BUF_SIZE> for Distortion<T, BUF_SIZE> {
292    fn process(
293        &mut self,
294        _ctx: &RenderContext,
295        _signal_inputs: &[&[T; BUF_SIZE]],
296        _control_inputs: &[T],
297        _clock_inputs: &[RenderContext],
298        _feedback_inputs: &[&[T; BUF_SIZE]],
299    ) -> ProcessResult<()> {
300        let inp = self.inputs[0].read();
301        let out = self.outputs[0].write();
302        let drive_t = T::from_f32(self.drive);
303        let gain_t = T::from_f32(self.output_gain);
304        let chunks = BUF_SIZE / 4;
305
306        match self.distortion_type {
307            DistortionType::HardClip => {
308                let min = ScalarVector4::splat(T::MIN);
309                let max = ScalarVector4::splat(T::MAX);
310                for chunk in 0..chunks {
311                    let o = chunk * 4;
312                    let x = ScalarVector4::load(&inp[o..o + 4]);
313                    let d = x.mul(&ScalarVector4::splat(drive_t));
314                    let r = d.clamp(&min, &max);
315                    r.mul(&ScalarVector4::splat(gain_t))
316                        .store(&mut out[o..o + 4]);
317                }
318            }
319            DistortionType::SoftClip => {
320                for chunk in 0..chunks {
321                    let o = chunk * 4;
322                    let vals: [T; 4] = std::array::from_fn(|k| {
323                        let d = inp[o + k].mul(drive_t);
324                        let s = T::from_f32(d.to_f32().tanh());
325                        s.mul(gain_t)
326                    });
327                    out[o..o + 4].copy_from_slice(&vals);
328                }
329            }
330            DistortionType::Tube => {
331                for chunk in 0..chunks {
332                    let o = chunk * 4;
333                    let d_v = ScalarVector4::load(&[
334                        inp[o].mul(drive_t),
335                        inp[o + 1].mul(drive_t),
336                        inp[o + 2].mul(drive_t),
337                        inp[o + 3].mul(drive_t),
338                    ]);
339                    let vals = ScalarVector4::from_fn(|i| {
340                        let d = d_v.extract(i);
341                        if d > T::ZERO {
342                            T::ONE - (-d).exp()
343                        } else {
344                            -T::ONE + d.exp()
345                        }
346                    });
347                    vals.mul(&ScalarVector4::splat(gain_t))
348                        .store(&mut out[o..o + 4]);
349                }
350            }
351            DistortionType::Fuzz => {
352                for chunk in 0..chunks {
353                    let o = chunk * 4;
354                    let out_arr: [T; 4] = std::array::from_fn(|k| {
355                        let d = inp[o + k].mul(drive_t);
356                        let f = if d > T::ZERO {
357                            T::ONE - T::ONE.div(T::ONE + d)
358                        } else {
359                            d
360                        };
361                        f.mul(gain_t)
362                    });
363                    out[o..o + 4].copy_from_slice(&out_arr);
364                }
365            }
366        }
367
368        // Remainder (only when BUF_SIZE % 4 != 0)
369        let dt = self.distortion_type;
370        let dr = self.drive;
371        let og = self.output_gain;
372        for i in chunks * 4..BUF_SIZE {
373            let driven = inp[i].mul(T::from_f32(dr));
374            out[i] = match dt {
375                DistortionType::HardClip => driven.clamp(T::MIN, T::MAX),
376                DistortionType::SoftClip => T::from_f32(driven.to_f32().tanh()),
377                DistortionType::Tube => {
378                    if driven > T::ZERO {
379                        T::ONE - (-driven).exp()
380                    } else {
381                        -T::ONE + driven.exp()
382                    }
383                }
384                DistortionType::Fuzz => {
385                    if driven > T::ZERO {
386                        T::ONE - T::ONE.div(T::ONE + driven)
387                    } else {
388                        driven
389                    }
390                }
391            }
392            .mul(T::from_f32(og));
393        }
394
395        self.state.advance();
396        Ok(())
397    }
398
399    fn latency(&self) -> usize {
400        0
401    }
402}