wavegen/
macros.rs

1/// Helper macro to construct [`Waveform`] instance.
2///
3/// # Double precision
4///
5/// This macro does **not** support creating double precision waveforms. Use the [`Waveform`] methods directly in case you need it.
6///
7/// # Panics
8///
9/// This macro will cause panic if sampling rate is not a finite, positive, non-zero number.
10///
11/// # Examples
12///
13/// ```
14/// use wavegen::{wf, sine, square};
15///
16/// let empty_waveform = wf!(f32, 16000.);
17/// assert_eq!(0, empty_waveform.components().len());
18///
19/// let sine_waveform = wf!(f64, 44100., sine!(50.));
20/// assert_eq!(1, sine_waveform.components().len());
21///
22/// let some_other_waveform = wf!(i64, 22000., sine!(100.), square!(200.));
23/// assert_eq!(2, some_other_waveform.components().len());
24/// ```
25///
26/// [`Waveform`]: struct.waveform.html
27#[macro_export]
28macro_rules! wf {
29    ($sample_type:ty, $sample_rate:expr) => {
30        $crate::Waveform::<$sample_type>::new($sample_rate)
31    };
32    ($sample_type:ty, $sample_rate:expr, $($comp:expr),+ $(,)?) => {
33        {
34            extern crate alloc;
35            let __wf = $crate::Waveform::<$sample_type>::with_components($sample_rate, alloc::vec![$($comp,)+]);
36
37            __wf
38        }
39    };
40}
41
42/// Builder macro for DC Bias [`PeriodicFunction`].
43///
44/// Takes just one argument - the bias value.
45///
46/// # Examples
47///
48/// Defines bias of amplitude +10
49/// ```
50/// let bias: wavegen::PeriodicFunction<f32> = wavegen::dc_bias!(10.);
51///
52/// assert!((0..1000i16).all(|x| bias.sample(x.into()) == 10.0))
53/// ```
54///
55/// [`PeriodicFunction`]: type.periodicfunction.html
56/// [`Waveform`]: type.waveform.html
57#[macro_export]
58macro_rules! dc_bias {
59    ($bias:expr) => {
60        $crate::PeriodicFunction::dc_bias($bias)
61    };
62}
63
64/// Builder macro for Sawtooth [`PeriodicFunction`].
65///
66/// Takes up to 3 arguments - frequency {amplitude, {phase}}
67///
68/// | argument | unit | notes |
69/// | -------- | ---- | ----- |
70/// | frequency | Hz | Frequecy of the periodic function. Also: 1 / period |
71/// | amplitude | *arbitrary* | The amplitude of the function in 0-peak notation. |
72/// | phase | *periods* | The phase shift of the function. Value of 1 means full shift around.
73///
74/// [`PeriodicFunction`]: type.periodicfunction.html
75#[macro_export]
76macro_rules! sawtooth {
77    ($frequency:expr) => {
78        $crate::sawtooth!($frequency, 1.0, 0.0)
79    };
80    (frequency: $frequency:expr) => {
81        $crate::sawtooth!($frequency)
82    };
83    ($frequency:expr, $amplitude:expr) => {
84        $crate::sawtooth!($frequency, $amplitude, 0.0)
85    };
86    (frequency: $frequency:expr, amplitude: $amplitude:expr) => {
87        $crate::sawtooth!($frequency, $amplitude)
88    };
89    (frequency: $frequency:expr, amplitude: $amplitude:expr, phase: $phase:expr) => {
90        $crate::sawtooth!($frequency, $amplitude, 0.0)
91    };
92    ($frequency:expr, $amplitude:expr, $phase:expr) => {
93        $crate::PeriodicFunction::sawtooth($frequency, $amplitude, $phase)
94    };
95}
96
97/// Builder macro for Sine [`PeriodicFunction`].
98///
99/// Takes up to 3 arguments - frequency {amplitude, {phase}}
100///
101/// | argument | unit | notes |
102/// | -------- | ---- | ----- |
103/// | frequency | Hz | Frequecy of the periodic function. Also: 1 / period |
104/// | amplitude | *arbitrary* | The amplitude of the function in 0-peak notation. |
105/// | phase | *periods* | The phase shift of the function. Value of 1 means full shift around.
106///
107/// # Examples
108///
109/// 50 Hz sine of amplitude 1 and no phase shift
110/// ```
111/// let sine: wavegen::PeriodicFunction<f32> = wavegen::sine!(50.);
112/// ```
113///
114/// 50 Hz sine of amplitude 20 and no phase shift
115/// ```
116/// let sine: wavegen::PeriodicFunction<f32> = wavegen::sine!(frequency: 50., amplitude: 20.);
117/// ```
118///
119/// 50 Hz sine of amplitude 20 and phase shift of half a turn
120/// ```
121/// let sine: wavegen::PeriodicFunction<f32> = wavegen::sine!(50.   , 20., 0.5);
122/// ```
123///
124/// [`PeriodicFunction`]: type.periodicfunction.html
125#[macro_export]
126macro_rules! sine {
127    (frequency: $frequency:expr) => {
128        $crate::sine!($frequency)
129    };
130    (frequency: $frequency:expr, amplitude: $amplitude:expr) => {
131        $crate::sine!($frequency, $amplitude)
132    };
133    (frequency: $frequency:expr, amplitude: $amplitude:expr, phase: $phase:expr) => {
134        $crate::sine!($frequency, $amplitude, $phase)
135    };
136    ($frequency:expr) => {
137        $crate::sine!($frequency, 1.0, 0.0)
138    };
139    ($frequency:expr, $amplitude:expr) => {
140        $crate::sine!($frequency, $amplitude, 0.0)
141    };
142    ($frequency:expr, $amplitude:expr, $phase:expr) => {
143        $crate::PeriodicFunction::sine($frequency, $amplitude, $phase)
144    };
145}
146
147/// Builder macro for Square [`PeriodicFunction`].
148///
149/// Takes up to 3 arguments - frequency {amplitude, {phase}}
150///
151/// | argument | unit | notes |
152/// | -------- | ---- | ----- |
153/// | frequency | Hz | Frequecy of the periodic function. Also: 1 / period |
154/// | amplitude | *arbitrary* | The amplitude of the function in 0-peak notation. |
155/// | phase | *periods* | The phase shift of the function. Value of 1 means full shift around.
156///
157/// [`PeriodicFunction`]: type.periodicfunction.html
158#[macro_export]
159macro_rules! square {
160    (frequency: $frequency:expr) => {
161        $crate::square!($frequency)
162    };
163    (frequency: $frequency:expr, amplitude: $amplitude:expr) => {
164        $crate::square!($frequency, $amplitude)
165    };
166    (frequency: $frequency:expr, amplitude: $amplitude:expr, phase: $phase:expr) => {
167        $crate::square!($frequency, $amplitude, 0.0)
168    };
169    ($frequency:expr) => {
170        $crate::square!($frequency, 1.0, 0.0)
171    };
172    ($frequency:expr, $amplitude:expr) => {
173        $crate::square!($frequency, $amplitude, 0.0)
174    };
175    ($frequency:expr, $amplitude:expr, $phase:expr) => {
176        $crate::PeriodicFunction::square($frequency, $amplitude, $phase)
177    };
178}
179
180#[cfg(test)]
181mod tests {
182    use float_cmp::approx_eq;
183
184    use crate::PeriodicFunction;
185
186    const EPS: f64 = 1e-3;
187
188    #[test]
189    fn empty_waveform_has_zero_components() {
190        let wf = wf!(f64, 44100.);
191        assert_eq!(0, wf.components().len());
192    }
193
194    #[test]
195    fn wavefrom_with_one_component() {
196        let wf = wf!(f64, 44100., sine!(500.));
197        assert_eq!(1, wf.components().len());
198    }
199    #[test]
200    fn wavefrom_with_three_components() {
201        let wf = wf!(f64, 44100., sine!(500.), square!(1000.), sawtooth!(1500.));
202        assert_eq!(3, wf.components().len());
203    }
204
205    #[test]
206    fn dc_bias_is_const_for_any_input() {
207        let y = 42.0;
208        let dc: PeriodicFunction<f64> = dc_bias!(y);
209        for x in (0..10000000).map(|x| x.into()) {
210            assert_eq!(dc.sample(x), y);
211        }
212    }
213
214    #[test]
215    fn default_sawtooth_has_amplitude_of_one() {
216        let f = sawtooth!(2.0);
217
218        assert!(approx_eq!(f64, f.sample(0.49999), 1.0, epsilon = EPS));
219        assert!(approx_eq!(f64, f.sample(0.5), -1.0, epsilon = EPS));
220    }
221
222    #[test]
223    fn default_sine_has_amplitude_of_one_and_no_phase_shift() {
224        let sine = sine!(1);
225
226        let max = sine.sample(0.25);
227        let min = sine.sample(0.75);
228        let zero = sine.sample(0.5);
229
230        assert!(approx_eq!(f64, max, 1.0, epsilon = EPS));
231        assert!(approx_eq!(f64, min, -1.0, epsilon = EPS));
232        assert!(approx_eq!(f64, zero, 0.0, epsilon = EPS));
233    }
234
235    #[test]
236    fn sine_phase_affects_min_max_amplitude_position() {
237        let sine = sine!(1, 1, 0.5);
238
239        let max = sine.sample(0.75);
240        let min = sine.sample(0.25);
241        let zero = sine.sample(0.5);
242
243        assert!(approx_eq!(f64, max, 1.0, epsilon = EPS));
244        assert!(approx_eq!(f64, min, -1.0, epsilon = EPS));
245        assert!(approx_eq!(f64, zero, 0.0, epsilon = EPS));
246    }
247
248    #[test]
249    fn default_square_has_amplitude_of_one() {
250        let square = square!(1);
251
252        for x in [0.0, 0.1, 0.2, 0.3, 0.4] {
253            assert!(approx_eq!(f64, square.sample(x), 1.0, epsilon = EPS))
254        }
255
256        for x in [0.5, 0.6, 0.7, 0.8, 0.9] {
257            assert!(approx_eq!(f64, square.sample(x), -1.0, epsilon = EPS))
258        }
259    }
260}