Skip to main content

pictorus_blocks/core_blocks/
squarewave_block.rs

1use crate::traits::Float;
2use pictorus_block_data::BlockData;
3use pictorus_traits::GeneratorBlock;
4
5pub struct Parameters<T: Float> {
6    pub amplitude: T,
7    pub on_duration: T,
8    pub off_duration: T,
9    pub phase: T,
10    pub bias: T,
11}
12
13impl<T: Float> Parameters<T> {
14    pub fn new(amplitude: T, on_duration: T, off_duration: T, phase: T, bias: T) -> Self {
15        Self {
16            amplitude,
17            on_duration,
18            off_duration,
19            phase,
20            bias,
21        }
22    }
23}
24
25/// Outputs a square wave signal with specified amplitude, on duration, off duration, phase, and bias.
26pub struct SquarewaveBlock<T: Float> {
27    phantom_output_type: core::marker::PhantomData<T>,
28    pub data: BlockData,
29}
30
31impl<T: Float> Default for SquarewaveBlock<T>
32where
33    f64: From<T>,
34{
35    fn default() -> Self {
36        Self {
37            phantom_output_type: core::marker::PhantomData,
38            data: BlockData::from_scalar(T::zero().into()),
39        }
40    }
41}
42
43impl<T> GeneratorBlock for SquarewaveBlock<T>
44where
45    T: Float,
46    f64: From<T>,
47{
48    type Output = T;
49    type Parameters = Parameters<T>;
50
51    fn generate(
52        &mut self,
53        parameters: &Self::Parameters,
54        context: &dyn pictorus_traits::Context,
55    ) -> pictorus_traits::PassBy<Self::Output> {
56        let adjusted_time = Self::Output::from_duration(context.time()) - parameters.phase;
57        let pulse_time = parameters.on_duration + parameters.off_duration;
58        let mut time_since_last_pulse_start: Self::Output = adjusted_time % pulse_time;
59
60        if time_since_last_pulse_start < T::zero() {
61            // Adjust for negative phase
62            time_since_last_pulse_start += pulse_time
63        };
64
65        let output = if time_since_last_pulse_start > parameters.on_duration {
66            parameters.bias
67        } else {
68            parameters.bias + parameters.amplitude
69        };
70        self.data = BlockData::from_scalar(f64::from(output));
71        output
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use crate::testing::StubRuntime;
78
79    use super::*;
80    use core::time::Duration;
81
82    #[test]
83    fn test_squarewave_block_f64() {
84        let amplitude = 2.0;
85        let on_duration = 1.0;
86        let off_duration = 2.0;
87        let bias = 0.25;
88        let phase = 0.5;
89
90        let p = Parameters::new(amplitude, on_duration, off_duration, phase, bias);
91
92        let mut block = SquarewaveBlock::<f64>::default();
93
94        let mut runtime = StubRuntime::default();
95
96        block.generate(&p, &runtime.context());
97        assert_eq!(block.generate(&p, &runtime.context()), bias);
98        assert_eq!(block.data.scalar(), bias);
99
100        runtime.set_time(Duration::from_millis(500));
101        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
102        assert_eq!(block.data.scalar(), bias + amplitude);
103
104        runtime.set_time(Duration::from_secs_f64(1.0));
105        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
106        assert_eq!(block.data.scalar(), bias + amplitude);
107
108        runtime.set_time(Duration::from_secs_f64(1.499));
109        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
110        assert_eq!(block.data.scalar(), bias + amplitude);
111
112        runtime.set_time(Duration::from_secs_f64(1.5));
113        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
114        assert_eq!(block.data.scalar(), bias + amplitude);
115
116        // Off duration
117        runtime.set_time(Duration::from_secs_f64(2.5));
118        assert_eq!(block.generate(&p, &runtime.context()), bias);
119        assert_eq!(block.data.scalar(), bias);
120
121        runtime.set_time(Duration::from_secs_f64(3.499));
122        assert_eq!(block.generate(&p, &runtime.context()), bias);
123        assert_eq!(block.data.scalar(), bias);
124
125        // Back on
126        runtime.set_time(Duration::from_secs_f64(3.5));
127        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
128        assert_eq!(block.data.scalar(), bias + amplitude);
129    }
130
131    #[test]
132    fn test_squarewave_block_f32() {
133        let amplitude = 2.0;
134        let on_duration = 1.0;
135        let off_duration = 2.0;
136        let bias = 0.5;
137        let phase = 0.5;
138
139        let p = Parameters::new(amplitude, on_duration, off_duration, phase, bias);
140
141        let mut block = SquarewaveBlock::<f32>::default();
142
143        let mut runtime = StubRuntime::default();
144
145        block.generate(&p, &runtime.context());
146        assert_eq!(block.generate(&p, &runtime.context()), bias);
147        assert_eq!(block.data.scalar() as f32, bias);
148
149        runtime.set_time(Duration::from_millis(500));
150        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
151        assert_eq!(block.data.scalar() as f32, bias + amplitude);
152
153        runtime.set_time(Duration::from_secs_f32(1.0));
154        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
155        assert_eq!(block.data.scalar() as f32, bias + amplitude);
156
157        runtime.set_time(Duration::from_secs_f32(1.499));
158        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
159        assert_eq!(block.data.scalar() as f32, bias + amplitude);
160
161        runtime.set_time(Duration::from_secs_f32(1.5));
162        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
163        assert_eq!(block.data.scalar() as f32, bias + amplitude);
164
165        // Off duration
166        runtime.set_time(Duration::from_secs_f32(2.5));
167        assert_eq!(block.generate(&p, &runtime.context()), bias);
168        assert_eq!(block.data.scalar() as f32, bias);
169
170        runtime.set_time(Duration::from_secs_f32(3.499));
171        assert_eq!(block.generate(&p, &runtime.context()), bias);
172        assert_eq!(block.data.scalar() as f32, bias);
173
174        // Back on
175        runtime.set_time(Duration::from_secs_f32(3.5));
176        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
177        assert_eq!(block.data.scalar() as f32, bias + amplitude);
178    }
179
180    #[test]
181    fn test_squarewave_phase() {
182        // Shout out to Jason for spotting this gap in testing
183        let amplitude = 1.0;
184        let on_duration = 1.0;
185        let off_duration = 2.0;
186        let bias = 0.0;
187        let phase = 1.5;
188
189        let mut p = Parameters::new(amplitude, on_duration, off_duration, phase, bias);
190
191        let mut block = SquarewaveBlock::<f32>::default();
192
193        let runtime = StubRuntime::default();
194
195        p.phase = 1.5;
196        block.generate(&p, &runtime.context());
197        assert_eq!(block.generate(&p, &runtime.context()), bias);
198
199        p.phase = 0.0;
200        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
201
202        p.phase = 0.5;
203        assert_eq!(block.generate(&p, &runtime.context()), bias);
204
205        p.phase = 2.5;
206        assert_eq!(block.generate(&p, &runtime.context()), bias + amplitude);
207
208        // No Phase Shift:
209        //
210        //-----               |--------               ----------
211        //                    |
212        //                    |
213        //     ---------------|        ----------------
214        //-----------------------------------------------------
215        //   -2      -1       0       1       2       3       4
216
217        // 2.5 Phase Shift:
218        //
219        //-----           ----|----                ----------
220        //                    |
221        //                    |
222        //     -----------    |    ----------------
223        //-----------------------------------------------------
224        //   -2      -1       0       1       2       3       4
225    }
226
227    #[test]
228    fn test_squarewave_bias() {
229        let amplitude = 1.0;
230        let on_duration = 1.0;
231        let off_duration = 2.0;
232        let bias = 1.0;
233        let phase = 0.0;
234
235        let p = Parameters::new(amplitude, on_duration, off_duration, phase, bias);
236        let mut block = SquarewaveBlock::<f32>::default();
237
238        let mut runtime = StubRuntime::default();
239
240        let output = block.generate(&p, &runtime.context());
241        assert_eq!(output, bias + amplitude);
242
243        runtime.set_time(Duration::from_secs_f32(1.5));
244        let output = block.generate(&p, &runtime.context());
245        assert_eq!(output, bias);
246    }
247}