synfx_dsp/
low_freq.rs

1// Copyright (c) 2021-2022 Weird Constructor <weirdconstructor@gmail.com>
2// This file is a part of synfx-dsp. Released under GPL-3.0-or-later.
3// See README.md and COPYING for details.
4
5//! Low frequency utilities for handling control signals (partially also at audio rate).
6
7use crate::{f, fclampc, Flt};
8
9// Adapted from https://github.com/ValleyAudio/ValleyRackFree/blob/v1.0/src/Common/DSP/LFO.hpp
10//
11// ValleyRackFree Copyright (C) 2020, Valley Audio Soft, Dale Johnson
12// Adapted under the GPL-3.0-or-later License.
13/// An LFO with a variable reverse point, which can go from reverse Saw, to Tri
14/// and to Saw, depending on the reverse point.
15#[derive(Debug, Clone, Copy)]
16pub struct TriSawLFO<F: Flt> {
17    /// The (inverse) sample rate. Eg. 1.0 / 44100.0.
18    israte: F,
19    /// The current oscillator phase.
20    phase: F,
21    /// The point from where the falling edge will be used.
22    rev: F,
23    /// The frequency.
24    freq: F,
25    /// Precomputed rise/fall rate of the LFO.
26    rise_r: F,
27    fall_r: F,
28    /// Initial phase offset.
29    init_phase: F,
30}
31
32impl<F: Flt> TriSawLFO<F> {
33    pub fn new() -> Self {
34        let mut this = Self {
35            israte: f(1.0 / 44100.0),
36            phase: f(0.0),
37            rev: f(0.5),
38            freq: f(1.0),
39            fall_r: f(0.0),
40            rise_r: f(0.0),
41            init_phase: f(0.0),
42        };
43        this.recalc();
44        this
45    }
46
47    pub fn set_phase_offs(&mut self, phase: F) {
48        self.init_phase = phase;
49        self.phase = phase;
50    }
51
52    #[inline]
53    fn recalc(&mut self) {
54        self.rev = fclampc(self.rev, 0.0001, 0.999);
55        self.rise_r = f::<F>(1.0) / self.rev;
56        self.fall_r = f::<F>(-1.0) / (f::<F>(1.0) - self.rev);
57    }
58
59    pub fn set_sample_rate(&mut self, srate: F) {
60        self.israte = f::<F>(1.0) / (srate as F);
61        self.recalc();
62    }
63
64    pub fn reset(&mut self) {
65        self.phase = self.init_phase;
66        self.rev = f(0.5);
67    }
68
69    #[inline]
70    pub fn set(&mut self, freq: F, rev: F) {
71        self.freq = freq as F;
72        self.rev = rev as F;
73        self.recalc();
74    }
75
76    #[inline]
77    pub fn next_unipolar(&mut self) -> F {
78        if self.phase >= f(1.0) {
79            self.phase = self.phase - f(1.0);
80        }
81
82        let s = if self.phase < self.rev {
83            self.phase * self.rise_r
84        } else {
85            self.phase * self.fall_r - self.fall_r
86        };
87
88        self.phase = self.phase + self.freq * self.israte;
89
90        s
91    }
92
93    #[inline]
94    pub fn next_bipolar(&mut self) -> F {
95        (self.next_unipolar() * f(2.0)) - f(1.0)
96    }
97}
98
99/// A slew rate limiter, with a configurable time per 1.0 increase.
100#[derive(Debug, Clone, Copy)]
101pub struct SlewValue<F: Flt> {
102    current: F,
103    slew_per_ms: F,
104}
105
106impl<F: Flt> SlewValue<F> {
107    pub fn new() -> Self {
108        Self { current: f(0.0), slew_per_ms: f(1000.0 / 44100.0) }
109    }
110
111    pub fn reset(&mut self) {
112        self.current = f(0.0);
113    }
114
115    pub fn set_sample_rate(&mut self, srate: F) {
116        self.slew_per_ms = f::<F>(1000.0) / srate;
117    }
118
119    #[inline]
120    pub fn value(&self) -> F {
121        self.current
122    }
123
124    /// * `slew_ms_per_1` - The time (in milliseconds) it should take
125    /// to get to 1.0 from 0.0.
126    #[inline]
127    pub fn next(&mut self, target: F, slew_ms_per_1: F) -> F {
128        // at 0.11ms, there are barely enough samples for proper slew.
129        if slew_ms_per_1 < f(0.11) {
130            self.current = target;
131        } else {
132            let max_delta = self.slew_per_ms / slew_ms_per_1;
133            self.current = target.min(self.current + max_delta).max(self.current - max_delta);
134        }
135
136        self.current
137    }
138}
139
140/// A ramped value changer, with a configurable time to reach the target value.
141#[derive(Debug, Clone, Copy)]
142pub struct RampValue<F: Flt> {
143    slew_count: u64,
144    current: F,
145    target: F,
146    inc: F,
147    sr_ms: F,
148}
149
150impl<F: Flt> RampValue<F> {
151    pub fn new() -> Self {
152        Self {
153            slew_count: 0,
154            current: f(0.0),
155            target: f(0.0),
156            inc: f(0.0),
157            sr_ms: f(44100.0 / 1000.0),
158        }
159    }
160
161    pub fn reset(&mut self) {
162        self.slew_count = 0;
163        self.current = f(0.0);
164        self.target = f(0.0);
165        self.inc = f(0.0);
166    }
167
168    pub fn set_sample_rate(&mut self, srate: F) {
169        self.sr_ms = srate / f(1000.0);
170    }
171
172    #[inline]
173    pub fn set_target(&mut self, target: F, slew_time_ms: F) {
174        self.target = target;
175
176        // 0.02ms, thats a fraction of a sample at 44.1kHz
177        if slew_time_ms < f(0.02) {
178            self.current = self.target;
179            self.slew_count = 0;
180        } else {
181            let slew_samples = slew_time_ms * self.sr_ms;
182            self.slew_count = slew_samples.to_u64().unwrap_or(0);
183            self.inc = (self.target - self.current) / slew_samples;
184        }
185    }
186
187    #[inline]
188    pub fn value(&self) -> F {
189        self.current
190    }
191
192    #[inline]
193    pub fn next(&mut self) -> F {
194        if self.slew_count > 0 {
195            self.current = self.current + self.inc;
196            self.slew_count -= 1;
197        } else {
198            self.current = self.target;
199        }
200
201        self.current
202    }
203}
204
205#[derive(Debug, Clone)]
206pub struct Quantizer {
207    old_mask: i64,
208    lkup_tbl: [(f32, f32); 24],
209    last_key: f32,
210}
211
212impl Quantizer {
213    pub fn new() -> Self {
214        Self { old_mask: 0xFFFF_FFFF, lkup_tbl: [(0.0, 0.0); 24], last_key: 0.0 }
215    }
216
217    #[inline]
218    pub fn set_keys(&mut self, keys_mask: i64) {
219        if keys_mask == self.old_mask {
220            return;
221        }
222        self.old_mask = keys_mask;
223
224        self.setup_lookup_table();
225    }
226
227    #[inline]
228    fn setup_lookup_table(&mut self) {
229        let mask = self.old_mask;
230        let any_enabled = mask > 0x0;
231
232        for i in 0..24 {
233            let mut min_d_note_idx = 0;
234            let mut min_dist = 1000000000;
235
236            for note in -12..=24 {
237                let dist = ((i + 1_i64) / 2 - note).abs();
238                let note_idx = note.rem_euclid(12);
239
240                // XXX: We add 9 here for the mask lookup,
241                // to shift the keyboard, which starts at C!
242                // And first bit in the mask is the C note. 10th is the A note.
243                if any_enabled && (mask & (0x1 << ((note_idx + 9) % 12))) == 0x0 {
244                    continue;
245                }
246
247                //d// println!("I={:3} NOTE={:3} (IDX={:3} => bitset {}) DIST={:3}",
248                //d//     i, note, note_idx,
249                //d//     if (mask & (0x1 << ((note_idx + 9) % 12))) > 0x0 { 1 } else { 0 },
250                //d//     dist);
251
252                if dist < min_dist {
253                    min_d_note_idx = note;
254                    min_dist = dist;
255                } else {
256                    break;
257                }
258            }
259
260            self.lkup_tbl[i as usize] = (
261                (min_d_note_idx + 9).rem_euclid(12) as f32 * (0.1 / 12.0),
262                min_d_note_idx.rem_euclid(12) as f32 * (0.1 / 12.0)
263                    + (if min_d_note_idx < 0 {
264                        -0.1
265                    } else if min_d_note_idx > 11 {
266                        0.1
267                    } else {
268                        0.0
269                    }),
270            );
271        }
272        //d// println!("TBL: {:?}", self.lkup_tbl);
273    }
274
275    #[inline]
276    pub fn last_key_pitch(&self) -> f32 {
277        self.last_key
278    }
279
280    #[inline]
281    pub fn process(&mut self, inp: f32) -> f32 {
282        let note_num = (inp * 240.0).round() as i64;
283        let octave = note_num.div_euclid(24);
284        let note_idx = note_num - octave * 24;
285
286        //        println!(
287        //            "INP {:7.4} => octave={:3}, note_idx={:3} note_num={:3} inp={:9.6}",
288        //            inp, octave, note_idx, note_num, inp * 240.0);
289        //d// println!("TBL: {:?}", self.lkup_tbl);
290
291        let (ui_key_pitch, note_pitch) = self.lkup_tbl[note_idx as usize % 24];
292        self.last_key = ui_key_pitch;
293        note_pitch + octave as f32 * 0.1
294    }
295}
296
297#[derive(Debug, Clone)]
298pub struct CtrlPitchQuantizer {
299    /// All keys, containing the min/max octave!
300    keys: Vec<f32>,
301    /// Only the used keys with their pitches from the UI
302    used_keys: [f32; 12],
303    /// A value combination of the arguments to `update_keys`.
304    input_params: u64,
305    /// The number of used keys from the mask.
306    mask_key_count: u16,
307    /// The last key for the pitch that was returned by `process`.
308    last_key: u8,
309}
310
311const QUANT_TUNE_TO_A4: f32 = (9.0 / 12.0) * 0.1;
312
313impl CtrlPitchQuantizer {
314    pub fn new() -> Self {
315        Self {
316            keys: vec![0.0; 12 * 10],
317            used_keys: [0.0; 12],
318            mask_key_count: 0,
319            input_params: 0xFFFFFFFFFF,
320            last_key: 0,
321        }
322    }
323
324    #[inline]
325    pub fn last_key_pitch(&self) -> f32 {
326        self.used_keys[self.last_key as usize % (self.mask_key_count as usize)] + QUANT_TUNE_TO_A4
327    }
328
329    #[inline]
330    pub fn update_keys(&mut self, mut mask: i64, min_oct: i64, max_oct: i64) {
331        let inp_params = (mask as u64) | ((min_oct as u64) << 12) | ((max_oct as u64) << 20);
332
333        if self.input_params == inp_params {
334            return;
335        }
336
337        self.input_params = inp_params;
338
339        let mut mask_count = 0;
340
341        // set all keys, if none are set!
342        if mask == 0x0 {
343            mask = 0xFFFF;
344        }
345
346        for i in 0..12 {
347            if mask & (0x1 << i) > 0 {
348                self.used_keys[mask_count] = (i as f32 / 12.0) * 0.1 - QUANT_TUNE_TO_A4;
349                mask_count += 1;
350            }
351        }
352
353        self.keys.clear();
354
355        let min_oct = min_oct as usize;
356        for o in 0..min_oct {
357            let o = min_oct - o;
358
359            for i in 0..mask_count {
360                self.keys.push(self.used_keys[i] - (o as f32) * 0.1);
361            }
362        }
363
364        for i in 0..mask_count {
365            self.keys.push(self.used_keys[i]);
366        }
367
368        let max_oct = max_oct as usize;
369        for o in 1..=max_oct {
370            for i in 0..mask_count {
371                self.keys.push(self.used_keys[i] + (o as f32) * 0.1);
372            }
373        }
374
375        self.mask_key_count = mask_count as u16;
376    }
377
378    #[inline]
379    pub fn signal_to_pitch(&mut self, inp: f32) -> f32 {
380        let len = self.keys.len();
381        let key = (inp.clamp(0.0, 0.9999) * (len as f32)).floor();
382        let key = key as usize % len;
383        self.last_key = key as u8;
384        self.keys[key]
385    }
386}