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//}