diff --git a/CHANGES.md b/CHANGES.md index c113399..75712ef 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -17,6 +17,7 @@ - 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. +- Non-bandlimited ramp node with opcodes `ramp`, `ramp_hz`, `ramp_phase` and `ramp_hz_phase`. ### Version 0.18.2 diff --git a/README.md b/README.md index 8c25cbd..3f35150 100644 --- a/README.md +++ b/README.md @@ -1198,6 +1198,10 @@ The type parameters in the table refer to the hacker preludes. | `pluck(f, gain, damping)` | 1 (excitation) | 1 | [Karplus-Strong](https://en.wikipedia.org/wiki/Karplus%E2%80%93Strong_string_synthesis) plucked string oscillator with frequency `f` Hz, `gain` per second (`gain` <= 1) and high frequency `damping` in 0...1. | | `product(x, y)` | `x + y` | `x = y` | Multiply nodes `x` and `y`. Same as `x * y`. | | `pulse()` | 2 (frequency, duty cycle) | 1 | Bandlimited pulse wave with duty cycle in 0...1. | +| `ramp()` | 1 (frequency) | 1 | Non-bandlimited ramp (sawtooth) wave in 0...1. | +| `ramp_hz(f)` | 0 | 1 | Non-bandlimited ramp (sawtooth) wave in 0...1 with frequency `f` Hz. | +| `ramp_phase(phase)` | 1 (frequency) | 1 | Non-bandlimited ramp (sawtooth) wave in 0...1 with initial `phase` in 0...1. | +| `ramp_hz_phase(f, phase)` | 0 | 1 | Non-bandlimited ramp (sawtooth) wave in 0...1 with frequency `f` Hz and initial `phase` in 0...1. | | `resample(node)` | 1 (speed) | `node` | Resample generator `node` using cubic interpolation at speed obtained from the input, where 1 is the original speed. | | `resonator()` | 3 (audio, frequency, Q) | 1 | Constant-gain bandpass resonator (2nd order). | | `resonator_hz(f, q)` | 1 | 1 | Constant-gain bandpass resonator (2nd order) with center frequency `f` Hz and Q `q`. | diff --git a/src/hacker.rs b/src/hacker.rs index 4366399..270800b 100644 --- a/src/hacker.rs +++ b/src/hacker.rs @@ -353,6 +353,32 @@ pub fn sine_phase(phase: f32) -> An> { An(Sine::with_phase(phase)) } +/// Ramp generator with output in 0...1. Not bandlimited. +/// - Input 0: repetition frequency (Hz) +/// - Output 0: ramp phase in 0...1 +pub fn ramp() -> An> { + An(Ramp::new()) +} + +/// Ramp generator with output in 0...1 at fixed frequency `f` Hz. +/// - Output 0: ramp phase in 0...1 +pub fn ramp_hz(f: f32) -> An, Ramp>> { + constant(f) >> ramp() +} + +/// Ramp generator with output in 0...1, starting from initial phase `phase` in 0...1. +/// - Input 0: repetition frequency (Hz) +/// - Output 0: ramp phase in 0...1 +pub fn ramp_phase(phase: f32) -> An> { + An(Ramp::with_phase(phase)) +} + +/// Ramp generator with output in 0...1 at fixed frequency `f` Hz. +/// - Output 0: ramp phase in 0...1 +pub fn ramp_hz_phase(f: f32, phase: f32) -> An, Ramp>> { + constant(f) >> ramp_phase(phase) +} + /// Rossler dynamical system oscillator. /// - Input 0: frequency. The Rossler oscillator exhibits peaks at multiples of this frequency. /// - Output 0: system output diff --git a/src/hacker32.rs b/src/hacker32.rs index fb79d2d..4c9f8b8 100644 --- a/src/hacker32.rs +++ b/src/hacker32.rs @@ -353,6 +353,32 @@ pub fn sine_phase(phase: f32) -> An> { An(Sine::with_phase(phase)) } +/// Ramp generator with output in 0...1. Not bandlimited. +/// - Input 0: repetition frequency (Hz) +/// - Output 0: ramp phase in 0...1 +pub fn ramp() -> An> { + An(Ramp::new()) +} + +/// Ramp generator with output in 0...1 at fixed frequency `f` Hz. +/// - Output 0: ramp phase in 0...1 +pub fn ramp_hz(f: f32) -> An, Ramp>> { + constant(f) >> ramp() +} + +/// Ramp generator with output in 0...1, starting from initial phase `phase` in 0...1. +/// - Input 0: repetition frequency (Hz) +/// - Output 0: ramp phase in 0...1 +pub fn ramp_phase(phase: f32) -> An> { + An(Ramp::with_phase(phase)) +} + +/// Ramp generator with output in 0...1 at fixed frequency `f` Hz. +/// - Output 0: ramp phase in 0...1 +pub fn ramp_hz_phase(f: f32, phase: f32) -> An, Ramp>> { + constant(f) >> ramp_phase(phase) +} + /// Rossler dynamical system oscillator. /// - Input 0: frequency. The Rossler oscillator exhibits peaks at multiples of this frequency. /// - Output 0: system output diff --git a/src/oscillator.rs b/src/oscillator.rs index c064c80..ce8605a 100644 --- a/src/oscillator.rs +++ b/src/oscillator.rs @@ -91,7 +91,7 @@ impl AudioNode for Sine { } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - super::signal::Routing::Generator(0.0).route(input, self.outputs()) + super::signal::Routing::Arbitrary(0.0).route(input, self.outputs()) } } @@ -190,7 +190,7 @@ impl> AudioNode for Dsf { } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - super::signal::Routing::Generator(0.0).route(input, self.outputs()) + super::signal::Routing::Arbitrary(0.0).route(input, self.outputs()) } } @@ -293,7 +293,7 @@ impl AudioNode for Pluck { } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - super::signal::Routing::Generator(0.0).route(input, self.outputs()) + super::signal::Routing::Arbitrary(0.0).route(input, self.outputs()) } fn allocate(&mut self) { @@ -358,7 +358,7 @@ impl AudioNode for Rossler { } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - super::signal::Routing::Generator(0.0).route(input, self.outputs()) + super::signal::Routing::Arbitrary(0.0).route(input, self.outputs()) } } @@ -417,6 +417,73 @@ impl AudioNode for Lorenz { } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - super::signal::Routing::Generator(0.0).route(input, self.outputs()) + super::signal::Routing::Arbitrary(0.0).route(input, self.outputs()) + } +} + +/// Ascending ramp generator with output in 0...1. Not bandlimited. +/// - Input 0: repetition frequency in Hz. +/// - Output 0: current phase in 0...1. +#[derive(Default, Clone)] +pub struct Ramp { + phase: F, + sample_duration: F, + hash: u64, + initial_phase: Option, +} + +impl Ramp { + /// Create ramp generator. + pub fn new() -> Self { + let mut ramp = Self::default(); + ramp.reset(); + ramp.set_sample_rate(DEFAULT_SR); + ramp + } + /// Create ramp generator with initial phase in 0...1. + pub fn with_phase(initial_phase: f32) -> Self { + let mut ramp = Self { + phase: F::zero(), + sample_duration: F::zero(), + hash: 0, + initial_phase: Some(F::from_f32(initial_phase)), + }; + ramp.reset(); + ramp.set_sample_rate(DEFAULT_SR); + ramp + } +} + +impl AudioNode for Ramp { + const ID: u64 = 21; + type Inputs = typenum::U1; + type Outputs = typenum::U1; + + fn reset(&mut self) { + self.phase = match self.initial_phase { + Some(phase) => phase, + None => convert(rnd1(self.hash)), + }; + } + + fn set_sample_rate(&mut self, sample_rate: f64) { + self.sample_duration = convert(1.0 / sample_rate); + } + + #[inline] + fn tick(&mut self, input: &Frame) -> Frame { + let phase = self.phase.to_f32(); + self.phase += F::from_f32(input[0]) * self.sample_duration; + self.phase -= self.phase.floor(); + [phase].into() + } + + fn set_hash(&mut self, hash: u64) { + self.hash = hash; + self.reset(); + } + + fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { + super::signal::Routing::Arbitrary(0.0).route(input, self.outputs()) } } diff --git a/src/prelude.rs b/src/prelude.rs index 21689cf..fe3c345 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -353,6 +353,32 @@ pub fn sine_phase(phase: f32) -> An> { An(Sine::with_phase(phase)) } +/// Ramp generator with output in 0...1. Not bandlimited. +/// - Input 0: repetition frequency (Hz) +/// - Output 0: ramp phase in 0...1 +pub fn ramp() -> An> { + An(Ramp::new()) +} + +/// Ramp generator with output in 0...1 at fixed frequency `f` Hz. +/// - Output 0: ramp phase in 0...1 +pub fn ramp_hz(f: f32) -> An, Ramp>> { + constant(f) >> ramp() +} + +/// Ramp generator with output in 0...1, starting from initial phase `phase` in 0...1. +/// - Input 0: repetition frequency (Hz) +/// - Output 0: ramp phase in 0...1 +pub fn ramp_phase(phase: f32) -> An> { + An(Ramp::with_phase(phase)) +} + +/// Ramp generator with output in 0...1 at fixed frequency `f` Hz. +/// - Output 0: ramp phase in 0...1 +pub fn ramp_hz_phase(f: f32, phase: f32) -> An, Ramp>> { + constant(f) >> ramp_phase(phase) +} + /// Rossler dynamical system oscillator. /// - Input 0: frequency. The Rossler oscillator exhibits peaks at multiples of this frequency. /// - Output 0: system output diff --git a/src/wavetable.rs b/src/wavetable.rs index 6bf29ce..12afc50 100644 --- a/src/wavetable.rs +++ b/src/wavetable.rs @@ -350,7 +350,7 @@ where } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - Routing::Generator(0.0).route(input, self.outputs()) + Routing::Arbitrary(0.0).route(input, self.outputs()) } } @@ -423,7 +423,7 @@ impl AudioNode for PhaseSynth { } fn route(&mut self, input: &SignalFrame, _frequency: f64) -> SignalFrame { - Routing::Generator(0.0).route(input, self.outputs()) + Routing::Arbitrary(0.0).route(input, self.outputs()) } } diff --git a/tests/test_basic.rs b/tests/test_basic.rs index f143218..ddf2b8a 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -226,6 +226,7 @@ fn test_basic() { noise() >> fresonator_hz(Atan(0.5), 500.0, 50.0) | noise() >> fhighpass_hz(Softsign(0.2), 2000.0, 2.0), ); + check_wave(dc(440.0) >> ramp() | dc(-220.0) >> ramp_phase(0.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))));