synfx_dsp/
oscillators.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//! Various "voltage" controlled (usually band limited) oscillator implementations.
6
7use crate::fast_sin;
8
9// PolyBLEP by Tale
10// (slightly modified)
11// http://www.kvraudio.com/forum/viewtopic.php?t=375517
12// from http://www.martin-finke.de/blog/articles/audio-plugins-018-polyblep-oscillator/
13//
14// default for `pw' should be 1.0, it's the pulse width
15// for the square wave.
16#[allow(dead_code)]
17fn poly_blep_64(t: f64, dt: f64) -> f64 {
18    if t < dt {
19        let t = t / dt;
20        2. * t - (t * t) - 1.
21    } else if t > (1.0 - dt) {
22        let t = (t - 1.0) / dt;
23        (t * t) + 2. * t + 1.
24    } else {
25        0.
26    }
27}
28
29fn poly_blep(t: f32, dt: f32) -> f32 {
30    if t < dt {
31        let t = t / dt;
32        2. * t - (t * t) - 1.
33    } else if t > (1.0 - dt) {
34        let t = (t - 1.0) / dt;
35        (t * t) + 2. * t + 1.
36    } else {
37        0.
38    }
39}
40
41/// This is a band-limited oscillator based on the PolyBlep technique.
42///
43/// **NOTE:** You need to call [crate::init_cos_tab].
44///
45/// Here is a quick example on how to use it:
46///
47///```
48/// use synfx_dsp::{PolyBlepOscillator, rand_01, init_cos_tab};
49/// init_cos_tab();
50///
51/// // Randomize the initial phase to make cancellation on summing less
52/// // likely:
53/// let mut osc =
54///     PolyBlepOscillator::new(rand_01() * 0.25);
55///
56///
57/// let freq   = 440.0; // Hz
58/// let israte = 1.0 / 44100.0; // Seconds per Sample
59/// let pw     = 0.2;   // Pulse-Width for the next_pulse()
60/// let waveform = 0;   // 0 being pulse in this example, 1 being sawtooth
61///
62/// let mut block_of_samples = [0.0; 128];
63/// // in your process function:
64/// for output_sample in block_of_samples.iter_mut() {
65///    *output_sample =
66///        if waveform == 1 {
67///             osc.next_saw(freq, israte)
68///        } else {
69///             osc.next_pulse(freq, israte, pw)
70///        }
71/// }
72///```
73#[derive(Debug, Clone)]
74pub struct PolyBlepOscillator {
75    phase: f32,
76    init_phase: f32,
77    last_output: f32,
78}
79
80impl PolyBlepOscillator {
81    /// Create a new instance of [PolyBlepOscillator].
82    ///
83    /// * `init_phase` - Initial phase of the oscillator.
84    /// Range of this parameter is from 0.0 to 1.0. Passing a random
85    /// value is advised for preventing phase cancellation when summing multiple
86    /// oscillators.
87    ///
88    ///```
89    /// use synfx_dsp::*;
90    ///
91    /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25);
92    ///```
93    pub fn new(init_phase: f32) -> Self {
94        Self { phase: 0.0, last_output: 0.0, init_phase }
95    }
96
97    /// Reset the internal state of the oscillator as if you just called
98    /// [PolyBlepOscillator::new].
99    #[inline]
100    pub fn reset(&mut self) {
101        self.phase = self.init_phase;
102        self.last_output = 0.0;
103    }
104
105    /// Creates the next sample of a sine wave.
106    ///
107    /// * `freq` - The frequency in Hz.
108    /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`.
109    ///```
110    /// use synfx_dsp::*;
111    ///
112    /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25);
113    ///
114    /// let freq   = 440.0; // Hz
115    /// let israte = 1.0 / 44100.0; // Seconds per Sample
116    ///
117    /// // ...
118    /// let sample = osc.next_sin(freq, israte);
119    /// // ...
120    ///```
121    #[inline]
122    pub fn next_sin(&mut self, freq: f32, israte: f32) -> f32 {
123        let phase_inc = freq * israte;
124
125        let s = fast_sin(self.phase * 2.0 * std::f32::consts::PI);
126
127        self.phase += phase_inc;
128        self.phase = self.phase.fract();
129
130        s as f32
131    }
132
133    /// Creates the next sample of a triangle wave. Please note that the
134    /// bandlimited waveform needs a few initial samples to swing in.
135    ///
136    /// * `freq` - The frequency in Hz.
137    /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`.
138    ///```
139    /// use synfx_dsp::*;
140    ///
141    /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25);
142    ///
143    /// let freq   = 440.0; // Hz
144    /// let israte = 1.0 / 44100.0; // Seconds per Sample
145    ///
146    /// // ...
147    /// let sample = osc.next_tri(freq, israte);
148    /// // ...
149    ///```
150    #[inline]
151    pub fn next_tri(&mut self, freq: f32, israte: f32) -> f32 {
152        let phase_inc = freq * israte;
153
154        let mut s = if self.phase < 0.5 { 1.0 } else { -1.0 };
155
156        s += poly_blep(self.phase, phase_inc);
157        s -= poly_blep((self.phase + 0.5).fract(), phase_inc);
158
159        // leaky integrator: y[n] = A * x[n] + (1 - A) * y[n-1]
160        s = phase_inc * s + (1.0 - phase_inc) * self.last_output;
161        self.last_output = s;
162
163        self.phase += phase_inc;
164        self.phase = self.phase.fract();
165
166        // the signal is a bit too weak, we need to amplify it
167        // or else the volume diff between the different waveforms
168        // is too big:
169        s * 4.0
170    }
171
172    /// Creates the next sample of a sawtooth wave.
173    ///
174    /// * `freq` - The frequency in Hz.
175    /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`.
176    ///```
177    /// use synfx_dsp::*;
178    ///
179    /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25);
180    ///
181    /// let freq   = 440.0; // Hz
182    /// let israte = 1.0 / 44100.0; // Seconds per Sample
183    ///
184    /// // ...
185    /// let sample = osc.next_saw(freq, israte);
186    /// // ...
187    ///```
188    #[inline]
189    pub fn next_saw(&mut self, freq: f32, israte: f32) -> f32 {
190        let phase_inc = freq * israte;
191
192        let mut s = (2.0 * self.phase) - 1.0;
193        s -= poly_blep(self.phase, phase_inc);
194
195        self.phase += phase_inc;
196        self.phase = self.phase.fract();
197
198        s
199    }
200
201    /// Creates the next sample of a pulse wave.
202    /// In comparison to [PolyBlepOscillator::next_pulse_no_dc] this
203    /// version is DC compensated, so that you may add multiple different
204    /// pulse oscillators for a unison effect without too big DC issues.
205    ///
206    /// * `freq` - The frequency in Hz.
207    /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`.
208    /// * `pw` - The pulse width. Use the value 0.0 for a square wave.
209    ///```
210    /// use synfx_dsp::*;
211    ///
212    /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25);
213    ///
214    /// let freq   = 440.0; // Hz
215    /// let israte = 1.0 / 44100.0; // Seconds per Sample
216    /// let pw     = 0.0; // 0.0 is a square wave.
217    ///
218    /// // ...
219    /// let sample = osc.next_pulse(freq, israte, pw);
220    /// // ...
221    ///```
222    #[inline]
223    pub fn next_pulse(&mut self, freq: f32, israte: f32, pw: f32) -> f32 {
224        let phase_inc = freq * israte;
225
226        let pw = (0.1 * pw) + ((1.0 - pw) * 0.5); // some scaling
227        let dc_compensation = (0.5 - pw) * 2.0;
228
229        let mut s = if self.phase < pw { 1.0 } else { -1.0 };
230
231        s += poly_blep(self.phase, phase_inc);
232        s -= poly_blep((self.phase + (1.0 - pw)).fract(), phase_inc);
233
234        s += dc_compensation;
235
236        self.phase += phase_inc;
237        self.phase = self.phase.fract();
238
239        s
240    }
241
242    /// Creates the next sample of a pulse wave.
243    /// In comparison to [PolyBlepOscillator::next_pulse] this
244    /// version is not DC compensated. So be careful when adding multiple
245    /// of this or generally using it in an audio context.
246    ///
247    /// * `freq` - The frequency in Hz.
248    /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`.
249    /// * `pw` - The pulse width. Use the value 0.0 for a square wave.
250    ///```
251    /// use synfx_dsp::*;
252    ///
253    /// let mut osc = PolyBlepOscillator::new(rand_01() * 0.25);
254    ///
255    /// let freq   = 440.0; // Hz
256    /// let israte = 1.0 / 44100.0; // Seconds per Sample
257    /// let pw     = 0.0; // 0.0 is a square wave.
258    ///
259    /// // ...
260    /// let sample = osc.next_pulse_no_dc(freq, israte, pw);
261    /// // ...
262    ///```
263    #[inline]
264    pub fn next_pulse_no_dc(&mut self, freq: f32, israte: f32, pw: f32) -> f32 {
265        let phase_inc = freq * israte;
266
267        let pw = (0.1 * pw) + ((1.0 - pw) * 0.5); // some scaling
268
269        let mut s = if self.phase < pw { 1.0 } else { -1.0 };
270
271        s += poly_blep(self.phase, phase_inc);
272        s -= poly_blep((self.phase + (1.0 - pw)).fract(), phase_inc);
273
274        self.phase += phase_inc;
275        self.phase = self.phase.fract();
276
277        s
278    }
279}
280
281// This oscillator is based on the work "VECTOR PHASESHAPING SYNTHESIS"
282// by: Jari Kleimola*, Victor Lazzarini†, Joseph Timoney†, Vesa Välimäki*
283// *Aalto University School of Electrical Engineering Espoo, Finland;
284// †National University of Ireland, Maynooth Ireland
285//
286// See also this PDF: http://recherche.ircam.fr/pub/dafx11/Papers/55_e.pdf
287/// Vector Phase Shaping Oscillator.
288/// The parameters `d` and `v` control the shape of the sinus
289/// wave. This leads to interesting modulation properties of those
290/// control values.
291///
292///```
293/// use synfx_dsp::*;
294///
295/// // Randomize the initial phase to make cancellation on summing less
296/// // likely:
297/// let mut osc =
298///     VPSOscillator::new(rand_01() * 0.25);
299///
300///
301/// let freq   = 440.0; // Hz
302/// let israte = 1.0 / 44100.0; // Seconds per Sample
303/// let d      = 0.5;   // Range: 0.0 to 1.0
304/// let v      = 0.75;  // Range: 0.0 to 1.0
305///
306/// let mut block_of_samples = [0.0; 128];
307/// // in your process function:
308/// for output_sample in block_of_samples.iter_mut() {
309///     // It is advised to limit the `v` value, because with certain
310///     // `d` values the combination creates just a DC offset.
311///     let v = VPSOscillator::limit_v(d, v);
312///     *output_sample = osc.next(freq, israte, d, v);
313/// }
314///```
315///
316/// It can be beneficial to apply distortion and oversampling.
317/// Especially oversampling can be important for some `d` and `v`
318/// combinations, even without distortion.
319///
320///```
321/// use synfx_dsp::{VPSOscillator, rand_01, apply_distortion};
322/// use synfx_dsp::Oversampling;
323///
324/// let mut osc = VPSOscillator::new(rand_01() * 0.25);
325/// let mut ovr : Oversampling<4> = Oversampling::new();
326///
327/// let freq   = 440.0; // Hz
328/// let israte = 1.0 / 44100.0; // Seconds per Sample
329/// let d      = 0.5;   // Range: 0.0 to 1.0
330/// let v      = 0.75;  // Range: 0.0 to 1.0
331///
332/// let mut block_of_samples = [0.0; 128];
333/// // in your process function:
334/// for output_sample in block_of_samples.iter_mut() {
335///     // It is advised to limit the `v` value, because with certain
336///     // `d` values the combination creates just a DC offset.
337///     let v = VPSOscillator::limit_v(d, v);
338///
339///     let overbuf = ovr.resample_buffer();
340///     for b in overbuf {
341///         *b = apply_distortion(osc.next(freq, israte, d, v), 0.9,  1);
342///     }
343///     *output_sample = ovr.downsample();
344/// }
345///```
346#[derive(Debug, Clone)]
347pub struct VPSOscillator {
348    phase: f32,
349    init_phase: f32,
350}
351
352impl VPSOscillator {
353    /// Create a new instance of [VPSOscillator].
354    ///
355    /// * `init_phase` - The initial phase of the oscillator.
356    pub fn new(init_phase: f32) -> Self {
357        Self { phase: 0.0, init_phase }
358    }
359
360    /// Reset the phase of the oscillator to the initial phase.
361    #[inline]
362    pub fn reset(&mut self) {
363        self.phase = self.init_phase;
364    }
365
366    #[inline]
367    fn s(p: f32) -> f32 {
368        -(std::f32::consts::TAU * p).cos()
369    }
370
371    #[inline]
372    fn phi_vps(x: f32, v: f32, d: f32) -> f32 {
373        if x < d {
374            (v * x) / d
375        } else {
376            v + ((1.0 - v) * (x - d)) / (1.0 - d)
377        }
378    }
379
380    /// This rather complicated function blends out some
381    /// combinations of 'd' and 'v' that just lead to a constant DC
382    /// offset. Which is not very useful in an audio oscillator
383    /// context.
384    ///
385    /// Call this before passing `v` to [VPSOscillator::next].
386    #[inline]
387    pub fn limit_v(d: f32, v: f32) -> f32 {
388        let delta = 0.5 - (d - 0.5).abs();
389        if delta < 0.05 {
390            let x = (0.05 - delta) * 19.99;
391            if d < 0.5 {
392                let mm = x * 0.5;
393                let max = 1.0 - mm;
394                if v > max && v < 1.0 {
395                    max
396                } else if v >= 1.0 && v < (1.0 + mm) {
397                    1.0 + mm
398                } else {
399                    v
400                }
401            } else {
402                if v < 1.0 {
403                    v.clamp(x * 0.5, 1.0)
404                } else {
405                    v
406                }
407            }
408        } else {
409            v
410        }
411    }
412
413    /// Creates the next sample of this oscillator.
414    ///
415    /// * `freq` - The frequency in Hz.
416    /// * `israte` - The inverse sampling rate, or seconds per sample as in eg. `1.0 / 44100.0`.
417    /// * `d` - The phase distortion parameter `d` which must be in the range `0.0` to `1.0`.
418    /// * `v` - The phase distortion parameter `v` which must be in the range `0.0` to `1.0`.
419    ///
420    /// It is advised to limit the `v` using the [VPSOscillator::limit_v] function
421    /// before calling this function. To prevent DC offsets when modulating the parameters.
422    pub fn next(&mut self, freq: f32, israte: f32, d: f32, v: f32) -> f32 {
423        let s = Self::s(Self::phi_vps(self.phase, v, d));
424
425        self.phase += freq * israte;
426        self.phase = self.phase.fract();
427
428        s
429    }
430}
431
432//pub struct UnisonBlep {
433//    oscs: Vec<PolyBlepOscillator>,
434////    dc_block: crate::filter::DCBlockFilter,
435//}
436//
437//impl UnisonBlep {
438//    pub fn new(max_unison: usize) -> Self {
439//        let mut oscs = vec![];
440//        let mut rng = RandGen::new();
441//
442//        let dis_init_phase = 0.05;
443//        for i in 0..(max_unison + 1) {
444//            // randomize phases so we fatten the unison, get
445//            // less DC and not an amplified signal until the
446//            // detune desyncs the waves.
447//            // But no random phase for first, so we reduce the click
448//            let init_phase =
449//                if i == 0 { 0.0 } else { rng.next_open01() };
450//            oscs.push(PolyBlepOscillator::new(init_phase));
451//        }
452//
453//        Self {
454//            oscs,
455////            dc_block: crate::filter::DCBlockFilter::new(),
456//        }
457//    }
458//
459//    pub fn set_sample_rate(&mut self, srate: f32) {
460////        self.dc_block.set_sample_rate(srate);
461//        for o in self.oscs.iter_mut() {
462//            o.set_sample_rate(srate);
463//        }
464//    }
465//
466//    pub fn reset(&mut self) {
467////        self.dc_block.reset();
468//        for o in self.oscs.iter_mut() {
469//            o.reset();
470//        }
471//    }
472//
473//    pub fn next<P: OscillatorInputParams>(&mut self, params: &P) -> f32 {
474//        let unison =
475//            (params.unison().floor() as usize)
476//            .min(self.oscs.len() - 1);
477//        let detune = params.detune() as f64;
478//
479//        let mix = (1.0 / ((unison + 1) as f32)).sqrt();
480//
481//        let mut s = mix * self.oscs[0].next(params, 0.0);
482//
483//        for u in 0..unison {
484//            let detune_factor =
485//                detune * (((u / 2) + 1) as f64
486//                          * if (u % 2) == 0 { 1.0 } else { -1.0 });
487//            s += mix * self.oscs[u + 1].next(params, detune_factor * 0.01);
488//        }
489//
490////        self.dc_block.next(s)
491//        s
492//    }
493//}