From 05b3efd85ef5f061a0688a12d0e9650ba64eba7f Mon Sep 17 00:00:00 2001 From: Sami Perttu Date: Sun, 20 Oct 2024 03:08:54 +0300 Subject: [PATCH] Update and fix. --- CHANGES.md | 2 ++ FUTURE.md | 1 + README.md | 3 +++ src/combinator.rs | 13 +++++++++++-- src/envelope.rs | 13 +++++++++++++ src/lib.rs | 2 +- src/net.rs | 3 ++- src/prelude.rs | 5 +++-- src/setting.rs | 9 +++++++++ src/wave.rs | 34 +++++++++++++++++++++++++--------- tests/test_basic.rs | 2 +- 11 files changed, 71 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 60ba73a..215f28f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,8 @@ - New builder notation for setting noise generator seed, for example, `noise().seed(1)`. - New opcode `biquad_bank()`. - `BiquadBank` parameters for channel `i` are now set with the syntax `Setting::biquad(...).index(i)`. +- New `Wave` methods `mix` and `mix_channel`. +- New builder notation for setting envelope sampling interval, for example, `lfo(|t| exp(-t)).interval(0.01)`. ### Version 0.20 diff --git a/FUTURE.md b/FUTURE.md index 95ee5a9..920310d 100644 --- a/FUTURE.md +++ b/FUTURE.md @@ -21,3 +21,4 @@ This is a list of feature ideas for the future. - Resampler with sinc interpolation. - Support feedback loops in `Net`. - Looping in `Sequencer`. +- Delay component that crossfades between taps. diff --git a/README.md b/README.md index 536f96c..af47bd3 100644 --- a/README.md +++ b/README.md @@ -1316,6 +1316,9 @@ The return type of the function - scalar or tuple - determines the number of out The samples are spaced at an average of 2 ms apart, jittered by noise derived from pseudorandom phase. The values in between are linearly interpolated. +To set a different sampling interval, use the `interval` builder method. +For example, `envelope(|t| clamp01(sqr_hz(10.0, t))).interval(0.01)` samples the envelope at 10 ms intervals. + `lfo` (Low Frequency Oscillator) is another name for `envelope`. #### Indexed And Fractional Generator Functions diff --git a/src/combinator.rs b/src/combinator.rs index 83a2067..3ebce21 100644 --- a/src/combinator.rs +++ b/src/combinator.rs @@ -253,7 +253,7 @@ impl An { } /// This builder method sets oscillator initial phase in 0...1, - /// overriding pseudorandom phase. + /// overriding pseudorandom phase. The setting takes effect immediately. /// /// ### Example (Square Wave At 110 Hz With Initial Phase 0.5) /// ``` @@ -267,13 +267,22 @@ impl An { } /// This builder method sets noise generator seed, - /// overriding pseudorandom phase. + /// overriding pseudorandom phase. The setting takes effect immediately. /// Works with opcodes `mls`, `noise`, `white`, `pink` and `brown`. pub fn seed(mut self, seed: u64) -> Self { self.set(Setting::seed(seed).left()); self.reset(); self } + + /// This builder method sets the average interval (in seconds) + /// between samples in envelopes. The setting takes effect immediately. + /// Works with opcodes `envelope`, `envelope2`, `envelope3`, `envelope_in`, + /// `lfo`, `lfo2`, `lfo3` and `lfo_in`. + pub fn interval(mut self, time: f32) -> Self { + self.set(Setting::interval(time)); + self + } } impl Neg for An diff --git a/src/envelope.rs b/src/envelope.rs index 1d90ab0..5d2366a 100644 --- a/src/envelope.rs +++ b/src/envelope.rs @@ -4,6 +4,7 @@ use super::audionode::*; use super::buffer::*; use super::combinator::*; use super::math::*; +use super::setting::*; use super::signal::*; use super::*; use core::marker::PhantomData; @@ -161,6 +162,12 @@ where } } + fn set(&mut self, setting: Setting) { + if let Parameter::Interval(time) = setting.parameter() { + self.interval = F::from_f32(*time); + } + } + fn set_hash(&mut self, hash: u64) { self.hash = hash; self.t_hash = hash; @@ -334,6 +341,12 @@ where } } + fn set(&mut self, setting: Setting) { + if let Parameter::Interval(time) = setting.parameter() { + self.interval = F::from_f32(*time); + } + } + fn set_hash(&mut self, hash: u64) { self.hash = hash; self.t_hash = hash; diff --git a/src/lib.rs b/src/lib.rs index 4cbd519..761dd85 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -23,9 +23,9 @@ use numeric_array::{ArrayLength, NumericArray}; use typenum::{U1, U4, U8}; use core::cmp::PartialEq; +use core::marker::{Send, Sync}; use core::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}; use core::ops::{BitAnd, BitOr, BitXor, Not, Shl, Shr}; -use std::marker::{Send, Sync}; use wide::{f32x8, f64x4, i32x8, u32x8}; diff --git a/src/net.rs b/src/net.rs index ccc6bf2..5fe667c 100644 --- a/src/net.rs +++ b/src/net.rs @@ -765,7 +765,8 @@ impl Net { /// Global outputs will be assigned to the outputs of the unit. /// If there are more global outputs than there are outputs in the unit, then a modulo /// is taken to plug all of them. - /// The previous global output sources become inputs to the unit. + /// If this is the first unit in the net, then global inputs are assigned to inputs of the unit; + /// if the net was not empty then the previous global output sources become inputs to the unit. /// Returns the ID of the new unit. /// /// ### Example (Lowpass And Highpass Filters In Series) diff --git a/src/prelude.rs b/src/prelude.rs index 5492e32..f127c4b 100644 --- a/src/prelude.rs +++ b/src/prelude.rs @@ -2654,7 +2654,7 @@ pub fn chorus( ) -> An> { (pass() & (pass() - | An(Envelope::new(0.01, move |t| { + | lfo(move |t| { ( lerp11( separation, @@ -2677,7 +2677,8 @@ pub fn chorus( fractal_noise(hash1(seed ^ 0xfedcba), 8, 0.45, t * (mod_frequency + 0.06)), ), ) - }))) + }) + .interval(0.01)) >> multitap::(separation, separation * 4.0 + variation)) * dc(0.2) } diff --git a/src/setting.rs b/src/setting.rs index 3985830..c22744f 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -45,6 +45,8 @@ pub enum Parameter { Phase(f32), /// Generator seed. Seed(u64), + /// Average sampling interval in seconds for envelopes. + Interval(f32), } /// Address specifies location to apply setting in a graph. @@ -164,6 +166,13 @@ impl Setting { address: ArrayVec::new(), } } + /// Create setting for envelope sampling interval in seconds. + pub fn interval(time: f32) -> Self { + Self { + parameter: Parameter::Interval(time), + address: ArrayVec::new(), + } + } /// Add indexed address to setting. pub fn index(mut self, index: usize) -> Self { self.address.push(Address::Index(index)); diff --git a/src/wave.rs b/src/wave.rs index 272e7ee..dd4c08e 100644 --- a/src/wave.rs +++ b/src/wave.rs @@ -107,6 +107,7 @@ impl Wave { } /// The sample rate of the wave. + #[inline] pub fn sample_rate(&self) -> f64 { self.sample_rate } @@ -157,6 +158,15 @@ impl Wave { self.vec.insert(channel, samples.into()); } + /// Mix from a vector of `samples` to an existing channel here starting from index `offset`. + /// The offset may be negative, in which case the first items from `samples` will be ignored. + /// Does not resize this wave - ignores samples that spill over from either end. + pub fn mix_channel(&mut self, channel: usize, offset: isize, samples: &[f32]) { + for i in max(offset, 0)..min(offset + samples.len() as isize, self.len() as isize) { + self.mix(channel, i as usize, samples[(i - offset) as usize]); + } + } + /// Remove channel `channel` from this wave. Returns the removed channel. pub fn remove_channel(&mut self, channel: usize) -> Vec { assert!(channel < self.channels()); @@ -169,12 +179,18 @@ impl Wave { self.vec[channel][index] } - /// Set sample to value. + /// Set sample to `value`. #[inline] pub fn set(&mut self, channel: usize, index: usize, value: f32) { self.vec[channel][index] = value; } + /// Add `value` to sample. + #[inline] + pub fn mix(&mut self, channel: usize, index: usize, value: f32) { + self.vec[channel][index] += value; + } + /// Insert a new frame of samples to the end of the wave. /// Pushing a scalar frame, the value is broadcast to any number of channels. /// Otherwise, the number of channels must match. @@ -271,7 +287,7 @@ impl Wave { /// use fundsp::hacker::*; /// let mut wave = Wave::render(44100.0, 1.0, &mut (sine_hz(60.0))); /// let amplitude = wave.amplitude(); - /// assert!(amplitude > 1.0 - 1.0e-5 && amplitude <= 1.0); + /// assert!(amplitude >= 1.0 - 1.0e-5 && amplitude <= 1.0); /// ``` pub fn amplitude(&self) -> f32 { let mut peak = 0.0; @@ -305,8 +321,8 @@ impl Wave { } } - /// Applies a fade-in envelope to the wave with a duration of `time` seconds. - /// The duration may not exceed the duration of the wave. + /// Applies a smooth fade-in envelope to the wave with a duration of `time` seconds. + /// If `time` is greater than the duration of the wave, then it will be set to the duration of the wave. /// /// ### Example /// @@ -316,7 +332,7 @@ impl Wave { /// wave.fade_in(1.0); /// ``` pub fn fade_in(&mut self, time: f64) { - assert!(time <= self.duration()); + let time = min(time, self.duration()); let fade_n = round(time * self.sample_rate()); for i in 0..fade_n as usize { let a = smooth5((i + 1) as f64 / (fade_n + 1.0)) as f32; @@ -326,8 +342,8 @@ impl Wave { } } - /// Applies a fade-out envelope to the wave with a duration of `time` seconds. - /// The duration may not exceed the duration of the wave. + /// Applies a smooth fade-out envelope to the wave with a duration of `time` seconds. + /// If `time` is greater than the duration of the wave, then it will be set to the duration of the wave. /// /// ### Example /// @@ -337,7 +353,7 @@ impl Wave { /// wave.fade_out(5.0); /// ``` pub fn fade_out(&mut self, time: f64) { - assert!(time <= self.duration()); + let time = min(time, self.duration()); let fade_n = round(time * self.sample_rate()); let fade_i = fade_n as usize; for i in 0..fade_i { @@ -350,7 +366,7 @@ impl Wave { } /// Applies both fade-in and fade-out to the wave with a duration of `time` seconds. - /// The duration may not exceed the duration of the wave. + /// If `time` is greater than the duration of the wave, then it will be set to the duration of the wave. /// /// ### Example /// diff --git a/tests/test_basic.rs b/tests/test_basic.rs index 1bd961e..7ca4926 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -342,7 +342,7 @@ fn test_basic() { check_wave_filter(&input, tap_node.clone() | tap_node.clone()); // Check cycle. - let mut cycle = Net::new(1, 1); + let mut cycle = Net::new(2, 1); let id1 = cycle.chain(Box::new(join::())); let id2 = cycle.chain(Box::new(pass())); assert_eq!(cycle.error(), &None);