Skip to main content

devela/num/signal/
transform.rs

1// devela/src/num/signal/transform.rs
2//
3//! Signal mapping, modulation, and signal-to-signal transformation.
4//
5// TOC:
6// - struct SignalClamp
7// - struct SignalMap
8// - struct SignalScale
9// - struct SignalZip
10
11use crate::{Mul, SignalAt, SignalNext};
12
13#[doc = crate::_tags!(num signal)]
14/// Clamps each sample to `[min, max]`.
15#[doc = crate::_doc_meta!{
16    location("num/signal", struct SignalClamp),
17}]
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
19pub struct SignalClamp<S, T> {
20    /// The signal whose samples are clamped.
21    pub signal: S,
22    /// The minimum allowed sample value.
23    pub min: T,
24    /// The maximum allowed sample value.
25    pub max: T,
26}
27#[rustfmt::skip]
28impl<S, T> SignalClamp<S, T> {
29    /// Creates a signal adapter that clamps samples to `[min, max]`.
30    pub const fn new(signal: S, min: T, max: T) -> Self {
31        Self { signal, min, max }
32    }
33    fn clamp_value(&self, v: T) -> T where T: Copy + PartialOrd {
34        if v < self.min { self.min } else if v > self.max { self.max } else { v }
35    }
36}
37impl<S, T> SignalNext for SignalClamp<S, T>
38where
39    S: SignalNext<Sample = T>,
40    T: Copy + PartialOrd,
41{
42    type Sample = T;
43    fn next(&mut self) -> T {
44        let v = self.signal.next();
45        self.clamp_value(v)
46    }
47}
48impl<X, S, T> SignalAt<X> for SignalClamp<S, T>
49where
50    S: SignalAt<X, Sample = T>,
51    T: Copy + PartialOrd,
52{
53    type Sample = T;
54    fn at(&self, x: X) -> T {
55        self.clamp_value(self.signal.at(x))
56    }
57}
58
59#[doc = crate::_tags!(num signal)]
60/// Maps each sample through a function.
61#[doc = crate::_doc_meta!{
62    location("num/signal", struct SignalMap),
63}]
64#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
65pub struct SignalMap<S, F> {
66    /// The signal whose samples are transformed.
67    pub signal: S,
68    /// The mapping function applied to each sample.
69    pub f: F,
70}
71impl<S, F> SignalMap<S, F> {
72    /// Creates a signal adapter that maps each sample through `f`.
73    pub const fn new(signal: S, f: F) -> Self {
74        Self { signal, f }
75    }
76}
77impl<S, F, O> SignalNext for SignalMap<S, F>
78where
79    S: SignalNext,
80    F: FnMut(S::Sample) -> O,
81{
82    type Sample = O;
83    fn next(&mut self) -> O {
84        (self.f)(self.signal.next())
85    }
86}
87impl<X, S, F, O> SignalAt<X> for SignalMap<S, F>
88where
89    S: SignalAt<X>,
90    F: Fn(S::Sample) -> O,
91{
92    type Sample = O;
93    fn at(&self, x: X) -> O {
94        (self.f)(self.signal.at(x))
95    }
96}
97
98#[doc = crate::_tags!(num signal)]
99/// Multiplies each sample by a fixed factor.
100#[doc = crate::_doc_meta!{
101    location("num/signal", struct SignalScale),
102}]
103#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
104pub struct SignalScale<S, K> {
105    /// The signal whose samples are scaled.
106    pub signal: S,
107    /// The factor multiplied with each sample.
108    pub factor: K,
109}
110impl<S, K> SignalScale<S, K> {
111    /// Creates a signal adapter that multiplies each sample by `factor`.
112    pub const fn new(signal: S, factor: K) -> Self {
113        Self { signal, factor }
114    }
115}
116impl<S, K> SignalNext for SignalScale<S, K>
117where
118    S: SignalNext,
119    S::Sample: Mul<K, Output = S::Sample>,
120    K: Copy,
121{
122    type Sample = S::Sample;
123    fn next(&mut self) -> Self::Sample {
124        self.signal.next() * self.factor
125    }
126}
127impl<X, S, K> SignalAt<X> for SignalScale<S, K>
128where
129    S: SignalAt<X>,
130    S::Sample: Mul<K, Output = S::Sample>,
131    K: Copy,
132{
133    type Sample = S::Sample;
134    fn at(&self, x: X) -> Self::Sample {
135        self.signal.at(x) * self.factor
136    }
137}
138
139#[doc = crate::_tags!(num signal)]
140/// Combines two signals sample-by-sample.
141#[doc = crate::_doc_meta!{
142    location("num/signal", struct SignalZip),
143}]
144#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
145pub struct SignalZip<A, B, F> {
146    /// The first input signal.
147    pub a: A,
148    /// The second input signal.
149    pub b: B,
150    /// The combining function applied to paired samples.
151    pub f: F,
152}
153impl<A, B, F> SignalZip<A, B, F> {
154    /// Creates a signal adapter that combines paired samples from two signals.
155    pub const fn new(a: A, b: B, f: F) -> Self {
156        Self { a, b, f }
157    }
158}
159impl<A, B, F, O> SignalNext for SignalZip<A, B, F>
160where
161    A: SignalNext,
162    B: SignalNext,
163    F: FnMut(A::Sample, B::Sample) -> O,
164{
165    type Sample = O;
166    fn next(&mut self) -> O {
167        (self.f)(self.a.next(), self.b.next())
168    }
169}
170impl<X, A, B, F, O> SignalAt<X> for SignalZip<A, B, F>
171where
172    X: Copy,
173    A: SignalAt<X>,
174    B: SignalAt<X>,
175    F: Fn(A::Sample, B::Sample) -> O,
176{
177    type Sample = O;
178
179    fn at(&self, x: X) -> O {
180        (self.f)(self.a.at(x), self.b.at(x))
181    }
182}