pointillism/effects/
distortion.rs

1//! Defines different kinds of signal distortion.
2//!
3//! No new signal structs are defined in this file. Instead, we define new initiailzations for
4//! [`eff::PwMapSgn`].
5
6use crate::prelude::*;
7
8/// Infinite clipping distortion.
9///
10/// Maps positive values to `1.0`, negative values to `-1.0`.
11#[derive(Clone, Copy, Debug, Default)]
12pub struct InfClip;
13
14impl Map for InfClip {
15    type Input = f64;
16    type Output = f64;
17
18    fn eval(&self, x: f64) -> f64 {
19        x.signum()
20    }
21}
22
23impl<S: Signal> eff::PwMapSgn<S, InfClip> {
24    /// Applies [`InfClip`] distortion to a signal.
25    pub const fn inf_clip(sgn: S) -> Self {
26        Self::new_pw(sgn, InfClip)
27    }
28}
29
30/// Clipping distortion.
31///
32/// Clamps all values between `-threshold` and `threshold`, and normalizes.
33#[derive(Clone, Copy, Debug)]
34pub struct Clip {
35    /// The threshold for clipping.
36    pub threshold: f64,
37}
38
39impl Clip {
40    /// Initializes a new [`Clip`] struct.
41    #[must_use]
42    pub const fn new(threshold: f64) -> Self {
43        Self { threshold }
44    }
45}
46
47impl Default for Clip {
48    fn default() -> Self {
49        Self::new(1.0)
50    }
51}
52
53impl Map for Clip {
54    type Input = f64;
55    type Output = f64;
56
57    fn eval(&self, x: f64) -> f64 {
58        x.clamp(-self.threshold, self.threshold) / self.threshold
59    }
60}
61
62impl<S: Signal> eff::PwMapSgn<S, Clip> {
63    /// Applies [`Clip`] distortion to a signal.
64    pub const fn clip(sgn: S, threshold: f64) -> Self {
65        Self::new_pw(sgn, Clip::new(threshold))
66    }
67}
68
69/// Arctangent distortion.
70///
71/// Applies the function `tan⁻¹(shape * x)` to the input signal and normalizes.
72#[derive(Clone, Copy, Debug)]
73pub struct Atan {
74    /// The shape of the distortion. Typically larger than `1.0`.
75    pub shape: f64,
76}
77
78impl Atan {
79    /// Initializes a new [`Atan`] struct.
80    #[must_use]
81    pub const fn new(shape: f64) -> Self {
82        Self { shape }
83    }
84}
85
86impl Default for Atan {
87    fn default() -> Self {
88        Self::new(1.0)
89    }
90}
91
92impl Map for Atan {
93    type Input = f64;
94    type Output = f64;
95
96    fn eval(&self, x: f64) -> f64 {
97        (self.shape * x).atan() / std::f64::consts::FRAC_PI_2
98    }
99}
100
101impl<S: Signal> eff::PwMapSgn<S, Atan> {
102    /// Applies [`Atan`] distortion to a signal.
103    pub const fn atan(sgn: S, shape: f64) -> Self {
104        Self::new_pw(sgn, Atan::new(shape))
105    }
106}
107
108/// The function `x^n`, renormalized for even exponents.
109#[derive(Clone, Copy, Debug)]
110pub struct Pow {
111    /// Exponent to raise a number to.
112    pub exponent: u16,
113}
114
115impl Pow {
116    /// Initializes a new [`Pow`].
117    #[must_use]
118    pub const fn new(exponent: u16) -> Self {
119        Self { exponent }
120    }
121
122    /// No distortion.
123    #[must_use]
124    pub const fn linear() -> Self {
125        Self::new(1)
126    }
127
128    /// Cubic distortion.
129    #[must_use]
130    pub const fn cubic() -> Self {
131        Self::new(3)
132    }
133}
134
135impl Default for Pow {
136    fn default() -> Self {
137        Self::linear()
138    }
139}
140
141impl Map for Pow {
142    type Input = f64;
143    type Output = f64;
144
145    fn eval(&self, x: f64) -> f64 {
146        let res = x.powi(i32::from(self.exponent));
147
148        if self.exponent % 2 == 0 {
149            map::sgn(res)
150        } else {
151            res
152        }
153    }
154}
155
156impl<S: Signal> eff::PwMapSgn<S, Pow> {
157    /// Applies [`Pow`] distortion to a signal.
158    pub const fn pow(sgn: S, exponent: u16) -> Self {
159        Self::new_pw(sgn, Pow::new(exponent))
160    }
161
162    /// Cubic distortion.
163    pub const fn cubic(sgn: S) -> Self {
164        Self::pow(sgn, 3)
165    }
166}
167
168// Todo: bitcrusher effect.