diff --git a/README.md b/README.md index af47bd3..bd7d836 100644 --- a/README.md +++ b/README.md @@ -571,7 +571,7 @@ let mut sequencer = Sequencer::new(false, 2); ``` Adding new events, their start and stop times as well as fade-in and fade-out envelopes -can be set. The sequencer returns an `ID` that can be used for later edits to the events. +can be set. The sequencer returns an `EventId` that can be used for later edits to the event. ```rust // Add a new event with start time 1.0 seconds and end time 2.0 seconds. @@ -619,7 +619,7 @@ This means that `noise() | noise()` is a stereo noise source, for example. Pseudorandom phase is an attempt to decorrelate different channels of audio. It is also used to pick sample points for envelopes, contributing to a "warmer" sound. -To override pseudorandom phase in a noise component (`brown`, `mls`, `noise`, `pink` and `white` opcodes), +To override pseudorandom phase in a noise component (`brown`, `mls`, `mls_bits`, `noise`, `pink` and `white` opcodes), use the `seed` builder method. For example, `noise().seed(1)`. ### Oscillators diff --git a/benches/benchmark.rs b/benches/benchmark.rs index 4623a7b..b0f7f4a 100644 --- a/benches/benchmark.rs +++ b/benches/benchmark.rs @@ -87,6 +87,36 @@ fn phaser_bench(_dummy: usize) -> Wave { ) } +fn lowpass_bench(_dummy: usize) -> Wave { + Wave::render( + 44100.0, + 1.0, + &mut ((noise() + | lfo(|t| { + ( + xerp11(1000.0, 10000.0, sin_hz(1.0, t)), + xerp11(1.0, 2.0, sin_hz(2.0, t)), + ) + })) + >> lowpass().subsample(1)), + ) +} + +fn lowpass16_bench(_dummy: usize) -> Wave { + Wave::render( + 44100.0, + 1.0, + &mut ((noise() + | lfo(|t| { + ( + xerp11(1000.0, 10000.0, sin_hz(1.0, t)), + xerp11(1.0, 2.0, sin_hz(2.0, t)), + ) + })) + >> lowpass().subsample(16)), + ) +} + fn criterion_benchmark(c: &mut Criterion) { c.bench_function("netpass", |b| b.iter(|| netpass_bench(black_box(0)))); c.bench_function("sine", |b| b.iter(|| sine_bench(black_box(0)))); @@ -100,6 +130,8 @@ fn criterion_benchmark(c: &mut Criterion) { c.bench_function("reverb", |b| b.iter(|| reverb_bench(black_box(0)))); c.bench_function("limiter", |b| b.iter(|| limiter_bench(black_box(0)))); c.bench_function("phaser", |b| b.iter(|| phaser_bench(black_box(0)))); + c.bench_function("lowpass", |b| b.iter(|| lowpass_bench(black_box(0)))); + c.bench_function("lowpass16", |b| b.iter(|| lowpass16_bench(black_box(0)))); } criterion_group!(benches, criterion_benchmark); diff --git a/src/combinator.rs b/src/combinator.rs index 8c75ce7..6c5624f 100644 --- a/src/combinator.rs +++ b/src/combinator.rs @@ -179,81 +179,81 @@ pub struct An(pub X); impl core::ops::Deref for An { type Target = X; - #[inline] + #[inline(always)] fn deref(&self) -> &Self::Target { &self.0 } } impl core::ops::DerefMut for An { - #[inline] + #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 } } -// We relay some calls preferentially to the underlying AudioNode -// - otherwise the AudioUnit implementation would be picked. +// We relay some calls preferentially to the underlying `AudioNode` +// - otherwise the `AudioUnit` implementation would be picked. impl An { - #[inline] + #[inline(always)] pub fn reset(&mut self) { self.0.reset(); } - #[inline] + #[inline(always)] pub fn set_sample_rate(&mut self, sample_rate: f64) { self.0.set_sample_rate(sample_rate); } - #[inline] + #[inline(always)] pub fn tick(&mut self, input: &Frame) -> Frame { self.0.tick(input) } - #[inline] + #[inline(always)] pub fn process(&mut self, size: usize, input: &BufferRef, output: &mut BufferMut) { self.0.process(size, input, output); } - #[inline] + #[inline(always)] pub fn set(&mut self, setting: Setting) { self.0.set(setting); } - #[inline] + #[inline(always)] pub fn route(&mut self, input: &SignalFrame, frequency: f64) -> SignalFrame { self.0.route(input, frequency) } - #[inline] + #[inline(always)] pub fn inputs(&self) -> usize { self.0.inputs() } - #[inline] + #[inline(always)] pub fn outputs(&self) -> usize { self.0.outputs() } - #[inline] + #[inline(always)] pub fn set_hash(&mut self, hash: u64) { self.0.set_hash(hash); } - #[inline] + #[inline(always)] pub fn ping(&mut self, probe: bool, hash: AttoHash) -> AttoHash { self.0.ping(probe, hash) } - #[inline] + #[inline(always)] pub fn get_mono(&mut self) -> f32 { self.0.get_mono() } - #[inline] + #[inline(always)] pub fn get_stereo(&mut self) -> (f32, f32) { self.0.get_stereo() } - #[inline] + #[inline(always)] pub fn filter_mono(&mut self, x: f32) -> f32 { self.0.filter_mono(x) } - #[inline] + #[inline(always)] pub fn filter_stereo(&mut self, x: f32, y: f32) -> (f32, f32) { self.0.filter_stereo(x, y) } /// This builder method sets oscillator initial phase in 0...1, - /// overriding pseudorandom phase. The setting takes effect immediately. + /// overriding pseudorandom phase. The setting takes effect immediately (the node is reset). /// /// ### Example (Square Wave At 110 Hz With Initial Phase 0.5) /// ``` @@ -267,8 +267,8 @@ impl An { } /// This builder method sets noise generator seed, - /// overriding pseudorandom phase. The setting takes effect immediately. - /// Works with opcodes `mls`, `noise`, `white`, `pink` and `brown`. + /// overriding pseudorandom phase. The setting takes effect immediately (the node is reset). + /// Works with opcodes `mls`, `mls_bits`, `noise`, `white`, `pink` and `brown`. pub fn seed(mut self, seed: u64) -> Self { self.set(Setting::seed(seed).left()); self.reset(); @@ -277,12 +277,25 @@ impl An { /// This builder method sets the average interval (in seconds) /// between samples in envelopes. The setting takes effect immediately. + /// The default interval is 2 ms. /// 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 } + + /// This builder method sets the subsampling `period` + /// between reads of non-audio inputs (frequency, Q and gain) in filters. + /// The non-audio inputs are read every `period` samples. + /// The setting has no effect on filters that do not have non-audio inputs (for example, `bell_hz`). + /// The default period is 1 (that is, no subsampling). + /// Works with opcodes `lowpass`, `highpass`, `bandpass`, `notch`, `peak`, + /// `allpass`, `bell`, `lowshelf` and `highshelf`. + pub fn subsample(mut self, period: u32) -> Self { + self.set(Setting::subsample(period)); + self + } } impl Neg for An diff --git a/src/fft.rs b/src/fft.rs index 9d9b4bf..e7a58b1 100644 --- a/src/fft.rs +++ b/src/fft.rs @@ -1,145 +1,100 @@ //! Interface to microfft. -use core::array::from_fn; use microfft::inverse::*; use microfft::real::*; use num_complex::Complex32; -/// Perform real-valued FFT. The length of `input` must be a power of two between 2 and 32768. -/// The length of `output` must be half that of `input` plus one. -pub fn real_fft(input: &[f32], output: &mut [Complex32]) { - assert!(input.len() == (output.len() - 1) * 2); - match input.len() { - 2 => { - let mut tmp: [f32; 2] = from_fn(|i| input[i]); - output[..1].copy_from_slice(rfft_2(&mut tmp)); - } - 4 => { - let mut tmp: [f32; 4] = from_fn(|i| input[i]); - output[..2].copy_from_slice(rfft_4(&mut tmp)); - } - 8 => { - let mut tmp: [f32; 8] = from_fn(|i| input[i]); - output[..4].copy_from_slice(rfft_8(&mut tmp)); - } - 16 => { - let mut tmp: [f32; 16] = from_fn(|i| input[i]); - output[..8].copy_from_slice(rfft_16(&mut tmp)); - } - 32 => { - let mut tmp: [f32; 32] = from_fn(|i| input[i]); - output[..16].copy_from_slice(rfft_32(&mut tmp)); - } - 64 => { - let mut tmp: [f32; 64] = from_fn(|i| input[i]); - output[..32].copy_from_slice(rfft_64(&mut tmp)); - } - 128 => { - let mut tmp: [f32; 128] = from_fn(|i| input[i]); - output[..64].copy_from_slice(rfft_128(&mut tmp)); - } - 256 => { - let mut tmp: [f32; 256] = from_fn(|i| input[i]); - output[..128].copy_from_slice(rfft_256(&mut tmp)); - } - 512 => { - let mut tmp: [f32; 512] = from_fn(|i| input[i]); - output[..256].copy_from_slice(rfft_512(&mut tmp)); - } - 1024 => { - let mut tmp: [f32; 1024] = from_fn(|i| input[i]); - output[..512].copy_from_slice(rfft_1024(&mut tmp)); - } - 2048 => { - let mut tmp: [f32; 2048] = from_fn(|i| input[i]); - output[..1024].copy_from_slice(rfft_2048(&mut tmp)); - } - 4096 => { - let mut tmp: [f32; 4096] = from_fn(|i| input[i]); - output[..2048].copy_from_slice(rfft_4096(&mut tmp)); - } - 8192 => { - let mut tmp: [f32; 8192] = from_fn(|i| input[i]); - output[..4096].copy_from_slice(rfft_8192(&mut tmp)); - } - 16384 => { - let mut tmp: [f32; 16384] = from_fn(|i| input[i]); - output[..8192].copy_from_slice(rfft_16384(&mut tmp)); - } - 32768 => { - let mut tmp: [f32; 32768] = from_fn(|i| input[i]); - output[..16384].copy_from_slice(rfft_32768(&mut tmp)); - } - _ => panic!("Unsupported FFT length."), +/// Perform real-valued FFT in-place. +/// The length of `data` must be a power of two between 2 and 32768. +/// The Nyquist frequency will be packed into the imaginary part of DC (the first element). +/// Returns the slice transmuted into a slice of `Complex32`. +pub fn real_fft(data: &mut [f32]) -> &mut [Complex32] { + match data.len() { + 2 => rfft_2(data.try_into().unwrap()).as_mut_slice(), + 4 => rfft_4(data.try_into().unwrap()).as_mut_slice(), + 8 => rfft_8(data.try_into().unwrap()).as_mut_slice(), + 16 => rfft_16(data.try_into().unwrap()).as_mut_slice(), + 32 => rfft_32(data.try_into().unwrap()).as_mut_slice(), + 64 => rfft_64(data.try_into().unwrap()).as_mut_slice(), + 128 => rfft_128(data.try_into().unwrap()).as_mut_slice(), + 256 => rfft_256(data.try_into().unwrap()).as_mut_slice(), + 512 => rfft_512(data.try_into().unwrap()).as_mut_slice(), + 1024 => rfft_1024(data.try_into().unwrap()).as_mut_slice(), + 2048 => rfft_2048(data.try_into().unwrap()).as_mut_slice(), + 4096 => rfft_4096(data.try_into().unwrap()).as_mut_slice(), + 8192 => rfft_8192(data.try_into().unwrap()).as_mut_slice(), + 16384 => rfft_16384(data.try_into().unwrap()).as_mut_slice(), + 32768 => rfft_32768(data.try_into().unwrap()).as_mut_slice(), + _ => panic!("invalid FFT length {}", data.len()), + } +} + +/// Move Nyquist frequency to its proper place from the imaginary part of DC (the first element). +/// The length of `data` must be 2 plus a power of two between 2 and 32768. +pub fn fix_nyquist(data: &mut [f32]) { + let length = data.len(); + data[length - 2] = data[1]; + data[length - 1] = 0.0; + data[1] = 0.0; +} + +/// Fix negative frequencies in the power-of-two array to make the inverse FFT real-valued. +pub fn fix_negative(data: &mut [Complex32]) { + let length = data.len(); + for i in length / 2 + 1..length { + data[i] = data[length - i].conj(); } - output[output.len() - 1] = Complex32::new(output[0].im, 0.0); - output[0] = Complex32::new(output[0].re, 0.0); } -/// Perform inverse FFT. The length of `input` must be a power of two between 2 and 32768. -/// The length of `output` must be equal to that of `input`. -pub fn inverse_fft(input: &[Complex32], output: &mut [Complex32]) { - assert!(input.len() == output.len()); - match input.len() { +/// Perform inverse FFT in-place. +/// The length of `data` must be a power of two between 2 and 32768. +#[allow(unused_must_use)] +pub fn inverse_fft(data: &mut [Complex32]) { + match data.len() { 2 => { - let mut tmp: [Complex32; 2] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_2(&mut tmp)); + ifft_2(data.try_into().unwrap()); } 4 => { - let mut tmp: [Complex32; 4] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_4(&mut tmp)); + ifft_4(data.try_into().unwrap()); } 8 => { - let mut tmp: [Complex32; 8] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_8(&mut tmp)); + ifft_8(data.try_into().unwrap()); } 16 => { - let mut tmp: [Complex32; 16] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_16(&mut tmp)); + ifft_16(data.try_into().unwrap()); } 32 => { - let mut tmp: [Complex32; 32] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_32(&mut tmp)); + ifft_32(data.try_into().unwrap()); } 64 => { - let mut tmp: [Complex32; 64] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_64(&mut tmp)); + ifft_64(data.try_into().unwrap()); } 128 => { - let mut tmp: [Complex32; 128] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_128(&mut tmp)); + ifft_128(data.try_into().unwrap()); } 256 => { - let mut tmp: [Complex32; 256] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_256(&mut tmp)); + ifft_256(data.try_into().unwrap()); } 512 => { - let mut tmp: [Complex32; 512] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_512(&mut tmp)); + ifft_512(data.try_into().unwrap()); } 1024 => { - let mut tmp: [Complex32; 1024] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_1024(&mut tmp)); + ifft_1024(data.try_into().unwrap()); } 2048 => { - let mut tmp: [Complex32; 2048] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_2048(&mut tmp)); + ifft_2048(data.try_into().unwrap()); } 4096 => { - let mut tmp: [Complex32; 4096] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_4096(&mut tmp)); + ifft_4096(data.try_into().unwrap()); } 8192 => { - let mut tmp: [Complex32; 8192] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_8192(&mut tmp)); + ifft_8192(data.try_into().unwrap()); } 16384 => { - let mut tmp: [Complex32; 16384] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_16384(&mut tmp)); + ifft_16384(data.try_into().unwrap()); } 32768 => { - let mut tmp: [Complex32; 32768] = from_fn(|i| input[i]); - output.copy_from_slice(ifft_32768(&mut tmp)); + ifft_32768(data.try_into().unwrap()); } - _ => panic!("Unsupported FFT length."), + _ => panic!("invalid FFT length {}", data.len()), } } diff --git a/src/resynth.rs b/src/resynth.rs index c97ba57..58990f7 100644 --- a/src/resynth.rs +++ b/src/resynth.rs @@ -25,12 +25,8 @@ pub struct FftWindow { length: usize, /// Input samples for each input channel. input: Vec>, - /// Input samples for each input channel in frequency domain. - input_fft: Vec>, - /// Output samples for each output channel in frequency domain. - output_fft: Vec>, /// Output samples for each output channel. - output: Vec>, + output: Vec>, /// Sample rate for convenience. sample_rate: f32, /// Current index into input and output vectors. @@ -46,16 +42,6 @@ impl FftWindow { self.input.len() } - /// Extend positive frequency values to negative frequencies to keep the inverse FFT result - /// real only. - pub(crate) fn extend_values(&mut self) { - for channel in 0..self.outputs() { - for i in self.length() / 2 + 1..self.length() { - self.output_fft[channel][i] = self.output_fft[channel][self.length() - i].conj(); - } - } - } - /// Number of output channels. #[inline] pub fn outputs(&self) -> usize { @@ -110,16 +96,16 @@ impl FftWindow { self.length as f64 / (WINDOWS as f64 * self.sample_rate as f64) } - /// Get forward vectors for forward FFT. + /// Get forward vector for forward FFT. #[inline] - pub(crate) fn forward_vectors(&mut self, channel: usize) -> (&Vec, &mut Vec) { - (&self.input[channel], &mut self.input_fft[channel]) + pub(crate) fn forward_vector(&mut self, channel: usize) -> &mut Vec { + &mut self.input[channel] } - /// Get inverse vectors for inverse FFT. + /// Get inverse vector for inverse FFT. #[inline] - pub(crate) fn inverse_vectors(&mut self, channel: usize) -> (&Vec, &mut Vec) { - (&self.output_fft[channel], &mut self.output[channel]) + pub(crate) fn inverse_vector(&mut self, channel: usize) -> &mut Vec { + &mut self.output[channel] } /// FFT window length. This is a power of two and at least four. @@ -146,40 +132,31 @@ impl FftWindow { /// Get input value at bin `i` of `channel`. #[inline] pub fn at(&self, channel: usize, i: usize) -> Complex32 { - self.input_fft[channel][i] + Complex32::new(self.input[channel][i * 2], self.input[channel][i * 2 + 1]) } /// Return output value for bin `i` of `channel`. #[inline] pub fn at_output(&self, channel: usize, i: usize) -> Complex32 { - self.output_fft[channel][i] + self.output[channel][i] } /// Set output value for bin `i` of `channel`. #[inline] pub fn set(&mut self, channel: usize, i: usize, value: Complex32) { - self.output_fft[channel][i] = value; + self.output[channel][i] = value; } /// Create new window. pub fn new(length: usize, index: usize, inputs: usize, outputs: usize) -> Self { - let mut window = Self { + Self { length, - input: vec![vec!(0.0; length); inputs], - input_fft: Vec::new(), - output_fft: Vec::new(), - output: vec![vec!(0.0; length); outputs], + input: vec![vec!(0.0; length + 2); inputs], + output: vec![vec!(Complex32::ZERO; length); outputs], sample_rate: DEFAULT_SR as f32, index, samples: 0, - }; - window - .input_fft - .resize(inputs, vec![Complex32::default(); window.bins()]); - window - .output_fft - .resize(outputs, vec![Complex32::default(); length]); - window + } } /// Set the sample rate. @@ -198,13 +175,13 @@ impl FftWindow { /// Read output for current index. #[inline] pub(crate) fn read>(&self, window_value: f32) -> Frame { - Frame::generate(|channel| convert(self.output[channel][self.index] * window_value)) + Frame::generate(|channel| convert(self.output[channel][self.index].re * window_value)) } /// Set FFT outputs to all zeros. pub fn clear_output(&mut self) { for i in 0..self.outputs() { - self.output_fft[i].fill(Complex32::default()); + self.output[i].fill(Complex32::ZERO); } } @@ -222,7 +199,7 @@ impl FftWindow { self.input[channel].fill(0.0); } for channel in 0..self.outputs() { - self.output[channel].fill(0.0); + self.output[channel].fill(Complex32::ZERO); } } @@ -265,8 +242,6 @@ where processing: F, /// Sample rate. sample_rate: f64, - /// Temporary vector for FFT. - scratch: Vec, /// Number of processed samples. samples: u64, /// Normalizing term for FFT and overlap-add. @@ -317,8 +292,6 @@ where ), ]; - let scratch = vec![Complex32::default(); window_length]; - Self { _marker: core::marker::PhantomData, window, @@ -326,7 +299,6 @@ where window_function, processing, sample_rate: DEFAULT_SR, - scratch, samples: 0, z: 2.0 / 3.0, } @@ -373,22 +345,20 @@ where for i in 0..WINDOWS { if self.window[i].is_fft_time() { for channel in 0..I::USIZE { - let (input, input_fft) = self.window[i].forward_vectors(channel); - super::fft::real_fft(input, input_fft); + let input = self.window[i].forward_vector(channel); + let fft_length = input.len() - 2; + super::fft::real_fft(&mut input[..fft_length]); + super::fft::fix_nyquist(input); } self.window[i].clear_output(); (self.processing)(&mut self.window[i]); - self.window[i].extend_values(); - for channel in 0..O::USIZE { - let (output_fft, output_scalar) = self.window[i].inverse_vectors(channel); - super::fft::inverse_fft(output_fft, &mut self.scratch); - for (x, y) in output_scalar.iter_mut().zip(self.scratch.iter()) { - *x = y.re; - } + let output = self.window[i].inverse_vector(channel); + super::fft::fix_negative(output); + super::fft::inverse_fft(output); } } } diff --git a/src/reverb.rs b/src/reverb.rs index b8c7fa1..d3e2545 100644 --- a/src/reverb.rs +++ b/src/reverb.rs @@ -37,9 +37,6 @@ pub fn reverb_fitness(reverb: An>) -> let mut fitness = 0.0; - let mut spectrum = Vec::::new(); - spectrum.resize(response.length() / 2 + 1, Complex32::new(0.0, 0.0)); - // Deal with left, right and center signals. for channel in 0..=1 { if channel == 0 || channel == 1 { @@ -65,7 +62,7 @@ pub fn reverb_fitness(reverb: An>) -> ); response.set(channel, i, response.at(channel, i) * w); } - let data = match channel { + let mut data = match channel { 0 | 1 => response.channel(channel).clone(), _ => { let mut stereo = response.channel(0).clone(); @@ -75,7 +72,7 @@ pub fn reverb_fitness(reverb: An>) -> stereo } }; - super::fft::real_fft(&data, &mut spectrum); + let spectrum = super::fft::real_fft(&mut data); /* let spectral_weight = 0.001; let flatness_weight = 1.0; diff --git a/src/setting.rs b/src/setting.rs index c22744f..3b1770b 100644 --- a/src/setting.rs +++ b/src/setting.rs @@ -47,6 +47,9 @@ pub enum Parameter { Seed(u64), /// Average sampling interval in seconds for envelopes. Interval(f32), + /// Subsampling period of non-audio inputs (frequency, Q and gain) in filters: + /// the non-audio inputs are read every this many samples. + Subsample(u32), } /// Address specifies location to apply setting in a graph. @@ -173,6 +176,14 @@ impl Setting { address: ArrayVec::new(), } } + /// Create setting for subsampling `period` of non-audio inputs (frequency, Q and gain) for filters. + /// Non-audio inputs are read every `period` samples. + pub fn subsample(period: u32) -> Self { + Self { + parameter: Parameter::Subsample(period), + 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/svf.rs b/src/svf.rs index 8a3399a..77f5f9e 100644 --- a/src/svf.rs +++ b/src/svf.rs @@ -151,7 +151,7 @@ impl SvfCoefs { } /// Calculate coefficients for a bell filter. - /// Gain is amplitude gain (gain > 0). + /// Gain is amplitude gain (`gain` > 0). pub fn bell(sample_rate: F, cutoff: F, q: F, gain: F) -> Self { let a = sqrt(gain); let g = tan(F::from_f64(f64::PI) * cutoff / sample_rate); @@ -174,7 +174,7 @@ impl SvfCoefs { } /// Calculate coefficients for a low shelf filter. - /// Gain is amplitude gain (gain > 0). + /// Gain is amplitude gain (`gain` > 0). pub fn lowshelf(sample_rate: F, cutoff: F, q: F, gain: F) -> Self { let a = sqrt(gain); let g = tan(F::from_f64(f64::PI) * cutoff / sample_rate) / sqrt(a); @@ -197,7 +197,7 @@ impl SvfCoefs { } /// Calculate coefficients for a high shelf filter. - /// Gain is amplitude gain (gain > 0). + /// Gain is amplitude gain (`gain` > 0). pub fn highshelf(sample_rate: F, cutoff: F, q: F, gain: F) -> Self { let a = sqrt(gain); let g = tan(F::from_f64(f64::PI) * cutoff / sample_rate) * sqrt(a); @@ -227,11 +227,6 @@ impl SvfCoefs { pub trait SvfMode: Clone + Default + Sync + Send { /// Number of inputs, which includes the audio input. Equal to the number of continuous parameters plus one. type Inputs: Size; - /// Format of settings for this mode. - type Setting: Sync + Send + Clone + Default; - - /// Update coefficients and parameters from settings. - fn set(&mut self, setting: Self::Setting, params: &mut SvfParams, coefs: &mut SvfCoefs); /// Update coefficients and state from the full set of parameters. fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs); @@ -260,11 +255,10 @@ pub trait SvfMode: Clone + Default + Sync + Send { #[allow(unused_variables)] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, - ) { - } + ); /// Response function. fn response(&self, params: &SvfParams, frequency: f64) -> Complex64; @@ -296,31 +290,22 @@ impl LowpassMode { impl SvfMode for LowpassMode { type Inputs = U3; - type Setting = (F, F); - fn set( - &mut self, - (cutoff, q): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::lowpass(params.sample_rate, params.cutoff, params.q); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); if cutoff != params.cutoff || q != params.q { + //if squared(cutoff - params.cutoff) + squared(q - params.q) > F::zero() { params.cutoff = cutoff; params.q = q; self.update(params, coefs); @@ -355,30 +340,20 @@ impl HighpassMode { impl SvfMode for HighpassMode { type Inputs = U3; - type Setting = (F, F); - fn set( - &mut self, - (cutoff, q): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::highpass(params.sample_rate, params.cutoff, params.q); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); if cutoff != params.cutoff || q != params.q { params.cutoff = cutoff; params.q = q; @@ -414,30 +389,20 @@ impl BandpassMode { impl SvfMode for BandpassMode { type Inputs = U3; - type Setting = (F, F); - fn set( - &mut self, - (cutoff, q): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::bandpass(params.sample_rate, params.cutoff, params.q); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); if cutoff != params.cutoff || q != params.q { params.cutoff = cutoff; params.q = q; @@ -473,30 +438,20 @@ impl NotchMode { impl SvfMode for NotchMode { type Inputs = U3; - type Setting = (F, F); - fn set( - &mut self, - (cutoff, q): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::notch(params.sample_rate, params.cutoff, params.q); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); if cutoff != params.cutoff || q != params.q { params.cutoff = cutoff; params.q = q; @@ -532,30 +487,20 @@ impl PeakMode { impl SvfMode for PeakMode { type Inputs = U3; - type Setting = (F, F); - fn set( - &mut self, - (cutoff, q): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::peak(params.sample_rate, params.cutoff, params.q); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); if cutoff != params.cutoff || q != params.q { params.cutoff = cutoff; params.q = q; @@ -592,30 +537,20 @@ impl AllpassMode { impl SvfMode for AllpassMode { type Inputs = U3; - type Setting = (F, F); - fn set( - &mut self, - (cutoff, q): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::allpass(params.sample_rate, params.cutoff, params.q); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); if cutoff != params.cutoff || q != params.q { params.cutoff = cutoff; params.q = q; @@ -652,32 +587,21 @@ impl BellMode { impl SvfMode for BellMode { type Inputs = U4; - type Setting = (F, F, F); - fn set( - &mut self, - (cutoff, q, gain): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - params.gain = gain; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::bell(params.sample_rate, params.cutoff, params.q, params.gain); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; - let gain = input[3]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); + let gain = F::from_f32(input[3]); if cutoff != params.cutoff || q != params.q || gain != params.gain { params.cutoff = cutoff; params.q = q; @@ -717,32 +641,21 @@ impl LowshelfMode { impl SvfMode for LowshelfMode { type Inputs = U4; - type Setting = (F, F, F); - fn set( - &mut self, - (cutoff, q, gain): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - params.gain = gain; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::lowshelf(params.sample_rate, params.cutoff, params.q, params.gain); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; - let gain = input[3]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); + let gain = F::from_f32(input[3]); if cutoff != params.cutoff || q != params.q || gain != params.gain { params.cutoff = cutoff; params.q = q; @@ -785,32 +698,21 @@ impl HighshelfMode { impl SvfMode for HighshelfMode { type Inputs = U4; - type Setting = (F, F, F); - fn set( - &mut self, - (cutoff, q, gain): Self::Setting, - params: &mut SvfParams, - coefs: &mut SvfCoefs, - ) { - params.cutoff = cutoff; - params.q = q; - params.gain = gain; - self.update(params, coefs); - } + #[inline] fn update(&mut self, params: &SvfParams, coefs: &mut SvfCoefs) { *coefs = SvfCoefs::highshelf(params.sample_rate, params.cutoff, params.q, params.gain); } #[inline] fn update_inputs( &mut self, - input: &Frame, + input: &Frame, params: &mut SvfParams, coefs: &mut SvfCoefs, ) { - let cutoff = input[1]; - let q = input[2]; - let gain = input[3]; + let cutoff = F::from_f32(input[1]); + let q = F::from_f32(input[2]); + let gain = F::from_f32(input[3]); if cutoff != params.cutoff || q != params.q || gain != params.gain { params.cutoff = cutoff; params.q = q; @@ -854,6 +756,8 @@ where coefs: SvfCoefs, ic1eq: F, ic2eq: F, + period: u32, + counter: u32, } impl Svf @@ -873,6 +777,8 @@ where coefs, ic1eq: F::zero(), ic2eq: F::zero(), + period: 1, + counter: 0, } } @@ -916,6 +822,7 @@ where fn reset(&mut self) { self.ic1eq = F::zero(); self.ic2eq = F::zero(); + self.counter = 0; } fn set_sample_rate(&mut self, sample_rate: f64) { @@ -925,11 +832,14 @@ where #[inline] fn tick(&mut self, input: &Frame) -> Frame { - // Update parameters from input. - let input = Frame::generate(|i| convert(input[i])); - self.mode - .update_inputs(&input, &mut self.params, &mut self.coefs); - let v0 = input[0]; + self.counter += 1; + if self.counter >= self.period { + self.counter = 0; + // Update parameters from input. + self.mode + .update_inputs(input, &mut self.params, &mut self.coefs); + } + let v0 = F::from_f32(input[0]); let v3 = v0 - self.ic2eq; let v1 = self.coefs.a1 * self.ic1eq + self.coefs.a2 * v3; let v2 = self.ic2eq + self.coefs.a2 * self.ic1eq + self.coefs.a3 * v3; @@ -941,6 +851,13 @@ where .into() } + fn set(&mut self, setting: Setting) { + if let Parameter::Subsample(period) = setting.parameter() { + self.period = max(1, *period); + self.counter = 0; + } + } + fn route(&mut self, input: &SignalFrame, frequency: f64) -> SignalFrame { let mut output = SignalFrame::new(self.outputs()); output.set( @@ -954,7 +871,6 @@ where } /// Simper SVF with fixed parameters. -/// Setting: (center, Q, gain) for low shelf, high shelf and bell modes, otherwise (center, Q). /// - Input 0: audio /// - Output 0: filtered audio #[derive(Default, Clone)] diff --git a/src/wave.rs b/src/wave.rs index dd4c08e..3d43c18 100644 --- a/src/wave.rs +++ b/src/wave.rs @@ -170,6 +170,10 @@ impl Wave { /// Remove channel `channel` from this wave. Returns the removed channel. pub fn remove_channel(&mut self, channel: usize) -> Vec { assert!(channel < self.channels()); + if self.channels() == 1 { + // This is the last channel, set length to zero. + self.len = 0; + } self.vec.remove(channel) } diff --git a/src/wavetable.rs b/src/wavetable.rs index e68fbc9..89e8b91 100644 --- a/src/wavetable.rs +++ b/src/wavetable.rs @@ -57,7 +57,7 @@ where let length = clamp(32, 8192, target_len.next_power_of_two()); - let mut a = vec![Complex32::new(0.0, 0.0); length]; + let mut a = vec![Complex32::ZERO; length]; for i in 1..=harmonics { let f = pitch * i as f64; @@ -72,12 +72,11 @@ where } } - let mut b = vec![Complex32::new(0.0, 0.0); length]; - super::fft::inverse_fft(&a, &mut b); + super::fft::inverse_fft(&mut a); let z = sqrt(length as f32); - b.iter().map(|x| x.im * z).collect() + a.iter().map(|x| x.im * z).collect() } #[derive(Clone)] @@ -124,14 +123,16 @@ impl Wavetable { Wavetable { table } } - /// Create new wavetable from a single cycle wave. `min_pitch` and `max_pitch` are the minimum + /// Create new wavetable from a single cycle wave. + /// The length of `wave` must be a power of two between 2 and 32768. + /// `min_pitch` and `max_pitch` are the minimum /// and maximum base frequencies in Hz (for example, 20.0 and 20_000.0). /// `tables_per_octave` is the number of wavetables per octave /// (for example, 4.0). The overall scale of numbers in `wave` is ignored; /// the wavetable is normalized to -1...1. pub fn from_wave(min_pitch: f64, max_pitch: f64, tables_per_octave: f64, wave: &[f32]) -> Self { - let mut spectrum = vec![Complex32::new(0.0, 0.0); wave.len() / 2 + 1]; - super::fft::real_fft(wave, &mut spectrum); + let mut data = Vec::from(wave); + let spectrum = super::fft::real_fft(&mut data); let phase = |i: u32| { if (i as usize) < spectrum.len() { spectrum[i as usize].arg() as f64 / f64::TAU @@ -139,7 +140,7 @@ impl Wavetable { 0.0 } }; - let amplitude = |_p: f64, i: u32| { + let amplitude = |_pitch: f64, i: u32| { if (i as usize) < spectrum.len() { spectrum[i as usize].norm() as f64 } else { diff --git a/tests/test_basic.rs b/tests/test_basic.rs index 7ca4926..bc0c9a8 100644 --- a/tests/test_basic.rs +++ b/tests/test_basic.rs @@ -648,23 +648,6 @@ fn test_basic() { #[test] /// Test a pass-through resynthesizer. fn test_resynth() { - // Sanity test FFT roundtrip. - const WINDOW: usize = 16; - let mut rnd = Rnd::from_u64(1); - let fft_input: [f32; WINDOW] = core::array::from_fn(|_| rnd.f32()); - let mut fft_output = [Complex32::ZERO; WINDOW]; - fft::real_fft(&fft_input, &mut fft_output[0..WINDOW / 2 + 1]); - let mut fft_round = [Complex32::ZERO; WINDOW]; - for i in WINDOW / 2 + 1..WINDOW { - fft_output[i] = fft_output[WINDOW - i].conj(); - } - fft::inverse_fft(&fft_output, &mut fft_round); - for i in 0..WINDOW { - let tolerance = 1.0e-6; - let norm = (fft_input[i] - fft_round[i].re).abs() + fft_round[i].im.abs(); - assert!(norm <= tolerance); - } - let window = 32; let mut synth: An> = resynth(window, |fft| { for i in 0..fft.bins() { diff --git a/tests/test_flow.rs b/tests/test_flow.rs index 8d02ef9..9ccd626 100644 --- a/tests/test_flow.rs +++ b/tests/test_flow.rs @@ -37,28 +37,29 @@ where filter.set_sample_rate(sample_rate); let mut input = 1.0; - let mut in_buffer = Vec::with_capacity(length); + let mut buffer = Vec::with_capacity(length); // Try to remove effect of DC by warming up the filter. for _i in 0..length / 2 { filter.filter_mono(0.0); } for _i in 0..length { let x = filter.filter_mono(input); - in_buffer.push(x); + buffer.push(x); input = 0.0; } - let mut buffer = vec![Complex32::ZERO; length / 2 + 1]; - - real_fft(&in_buffer, &mut buffer); + let spectrum = real_fft(&mut buffer); let mut f = 10.0; while f <= 22_000.0 { let i = round(f * length as f64 / sample_rate) as usize; + if i >= spectrum.len() { + break; + } let f_i = i as f64 / length as f64 * sample_rate; let reported = filter.response(0, f_i).unwrap(); let reported = Complex32::new(reported.re as f32, reported.im as f32); - let response = buffer[i]; + let response = spectrum[i]; if !is_equal_response(reported, response) { eprintln!( "{} Hz reported ({}, {}) measured ({}, {})", @@ -250,7 +251,6 @@ fn test_responses() { #[test] fn test_allpass() { let length = 0x8000; - let mut spectrum = vec![Complex32::ZERO; length / 2 + 1]; let allpasses: [Box; 12] = [ Box::new(pass()), @@ -270,8 +270,9 @@ fn test_allpass() { let impulse = Wave::render(DEFAULT_SR, 1.0 / DEFAULT_SR, &mut (impulse::())); for mut node in allpasses { - let response = impulse.filter(length as f64 / DEFAULT_SR, &mut *node); - real_fft(response.channel(0), &mut spectrum); + let mut response = impulse.filter(length as f64 / DEFAULT_SR, &mut *node); + let mut data = response.remove_channel(0); + let spectrum = real_fft(&mut data); // This tolerance has been tuned to a minimum value that allows the tests to pass. let tolerance = 1.0e-5; for s in &spectrum[1..] {