synfx_dsp/
trig_clock.rs

1// Copyright (c) 2021-2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of HexoDSP. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5///! Contains various utilities for trigger signals in a modular synthesizer.
6///
7/// There are also clock synchronizing helpers in here like [TriggerPhaseClock]
8/// or [TriggerSampleClock].
9
10/// A-100 Eurorack states, that a trigger is usually 2-10 milliseconds.
11pub const TRIG_SIGNAL_LENGTH_MS: f32 = 2.0;
12
13/// The lower threshold for the schmidt trigger to reset.
14pub const TRIG_LOW_THRES: f32 = 0.25;
15/// The threshold, once reached, will cause a trigger event and signals
16/// a logical '1'. Anything below this is a logical '0'.
17pub const TRIG_HIGH_THRES: f32 = 0.5;
18
19/// Gate signal generator for HexoDSP nodes.
20///
21/// This generator generates a gate signal when [GateSignal::trigger] is called.
22/// The length is given as parameter to [GateSignal::next].
23#[derive(Debug, Clone, Copy)]
24pub struct GateSignal {
25    ms_per_sample: f32,
26    ms_count: f32,
27}
28
29impl GateSignal {
30    /// Create a new gate generator
31    pub fn new() -> Self {
32        Self { ms_per_sample: 1000.0 / 44100.0, ms_count: 0.0 }
33    }
34
35    /// Reset the gate generator.
36    pub fn reset(&mut self) {
37        self.ms_count = 0.0;
38    }
39
40    /// Set the sample rate
41    pub fn set_sample_rate(&mut self, srate: f32) {
42        self.ms_per_sample = 1000.0 / srate;
43    }
44
45    /// Start a new gate the next time [TrigSignal::next] is called.
46    #[inline]
47    pub fn trigger(&mut self) {
48        self.ms_count = 0.0001;
49    }
50
51    /// Gate signal output, the length is given via 'length_ms'.
52    #[inline]
53    pub fn next(&mut self, length_ms: f32) -> f32 {
54        if self.ms_count > 0.0 {
55            self.ms_count += self.ms_per_sample;
56            if (self.ms_count - 0.0001) > length_ms {
57                self.ms_count = 0.0;
58            }
59            1.0
60        } else {
61            0.0
62        }
63    }
64}
65
66impl Default for GateSignal {
67    fn default() -> Self {
68        Self::new()
69    }
70}
71
72/// Trigger signal generator for HexoDSP nodes.
73///
74/// A trigger in HexoSynth and HexoDSP is commonly 2.0 milliseconds.
75/// This generator generates a trigger signal when [TrigSignal::trigger] is called.
76#[derive(Debug, Clone, Copy)]
77pub struct TrigSignal {
78    length: u32,
79    scount: u32,
80}
81
82impl TrigSignal {
83    /// Create a new trigger generator
84    pub fn new() -> Self {
85        Self { length: ((44100.0 * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32, scount: 0 }
86    }
87
88    /// Reset the trigger generator.
89    pub fn reset(&mut self) {
90        self.scount = 0;
91    }
92
93    /// Set the sample rate to calculate the amount of samples for the trigger signal.
94    pub fn set_sample_rate(&mut self, srate: f32) {
95        self.length = ((srate * TRIG_SIGNAL_LENGTH_MS) / 1000.0).ceil() as u32;
96        self.scount = 0;
97    }
98
99    /// Enable sending a trigger impulse the next time [TrigSignal::next] is called.
100    #[inline]
101    pub fn trigger(&mut self) {
102        self.scount = self.length;
103    }
104
105    /// Trigger signal output.
106    #[inline]
107    pub fn next(&mut self) -> f32 {
108        if self.scount > 0 {
109            self.scount -= 1;
110            1.0
111        } else {
112            0.0
113        }
114    }
115}
116
117impl Default for TrigSignal {
118    fn default() -> Self {
119        Self::new()
120    }
121}
122
123/// Signal change detector that emits a trigger when the input signal changed.
124///
125/// This is commonly used for control signals. It has not much use for audio signals.
126#[derive(Debug, Clone, Copy)]
127pub struct ChangeTrig {
128    ts: TrigSignal,
129    last: f32,
130}
131
132impl ChangeTrig {
133    /// Create a new change detector
134    pub fn new() -> Self {
135        Self {
136            ts: TrigSignal::new(),
137            last: -100.0, // some random value :-)
138        }
139    }
140
141    /// Reset internal state.
142    pub fn reset(&mut self) {
143        self.ts.reset();
144        self.last = -100.0;
145    }
146
147    /// Set the sample rate for the trigger signal generator
148    pub fn set_sample_rate(&mut self, srate: f32) {
149        self.ts.set_sample_rate(srate);
150    }
151
152    /// Feed a new input signal sample.
153    ///
154    /// The return value is the trigger signal.
155    #[inline]
156    pub fn next(&mut self, inp: f32) -> f32 {
157        if (inp - self.last).abs() > std::f32::EPSILON {
158            self.ts.trigger();
159            self.last = inp;
160        }
161
162        self.ts.next()
163    }
164}
165
166impl Default for ChangeTrig {
167    fn default() -> Self {
168        Self::new()
169    }
170}
171
172/// Trigger signal detector for HexoDSP.
173///
174/// Whenever you need to detect a trigger on an input you can use this component.
175/// A trigger in HexoDSP is any signal over [TRIG_HIGH_THRES]. The internal state is
176/// resetted when the signal drops below [TRIG_LOW_THRES].
177#[derive(Debug, Clone, Copy)]
178pub struct Trigger {
179    triggered: bool,
180}
181
182impl Trigger {
183    /// Create a new trigger detector.
184    pub fn new() -> Self {
185        Self { triggered: false }
186    }
187
188    /// Reset the internal state of the trigger detector.
189    #[inline]
190    pub fn reset(&mut self) {
191        self.triggered = false;
192    }
193
194    /// Checks the input signal for a trigger and returns true when the signal
195    /// surpassed [TRIG_HIGH_THRES] and has not fallen below [TRIG_LOW_THRES] yet.
196    #[inline]
197    pub fn check_trigger(&mut self, input: f32) -> bool {
198        if self.triggered {
199            if input <= TRIG_LOW_THRES {
200                self.triggered = false;
201            }
202
203            false
204        } else if input > TRIG_HIGH_THRES {
205            self.triggered = true;
206            true
207        } else {
208            false
209        }
210    }
211}
212
213/// Trigger signal detector with custom range.
214///
215/// Whenever you need to detect a trigger with a custom threshold.
216#[derive(Debug, Clone, Copy)]
217pub struct CustomTrigger {
218    triggered: bool,
219    low_thres: f32,
220    high_thres: f32,
221}
222
223impl CustomTrigger {
224    /// Create a new trigger detector.
225    pub fn new(low_thres: f32, high_thres: f32) -> Self {
226        Self { triggered: false, low_thres, high_thres }
227    }
228
229    pub fn set_threshold(&mut self, low_thres: f32, high_thres: f32) {
230        self.low_thres = low_thres;
231        self.high_thres = high_thres;
232    }
233
234    /// Reset the internal state of the trigger detector.
235    #[inline]
236    pub fn reset(&mut self) {
237        self.triggered = false;
238    }
239
240    /// Checks the input signal for a trigger and returns true when the signal
241    /// surpassed the high threshold and has not fallen below low threshold yet.
242    #[inline]
243    pub fn check_trigger(&mut self, input: f32) -> bool {
244        //        println!("TRIG CHECK: {} <> {}", input, self.high_thres);
245        if self.triggered {
246            if input <= self.low_thres {
247                self.triggered = false;
248            }
249
250            false
251        } else if input > self.high_thres {
252            self.triggered = true;
253            true
254        } else {
255            false
256        }
257    }
258}
259
260/// Generates a phase signal from a trigger/gate input signal.
261///
262/// This helper allows you to measure the distance between trigger or gate pulses
263/// and generates a phase signal for you that increases from 0.0 to 1.0.
264#[derive(Debug, Clone, Copy)]
265pub struct TriggerPhaseClock {
266    clock_phase: f64,
267    clock_inc: f64,
268    prev_trigger: bool,
269    clock_samples: u32,
270}
271
272impl TriggerPhaseClock {
273    /// Create a new phase clock.
274    pub fn new() -> Self {
275        Self { clock_phase: 0.0, clock_inc: 0.0, prev_trigger: true, clock_samples: 0 }
276    }
277
278    /// Reset the phase clock.
279    #[inline]
280    pub fn reset(&mut self) {
281        self.clock_samples = 0;
282        self.clock_inc = 0.0;
283        self.prev_trigger = true;
284        self.clock_samples = 0;
285    }
286
287    /// Restart the phase clock. It will count up from 0.0 again on [TriggerPhaseClock::next_phase].
288    #[inline]
289    pub fn sync(&mut self) {
290        self.clock_phase = 0.0;
291    }
292
293    /// Generate the phase signal of this clock.
294    ///
295    /// * `clock_limit` - The maximum number of samples to detect two trigger signals in.
296    /// * `trigger_in` - Trigger signal input.
297    #[inline]
298    pub fn next_phase(&mut self, clock_limit: f64, trigger_in: f32) -> f64 {
299        if self.prev_trigger {
300            if trigger_in <= TRIG_LOW_THRES {
301                self.prev_trigger = false;
302            }
303        } else if trigger_in > TRIG_HIGH_THRES {
304            self.prev_trigger = true;
305
306            if self.clock_samples > 0 {
307                self.clock_inc = 1.0 / (self.clock_samples as f64);
308            }
309
310            self.clock_samples = 0;
311        }
312
313        self.clock_samples += 1;
314
315        self.clock_phase += self.clock_inc;
316        self.clock_phase = self.clock_phase % clock_limit;
317
318        self.clock_phase
319    }
320}
321
322#[derive(Debug, Clone, Copy)]
323pub struct TriggerSampleClock {
324    prev_trigger: bool,
325    clock_samples: u32,
326    counter: u32,
327}
328
329impl TriggerSampleClock {
330    pub fn new() -> Self {
331        Self { prev_trigger: true, clock_samples: 0, counter: 0 }
332    }
333
334    #[inline]
335    pub fn reset(&mut self) {
336        self.clock_samples = 0;
337        self.counter = 0;
338    }
339
340    #[inline]
341    pub fn next(&mut self, trigger_in: f32) -> u32 {
342        if self.prev_trigger {
343            if trigger_in <= TRIG_LOW_THRES {
344                self.prev_trigger = false;
345            }
346        } else if trigger_in > TRIG_HIGH_THRES {
347            self.prev_trigger = true;
348            self.clock_samples = self.counter;
349            self.counter = 0;
350        }
351
352        self.counter += 1;
353
354        self.clock_samples
355    }
356}