From 3ae8d08236922726016453026517b120b1c21cfb Mon Sep 17 00:00:00 2001 From: Sami Perttu Date: Thu, 5 Sep 2024 13:56:35 +0300 Subject: [PATCH] Update. --- CHANGES.md | 6 ++++-- README.md | 6 ++++-- examples/keys.rs | 4 +++- src/biquad.rs | 7 ++++++- src/hacker.rs | 8 ++++---- src/hacker32.rs | 8 ++++---- src/oscillator.rs | 28 ++++++++++++++-------------- src/prelude.rs | 20 ++++++++++---------- tests/test_basic.rs | 17 +++++++++++++++++ 9 files changed, 66 insertions(+), 38 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c7e0d51..c113399 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -5,7 +5,7 @@ - Added choice of interpolation algorithm to `AtomicSynth`. - New opcode `sine_phase`. - Opcodes `delay` and `tap_linear` now support zero sample delays. -- New method `Net::crossfade` for replacing a unit with a crossfade. +- New method `Net::crossfade` for replacing a unit with a smooth crossfade. - Clarified latency: it only applies to involuntary causal latencies. - `AdaptiveTanh` is now generic `Adaptive` distortion with an inner shape. To migrate, try `Adaptive::new(timescale, Tanh(hardness))`. @@ -14,7 +14,9 @@ - Implemented denormal prevention for `x86` inside feedback loops. - The resonator now accepts a Q parameter instead of bandwidth in Hz. To migrate, Q = center / bandwidth. -- Feedback biquads and dirty biquads. +- Feedback biquads and dirty biquads by Jatin Chowdhury. +- Sine oscillator has now generic inner state. To migrate, use + `Sine` if speed is important or `Sine` if steady maintenance of phase is important. ### Version 0.18.2 diff --git a/README.md b/README.md index 9b2868f..8c25cbd 100644 --- a/README.md +++ b/README.md @@ -664,6 +664,8 @@ and folding constants. Linear networks are constructed from linear filters, dela Signal latencies are similarly analyzed from input to output in detail, facilitating automatic removal of pre-delay from effects chains. +The definition of latency is one of involuntary causal kind, so +voluntarily placed delay elements do not count as latency. For example, [FIR](https://en.wikipedia.org/wiki/Finite_impulse_response) filters @@ -734,11 +736,11 @@ Due to nonlinearity, we do not attempt to calculate frequency responses for thes | Opcode | Type | Parameters | Family | Notes | | ------------ | ---------------------- | ------------ | ------------ | --------- | | `bandrez` | bandpass (2nd order) | frequency, Q | nested 1st order | | -| `dbell` | peaking (2nd order) | frequency, Q, gain | dirty biquad | Biquad with nonlinear state shaping and adjustable amplitude gain. | +| `dbell` | peaking (2nd order) | frequency, Q, gain | [dirty biquad](https://jatinchowdhury18.medium.com/complex-nonlinearities-episode-4-nonlinear-biquad-filters-ae6b3f23cb0e) | Biquad with nonlinear state shaping and adjustable amplitude gain. | | `dhighpass` | highpass (2nd order) | frequency, Q | dirty biquad | | | `dlowpass` | lowpass (2nd order) | frequency, Q | dirty biquad | | | `dresonator` | bandpass (2nd order) | frequency, Q | dirty biquad | | -| `fbell` | peaking (2nd order) | frequency, Q, gain | feedback biquad | Biquad with nonlinear feedback and adjustable amplitude gain. | +| `fbell` | peaking (2nd order) | frequency, Q, gain | [feedback biquad](https://jatinchowdhury18.medium.com/complex-nonlinearities-episode-5-nonlinear-feedback-filters-115e65fc0402) | Biquad with nonlinear feedback and adjustable amplitude gain. | | `fhighpass` | highpass (2nd order) | frequency, Q | feedback biquad | | | `flowpass` | lowpass (2nd order) | frequency, Q | feedback biquad | | | `fresonator` | bandpass (2nd order) | frequency, Q | feedback biquad | | diff --git a/examples/keys.rs b/examples/keys.rs index 1858a23..48197cc 100644 --- a/examples/keys.rs +++ b/examples/keys.rs @@ -499,7 +499,9 @@ impl eframe::App for State { >> peak(), )), Filter::DirtyBiquad => Net::wrap(Box::new( - (pass() | lfo(move |t| (max(400.0, 20000.0 * exp(-t * 8.0)), 2.0))) + (pass() | lfo(move |t| (max(800.0, 20000.0 * exp(-t * 6.0)), 3.0))) + >> !dlowpass(Tanh(1.02)) + >> mul((1.0, 0.666, 1.0)) >> dlowpass(Tanh(1.0)), )), Filter::FeedbackBiquad => Net::wrap(Box::new( diff --git a/src/biquad.rs b/src/biquad.rs index ab3083a..8b0d0b7 100644 --- a/src/biquad.rs +++ b/src/biquad.rs @@ -1,4 +1,9 @@ -//! Biquad filters with optional nonlinearities. +//! Biquad filters with optional nonlinearities by Jatin Chowdhury. + +// For more information, see: +// https://github.com/jatinchowdhury18/ComplexNonlinearities entries 4 and 5. +// For some of the biquad formulae, see the Audio EQ Cookbook: +// https://www.w3.org/TR/audio-eq-cookbook/ use super::audionode::*; use super::math::*; diff --git a/src/hacker.rs b/src/hacker.rs index 326ae7a..4366399 100644 --- a/src/hacker.rs +++ b/src/hacker.rs @@ -330,7 +330,7 @@ pub fn reverse>() -> An> { /// use fundsp::hacker::*; /// lfo(|t| 110.0 + lerp11(-2.0, 2.0, sin_hz(t, 5.0))) >> sine(); /// ``` -pub fn sine() -> An { +pub fn sine() -> An> { An(Sine::new()) } @@ -342,14 +342,14 @@ pub fn sine() -> An { /// use fundsp::hacker::*; /// sine_hz(440.0); /// ``` -pub fn sine_hz(f: f32) -> An, Sine>> { +pub fn sine_hz(f: f32) -> An, Sine>> { constant(f) >> sine() } /// Sine oscillator with initial `phase` in 0...1. /// - Input 0: frequency (Hz) /// - Output 0: sine wave -pub fn sine_phase(phase: f32) -> An { +pub fn sine_phase(phase: f32) -> An> { An(Sine::with_phase(phase)) } @@ -2464,7 +2464,7 @@ pub fn fbell(shape: S) -> An, S>> { /// (The usual waveshapes are nonexpansive up to hardness 1.0). /// - Input 0: audio /// - Output 0: filtered audio -pub fn fbell_hz( +pub fn fbell_hz( shape: S, center: f32, q: f32, diff --git a/src/hacker32.rs b/src/hacker32.rs index a8d0f8c..fb79d2d 100644 --- a/src/hacker32.rs +++ b/src/hacker32.rs @@ -330,7 +330,7 @@ pub fn reverse>() -> An> { /// use fundsp::hacker32::*; /// lfo(|t| 110.0 + lerp11(-2.0, 2.0, sin_hz(t, 5.0))) >> sine(); /// ``` -pub fn sine() -> An { +pub fn sine() -> An> { An(Sine::new()) } @@ -342,14 +342,14 @@ pub fn sine() -> An { /// use fundsp::hacker32::*; /// sine_hz(440.0); /// ``` -pub fn sine_hz(f: f32) -> An, Sine>> { +pub fn sine_hz(f: f32) -> An, Sine>> { constant(f) >> sine() } /// Sine oscillator with initial `phase` in 0...1. /// - Input 0: frequency (Hz) /// - Output 0: sine wave -pub fn sine_phase(phase: f32) -> An { +pub fn sine_phase(phase: f32) -> An> { An(Sine::with_phase(phase)) } @@ -2464,7 +2464,7 @@ pub fn fbell(shape: S) -> An, S>> { /// (The usual waveshapes are nonexpansive up to hardness 1.0). /// - Input 0: audio /// - Output 0: filtered audio -pub fn fbell_hz( +pub fn fbell_hz( shape: S, center: f32, q: f32, diff --git a/src/oscillator.rs b/src/oscillator.rs index 4f52dd6..c064c80 100644 --- a/src/oscillator.rs +++ b/src/oscillator.rs @@ -18,14 +18,14 @@ use alloc::vec::Vec; /// - Input 0: frequency in Hz. /// - Output 0: sine wave. #[derive(Default, Clone)] -pub struct Sine { - phase: f32, - sample_duration: f32, +pub struct Sine { + phase: F, + sample_duration: F, hash: u64, - initial_phase: Option, + initial_phase: Option, } -impl Sine { +impl Sine { /// Create sine oscillator. pub fn new() -> Self { let mut sine = Sine::default(); @@ -36,10 +36,10 @@ impl Sine { /// Create sine oscillator with initial phase in 0...1. pub fn with_phase(initial_phase: f32) -> Self { let mut sine = Self { - phase: 0.0, - sample_duration: 0.0, + phase: F::zero(), + sample_duration: F::zero(), hash: 0, - initial_phase: Some(initial_phase), + initial_phase: Some(F::from_f32(initial_phase)), }; sine.reset(); sine.set_sample_rate(DEFAULT_SR); @@ -47,7 +47,7 @@ impl Sine { } } -impl AudioNode for Sine { +impl AudioNode for Sine { const ID: u64 = 21; type Inputs = typenum::U1; type Outputs = typenum::U1; @@ -55,7 +55,7 @@ impl AudioNode for Sine { fn reset(&mut self) { self.phase = match self.initial_phase { Some(phase) => phase, - None => rnd1(self.hash) as f32, + None => convert(rnd1(self.hash)), }; } @@ -66,17 +66,17 @@ impl AudioNode for Sine { #[inline] fn tick(&mut self, input: &Frame) -> Frame { let phase = self.phase; - self.phase += input[0] * self.sample_duration; + self.phase += F::from_f32(input[0]) * self.sample_duration; self.phase -= self.phase.floor(); - [sin(phase * f32::TAU)].into() + [sin(phase.to_f32() * f32::TAU)].into() } fn process(&mut self, size: usize, input: &BufferRef, output: &mut BufferMut) { let mut phase = self.phase; for i in 0..full_simd_items(size) { let element: [f32; SIMD_N] = core::array::from_fn(|j| { - let tmp = phase; - phase += input.at_f32(0, (i << SIMD_S) + j) * self.sample_duration; + let tmp = phase.to_f32(); + phase += F::from_f32(input.at_f32(0, (i << SIMD_S) + j)) * self.sample_duration; tmp }); output.set(0, i, (F32x::new(element) * f32::TAU).sin()); diff --git a/src/prelude.rs b/src/prelude.rs index 52f49b9..21689cf 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -180,7 +180,7 @@ pub type U128 = numeric_array::typenum::U128; /// ### Example: Sine Oscillator /// ``` /// use fundsp::prelude::*; -/// constant(440.0) >> sine(); +/// constant(440.0) >> sine::(); /// ``` pub fn constant>(x: X) -> An> where @@ -197,7 +197,7 @@ where /// ### Example: Dual Sine Oscillator /// ``` /// use fundsp::prelude::*; -/// dc((220.0, 440.0)) >> (sine() + sine()); +/// dc((220.0, 440.0)) >> (sine::() + sine::()); /// ``` pub fn dc>(x: X) -> An> where @@ -328,9 +328,9 @@ pub fn reverse>() -> An> { /// ### Example: Vibrato /// ``` /// use fundsp::prelude::*; -/// lfo(|t| 110.0 + lerp11(-2.0, 2.0, sin_hz(t, 5.0))) >> sine(); +/// lfo(|t| 110.0 + lerp11(-2.0, 2.0, sin_hz(t, 5.0))) >> sine::(); /// ``` -pub fn sine() -> An { +pub fn sine() -> An> { An(Sine::new()) } @@ -340,16 +340,16 @@ pub fn sine() -> An { /// ### Example /// ``` /// use fundsp::prelude::*; -/// sine_hz(440.0); +/// sine_hz::(440.0); /// ``` -pub fn sine_hz(f: f32) -> An, Sine>> { +pub fn sine_hz(f: f32) -> An, Sine>> { constant(f) >> sine() } /// Sine oscillator with initial `phase` in 0...1. /// - Input 0: frequency (Hz) /// - Output 0: sine wave -pub fn sine_phase(phase: f32) -> An { +pub fn sine_phase(phase: f32) -> An> { An(Sine::with_phase(phase)) } @@ -981,7 +981,7 @@ where /// use fundsp::prelude::*; /// let f: f32 = 440.0; /// let m: f32 = 1.0; -/// oversample(sine_hz(f) * f * m + f >> sine()); +/// oversample(sine_hz::(f) * f * m + f >> sine::()); /// ``` pub fn oversample(node: An) -> An> where @@ -1204,7 +1204,7 @@ pub fn clip_to(minimum: f32, maximum: f32) -> An> { /// ### Example: Panning Noise /// ``` /// use fundsp::prelude::*; -/// (noise() | sine_hz(0.5)) >> panner(); +/// (noise() | sine_hz::(0.5)) >> panner(); /// ``` pub fn panner() -> An> { An(Panner::new(0.0)) @@ -1355,7 +1355,7 @@ where /// ### Example (Sine Bundle) /// ``` /// use fundsp::prelude::*; -/// busi::(|i| sine_hz(110.0 * exp(lerp(-0.2, 0.2, rnd2(i) as f32)))); +/// busi::(|i| sine_hz::(110.0 * exp(lerp(-0.2, 0.2, rnd2(i) as f32)))); /// ``` pub fn busi(f: F) -> An> where diff --git a/tests/test_basic.rs b/tests/test_basic.rs index 6e5f286..f143218 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -210,6 +210,23 @@ fn test_basic() { dc((440.0, 880.0)) >> multisplit::() >> multijoin::() >> (sine() | sine()), ); check_wave((noise() >> split::() >> join()) | (noise() >> split::() >> join())); + check_wave( + noise() >> dbell_hz(Tanh(1.0), 1000.0, 10.0, 2.0) + | noise() >> dhighpass_hz(Softsign(1.0), 2000.0, 2.0), + ); + check_wave( + noise() >> dresonator_hz(Tanh(0.5), 1000.0, 10.0) + | noise() >> dlowpass_hz(Softsign(0.5), 2000.0, 2.0), + ); + check_wave( + noise() >> fbell_hz(Atan(1.0), 500.0, 50.0, 0.5) + | noise() >> flowpass_hz(Clip(1.0), 2000.0, 2.0), + ); + check_wave( + noise() >> fresonator_hz(Atan(0.5), 500.0, 50.0) + | noise() >> fhighpass_hz(Softsign(0.2), 2000.0, 2.0), + ); + check_wave_big(Box::new(dc((110.0, 0.5)) >> pulse() * 0.2 >> delay(0.1))); check_wave_big(Box::new(envelope(|t| exp(-t * 10.0))));