From 3e4a6b90e750379f23661d2ab0a56c5538a8d66d Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Mon, 26 Feb 2024 20:20:18 +0100 Subject: [PATCH 01/12] feat: create buffer type for owned and non owned audio slices --- src/contrib/fundsp.rs | 10 +- src/dsp/buffer.rs | 248 ++++++++++++++++++++++++++++++++++++++ src/dsp/mod.rs | 79 +++++++----- src/dsp/parameter.rs | 1 - src/dsp/utils.rs | 24 ++-- src/filters/biquad.rs | 11 +- src/filters/ladder.rs | 22 ++-- src/filters/statespace.rs | 19 ++- src/fir.rs | 14 +-- src/oscillators/blit.rs | 31 +++-- src/oversample/mod.rs | 43 ++++--- 11 files changed, 376 insertions(+), 126 deletions(-) create mode 100644 src/dsp/buffer.rs diff --git a/src/contrib/fundsp.rs b/src/contrib/fundsp.rs index 33bc9e5..81825d1 100644 --- a/src/contrib/fundsp.rs +++ b/src/contrib/fundsp.rs @@ -60,7 +60,7 @@ where #[cfg(test)] mod tests { - use crate::dsp::{utils::slice_to_mono_block_mut, DSPBlock}; + use crate::dsp::{buffer::AudioBufferBox, DSPBlock}; use crate::dsp::blocks::Integrator; use fundsp::hacker32::*; @@ -70,11 +70,11 @@ mod tests { #[test] fn test_wrapper() { let mut dsp = sine_hz(440.0) * sine_hz(10.0); - let input = [[]; 512]; - let mut output = [0.0; 512]; - dsp.process_block(&input, slice_to_mono_block_mut(&mut output)); + let input = AudioBufferBox::zeroed(512); + let mut output = AudioBufferBox::zeroed(512); + dsp.process_block(input.as_ref(), output.as_mut()); - insta::assert_csv_snapshot!(&output as &[_], { "[]" => insta::rounded_redaction(3) }) + insta::assert_csv_snapshot!(output.get_channel(0), { "[]" => insta::rounded_redaction(3) }) } #[test] diff --git a/src/dsp/buffer.rs b/src/dsp/buffer.rs new file mode 100644 index 0000000..5e0f448 --- /dev/null +++ b/src/dsp/buffer.rs @@ -0,0 +1,248 @@ +use num_traits::Zero; +use std::ops::{Deref, DerefMut, Index, IndexMut}; + +use crate::Scalar; + +/// AudioBuffer abstraction over containers of contiguous slices. This supports owned and non-owned, +/// immutable and mutable slices. +#[derive(Debug, Clone)] +pub struct AudioBuffer { + containers: [C; CHANNELS], + inner_size: usize, +} + +impl Default for AudioBuffer { + fn default() -> Self { + Self { + containers: [], + inner_size: 0, + } + } +} + +impl Index for AudioBuffer { + type Output = C; + + fn index(&self, index: usize) -> &Self::Output { + &self.containers[index] + } +} + +impl IndexMut for AudioBuffer { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.containers[index] + } +} + +impl AudioBuffer { + /// Returns a reference to the slice associated with the given channel. + /// + /// # Arguments + /// + /// * `ch`: Channel index + pub const fn get_channel(&self, ch: usize) -> &C { + &self.containers[ch] + } + + /// Returns a mutable reference to the slice associated with the given channel. + /// + /// # Arguments + /// + /// * `ch`: Channel index + pub fn get_channel_mut(&mut self, ch: usize) -> &mut C { + assert!(ch < CHANNELS); + &mut self.containers[ch] + } + + /// Number of samples contained in this buffer. + pub const fn samples(&self) -> usize { + self.inner_size + } +} + +impl, const CHANNELS: usize> AudioBuffer { + /// Create an audio buffer supported by the given containers of audio slices + pub fn new(containers: [C; CHANNELS]) -> Option { + if CHANNELS == 0 { + return Some(Self { + containers, + inner_size: 0, + }); + } + + let len = containers[0].len(); + if CHANNELS == 1 { + Some(Self { + containers, + inner_size: len, + }) + } else { + containers[1..] + .iter() + .all(|i| i.len() == len) + .then_some(Self { + containers, + inner_size: len, + }) + } + } + + pub fn frame_ref(&self, index: usize) -> [&T; CHANNELS] { + std::array::from_fn(|ch| &self.containers[ch][index]) + } + + /// Get a multi-channel sample at the given index + pub fn get_frame(&self, index: usize) -> [T; CHANNELS] + where + T: Clone, + { + std::array::from_fn(|ch| self.containers[ch][index].clone()) + } + + pub fn iter(&self) -> impl '_ + Iterator { + (0..self.inner_size).map(|i| self.frame_ref(i)) + } + + /// Return a non-owning buffer that refers to the content of this audio buffer. + pub fn as_ref(&self) -> AudioBufferRef { + AudioBuffer { + containers: std::array::from_fn(|i| self.containers[i].deref()), + inner_size: self.inner_size, + } + } +} + +impl, const CHANNELS: usize> AudioBuffer { + /// Return a non-owning mutable buffer that refers to the content of this audio buffer. + pub fn as_mut(&mut self) -> AudioBufferMut { + // We need to use `MaybeUninit` here to be able to split the incoming mutable reference on self into references + // on the individual channels' slices. The borrow checker cannot see through our intent, and so we need to drop + // into unsafe code in order to manually create the mutable references ourselves. + // Note that the only unsafe operation is `MaybeUninit::assume_init` here, as the reference split operation is + // already a safe method on slices; we only need `MaybeUninit` to create a static array of the correct size for + // usage in the `AudioBufferMut` we want to create. + let containers = { + use std::mem::MaybeUninit; + + let mut containers = std::array::from_fn(|_| MaybeUninit::uninit()); + let mut data = &mut self.containers as &mut [C]; + let mut i = 0; + while let Some((head, rest)) = data.split_first_mut() { + containers[i].write(head.deref_mut()); + data = rest; + i += 1; + } + containers.map(|mu| { + // # Safety + // + // This is safe as long as this `MaybeUninit` has successfully been written with a valid, unique (non- + // overlapping) mutable slice reference, which is guaranteed to be unique by the `split_first_mut` + // method used above. + // This block only executes when all references have been successfully created. + unsafe { mu.assume_init() } + }) + }; + AudioBuffer { + containers, + inner_size: self.inner_size, + } + } + + pub fn fill(&mut self, value: T) + where + T: Copy, + { + for container in &mut self.containers { + container.fill(value) + } + } + + pub fn fill_with(&mut self, mut fill: impl FnMut() -> T) { + for container in &mut self.containers { + container.fill_with(&mut fill); + } + } +} + +impl, const CHANNELS: usize> AudioBuffer { + /// Copy a slice into a specific channel of this audio buffer. + pub fn copy_from_slice(&mut self, ch: usize, slice: &[T]) { + self.containers[ch].copy_from_slice(slice); + } + + /// Set a multi-channel sample at the given index + pub fn set_frame(&mut self, index: usize, frame: [T; CHANNELS]) { + for (channel, sample) in self.containers.iter_mut().zip(frame.iter().copied()) { + channel[index] = sample; + } + } +} + +impl, const CHANNELS: usize> AudioBuffer { + /// Mix another buffer into this audio buffer, at the specified per-channel gain. + pub fn mix>( + &mut self, + other: &AudioBuffer, + mix: [T; CHANNELS], + ) { + assert_eq!(self.inner_size, other.inner_size); + for ((this_channel, other_channel), mix) in self + .containers + .iter_mut() + .zip(other.containers.iter()) + .zip(mix.iter().copied()) + { + for i in 0..self.inner_size { + this_channel[i] += other_channel[i] * mix; + } + } + } +} + +pub type AudioBufferRef<'a, T, const CHANNELS: usize> = AudioBuffer<&'a [T], CHANNELS>; + +impl<'a, T> From<&'a [T]> for AudioBufferRef<'a, T, 1> { + fn from(value: &'a [T]) -> Self { + let inner_size = value.len(); + Self { + containers: [value], + inner_size, + } + } +} + +pub type AudioBufferMut<'a, T, const CHANNELS: usize> = AudioBuffer<&'a mut [T], CHANNELS>; + +impl<'a, T> From<&'a mut [T]> for AudioBufferMut<'a, T, 1> { + fn from(value: &'a mut [T]) -> Self { + let inner_size = value.len(); + Self { + containers: [value], + inner_size, + } + } +} + +pub type AudioBufferBox = AudioBuffer, CHANNELS>; + +impl FromIterator for AudioBufferBox { + fn from_iter>(iter: It) -> Self { + let slice: Box<[T]> = iter.into_iter().collect(); + let inner_size = slice.len(); + Self { + containers: [slice], + inner_size, + } + } +} + +impl AudioBufferBox { + pub fn zeroed(size: usize) -> Self { + Self { + containers: std::array::from_fn(|_| { + std::iter::repeat_with(T::zero).take(size).collect() + }), + inner_size: size, + } + } +} diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index 49f923c..a03ee2d 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -1,4 +1,6 @@ +use crate::dsp::buffer::{AudioBufferBox, AudioBufferMut, AudioBufferRef}; use nalgebra::Complex; +use num_traits::Zero; use crate::Scalar; @@ -6,6 +8,7 @@ use self::analysis::DspAnalysis; pub mod analysis; pub mod blocks; +pub mod buffer; pub mod parameter; pub mod utils; @@ -53,7 +56,11 @@ pub trait DSPBlock { /// and as such, a simultaneous implementation of both onto one struct is impossible. /// /// Implementors should assume inputs and outputs are of the same length, as it is the caller's responsibility to make sure of that. - fn process_block(&mut self, inputs: &[[Self::Sample; I]], outputs: &mut [[Self::Sample; O]]); + fn process_block( + &mut self, + inputs: AudioBufferRef, + outputs: AudioBufferMut, + ); /// Sets the processing samplerate for this [`DSP`] instance. fn set_samplerate(&mut self, samplerate: f32) {} @@ -85,10 +92,16 @@ where type Sample = >::Sample; #[inline(never)] - fn process_block(&mut self, inputs: &[[Self::Sample; I]], outputs: &mut [[Self::Sample; O]]) { - let len = inputs.len(); - for i in 0..len { - outputs[i] = self.process(inputs[i]); + fn process_block( + &mut self, + inputs: AudioBufferRef, + mut outputs: AudioBufferMut, + ) { + if I == 0 || O == 0 { + return; + } + for i in 0..inputs.samples() { + outputs.set_frame(i, self.process(inputs.get_frame(i))) } } @@ -116,11 +129,12 @@ pub struct PerSampleBlockAdapter where P: DSPBlock, { - input_buffer: Box<[[P::Sample; I]]>, + input_buffer: AudioBufferBox, input_filled: usize, - output_buffer: Box<[[P::Sample; O]]>, + output_buffer: AudioBufferBox, output_filled: usize, inner: P, + pub buffer_size: usize, } impl std::ops::Deref for PerSampleBlockAdapter @@ -158,10 +172,11 @@ where .map(|mbs| mbs.min(max_buffer_size)) .unwrap_or(max_buffer_size); Self { - input_buffer: vec![[P::Sample::from_f64(0.0); I]; buffer_size].into_boxed_slice(), + input_buffer: AudioBufferBox::zeroed(buffer_size), input_filled: 0, - output_buffer: vec![[P::Sample::from_f64(0.0); O]; buffer_size].into_boxed_slice(), + output_buffer: AudioBufferBox::zeroed(buffer_size), output_filled: buffer_size, + buffer_size, inner: dsp_block, } } @@ -178,32 +193,32 @@ where type Sample = P::Sample; fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { - self.input_buffer[self.input_filled] = x; + self.input_buffer.set_frame(self.input_filled, x); self.input_filled += 1; - if self.input_buffer.len() == self.input_filled { + if self.input_buffer.samples() == self.input_filled { self.inner - .process_block(&self.input_buffer, &mut self.output_buffer); + .process_block(self.input_buffer.as_ref(), self.output_buffer.as_mut()); self.input_filled = 0; self.output_filled = 0; } - if self.output_filled < self.output_buffer.len() { - let ret = self.output_buffer[self.output_filled]; + if self.output_filled < self.buffer_size { + let ret = self.output_buffer.get_frame(self.output_filled); self.output_filled += 1; ret } else { - [Self::Sample::from_f64(0.0); O] + [Self::Sample::zero(); O] } } fn latency(&self) -> usize { - (self.inner.latency() + self.input_buffer.len()).saturating_sub(1) + (self.inner.latency() + self.input_buffer.samples()).saturating_sub(1) } fn reset(&mut self) { self.inner.reset(); self.input_filled = 0; - self.output_filled = self.output_buffer.len(); + self.output_filled = self.output_buffer.samples(); } } @@ -224,40 +239,42 @@ mod tests { #[test] fn test_per_sample_block_adapter() { - struct Echo(PhantomData); + struct Counter(PhantomData); - impl Echo { + impl Counter { pub fn new() -> Self { Self(PhantomData) } } - impl DSPBlock<0, 1> for Echo { + impl DSPBlock<0, 1> for Counter { type Sample = T; fn process_block( &mut self, - inputs: &[[Self::Sample; 0]], - outputs: &mut [[Self::Sample; 1]], + inputs: AudioBufferRef, + mut outputs: AudioBufferMut, ) { - let len = inputs.len(); - assert_eq!(len, outputs.len()); + let len = inputs.samples(); + assert_eq!(len, outputs.samples()); - for (i, [out]) in outputs.iter_mut().enumerate() { - *out = T::from_f64((i + 1) as _); + for (i, out) in outputs.get_channel_mut(0).iter_mut().enumerate() { + *out = T::from_f64(i as _); } } } - let mut adaptor = PerSampleBlockAdapter::new_with_max_buffer_size(Echo::::new(), 4); + let mut adaptor = PerSampleBlockAdapter::new_with_max_buffer_size(Counter::::new(), 4); assert_eq!(3, DSP::latency(&adaptor)); - let expected = [0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 4.0, 1.0].map(|v| [v]); - let mut actual = [[0.0]; 8]; - let input = [[]; 8]; + let expected = [0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 0.0]; + let mut actual = [0.0; 8]; // Calling `process_block` but it's actually calling the impl for all `DSP` passing each sample through. - adaptor.process_block(&input, &mut actual); + adaptor.process_block( + AudioBufferRef::new([]).unwrap(), + AudioBufferMut::new([&mut actual]).unwrap(), + ); assert_eq!(expected, actual); } diff --git a/src/dsp/parameter.rs b/src/dsp/parameter.rs index 20075ca..4cb5fe1 100644 --- a/src/dsp/parameter.rs +++ b/src/dsp/parameter.rs @@ -187,7 +187,6 @@ impl SmoothedParam { P1::new(samplerate, tau.recip()), ModMatrix { weights: SMatrix::<_, 1, 3>::new(1.0, 0.0, 0.0), - ..ModMatrix::default() }, )), } diff --git a/src/dsp/utils.rs b/src/dsp/utils.rs index 5f513fa..66258f8 100644 --- a/src/dsp/utils.rs +++ b/src/dsp/utils.rs @@ -1,31 +1,23 @@ /// Transmutes a mono block slice into a regular slice. -/// Safety: safe because [T; 1] has the same representation as T, so in turn -/// any access of &[T; 1] is also a valid access of &T. #[inline(always)] -pub fn mono_block_to_slice(inputs: &[[T; 1]]) -> &[T] { - unsafe { std::mem::transmute(inputs) } +pub fn mono_block_to_slice(inputs: [&[T]; 1]) -> &[T] { + inputs[0] } /// Transmutes a mutable mono block slice into a regular slice. -/// Safety: safe because [T; 1] has the same representation as T, so in turn -/// any access of &mut [T; 1] is also a valid access of &mut T. #[inline(always)] -pub fn mono_block_to_slice_mut(inputs: &mut [[T; 1]]) -> &mut [T] { - unsafe { std::mem::transmute(inputs) } +pub fn mono_block_to_slice_mut(inputs: [&mut [T]; 1]) -> &mut [T] { + inputs[0] } /// Transmutes a slice into a mono block slice. -/// Safety: safe because T has the same representation as [T; 1], so in turn -/// any access of &T is also a valid access of &[T; 1]. #[inline(always)] -pub fn slice_to_mono_block(inputs: &[T]) -> &[[T; 1]] { - unsafe { std::mem::transmute(inputs) } +pub fn slice_to_mono_block(inputs: &[T]) -> [&[T]; 1] { + [inputs] } /// Transmutes a mutable slice into a mutable mono block slice. -/// Safety: safe because T has the same representation as [T; 1], so in turn -/// any access of &mut T is also a valid access of &mut [T; 1]. #[inline(always)] -pub fn slice_to_mono_block_mut(inputs: &mut [T]) -> &mut [[T; 1]] { - unsafe { std::mem::transmute(inputs) } +pub fn slice_to_mono_block_mut(inputs: &mut [T]) -> [&mut [T]; 1] { + [inputs] } diff --git a/src/filters/biquad.rs b/src/filters/biquad.rs index 4b5960b..cc53ff2 100644 --- a/src/filters/biquad.rs +++ b/src/filters/biquad.rs @@ -240,7 +240,7 @@ where mod tests { use crate::{ dsp::{ - utils::{slice_to_mono_block, slice_to_mono_block_mut}, + buffer::{AudioBufferBox, AudioBufferRef}, DSPBlock, }, saturators::clippers::DiodeClipperModel, @@ -257,12 +257,9 @@ mod tests { let input: [_; 512] = std::array::from_fn(|i| i as f64 / samplerate).map(|t| (10.0 * t).fract() * 2.0 - 1.0); - let mut output = [0.0; 512]; - biquad.process_block( - slice_to_mono_block(&input), - slice_to_mono_block_mut(&mut output), - ); + let mut output = AudioBufferBox::zeroed(512); + biquad.process_block(AudioBufferRef::from(&input as &[_]), output.as_mut()); - insta::assert_csv_snapshot!(&output as &[_], { "[]" => insta::rounded_redaction(4) }); + insta::assert_csv_snapshot!(output.get_channel(0), { "[]" => insta::rounded_redaction(4) }); } } diff --git a/src/filters/ladder.rs b/src/filters/ladder.rs index a5a651a..4bd2ac6 100644 --- a/src/filters/ladder.rs +++ b/src/filters/ladder.rs @@ -223,14 +223,10 @@ impl> DspAnalysis<1, 1> for Ladder { #[cfg(test)] mod tests { - use num_traits::real::Real; use rstest::rstest; use simba::simd::SimdComplexField; - use crate::dsp::{ - utils::{slice_to_mono_block, slice_to_mono_block_mut}, - DSPBlock, - }; + use crate::dsp::{buffer::AudioBuffer, DSPBlock}; use crate::saturators::clippers::DiodeClipperModel; use super::*; @@ -245,19 +241,19 @@ mod tests { let mut filter = Ladder::::new(1024.0, 200.0, resonance).with_topology::(topology); filter.compensated = compensated; - let mut input = [1.0; 1024]; - let mut output = [0.0; 1024]; - input[0] = 0.0; - filter.process_block( - slice_to_mono_block(&input), - slice_to_mono_block_mut(&mut output), - ); + let input = AudioBuffer::new([std::iter::once(0.0) + .chain(std::iter::repeat(1.0)) + .take(1024) + .collect::>()]) + .unwrap(); + let mut output = AudioBuffer::zeroed(1024); + filter.process_block(input.as_ref(), output.as_mut()); let topo = std::any::type_name::() .replace("::", "__") .replace(['<', '>'], "_"); let name = format!("test_ladder_ir_{topo}_c{compensated}_r{resonance}"); - insta::assert_csv_snapshot!(name, &output as &[_], { "[]" => insta::rounded_redaction(3) }) + insta::assert_csv_snapshot!(name, output.get_channel(0), { "[]" => insta::rounded_redaction(3) }) } #[rstest] diff --git a/src/filters/statespace.rs b/src/filters/statespace.rs index dd1f691..b3be591 100644 --- a/src/filters/statespace.rs +++ b/src/filters/statespace.rs @@ -102,10 +102,8 @@ mod tests { use nalgebra::ComplexField; use numeric_literals::replace_float_literals; - use crate::dsp::{ - utils::{slice_to_mono_block, slice_to_mono_block_mut}, - DSPBlock, - }; + use crate::dsp::buffer::AudioBuffer; + use crate::dsp::DSPBlock; use super::*; @@ -136,14 +134,11 @@ mod tests { #[test] fn test_rc_filter() { let mut filter = RC::new(0.25); - let mut input = [0.0; 1024]; - let mut output = [0.0; 1024]; - input[0] = 1.0; - filter.process_block( - slice_to_mono_block(&input), - slice_to_mono_block_mut(&mut output), - ); - insta::assert_csv_snapshot!(&output as &[_], { "[]" => insta::rounded_redaction(3) }); + let mut input = AudioBuffer::zeroed(1024); + let mut output = input.clone(); + input[0][0] = 1.0; + filter.process_block(input.as_ref(), output.as_mut()); + insta::assert_csv_snapshot!(output.get_channel(0), { "[]" => insta::rounded_redaction(3) }); } #[test] diff --git a/src/fir.rs b/src/fir.rs index 2b79743..e48cfa8 100644 --- a/src/fir.rs +++ b/src/fir.rs @@ -171,25 +171,21 @@ pub mod kernels { #[cfg(test)] mod tests { - use crate::dsp::{ - utils::{slice_to_mono_block, slice_to_mono_block_mut}, - DSPBlock, - }; + use crate::dsp::buffer::AudioBuffer; + use crate::dsp::DSPBlock; use super::*; #[test] fn test_fir_direct() { let input = Box::from_iter([1.0, 0.0, 0.0, 0.0].into_iter().cycle().take(16)); + let input = AudioBuffer::new([input]).unwrap(); let mut output = input.clone(); let mut fir = Fir::new([0.25, 0.5, 0.25], 1); output.fill(0.0); - fir.process_block( - slice_to_mono_block(&input), - slice_to_mono_block_mut(&mut output), - ); - insta::assert_csv_snapshot!(&output, { "[]" => insta::rounded_redaction(4) }) + fir.process_block(input.as_ref(), output.as_mut()); + insta::assert_csv_snapshot!(output.get_channel(0), { "[]" => insta::rounded_redaction(4) }) } #[test] diff --git a/src/oscillators/blit.rs b/src/oscillators/blit.rs index 1b291b6..00d9a76 100644 --- a/src/oscillators/blit.rs +++ b/src/oscillators/blit.rs @@ -171,7 +171,8 @@ impl Square { #[cfg(test)] mod tests { - use crate::dsp::{utils::slice_to_mono_block_mut, DSPBlock}; + use crate::dsp::buffer::AudioBuffer; + use crate::dsp::DSPBlock; use super::*; @@ -179,35 +180,39 @@ mod tests { fn test_blit() { let mut blit = Blit::new(8192.0, 10.0); insta::assert_debug_snapshot!(&blit); - let mut actual = [0.0; 8192]; - blit.process_block(&[[]; 8192], slice_to_mono_block_mut(&mut actual)); - insta::assert_csv_snapshot!(&actual as &[_], { "[]" => insta::rounded_redaction(4) }); + let input = AudioBuffer::zeroed(8192); + let mut actual = AudioBuffer::zeroed(8192); + blit.process_block(input.as_ref(), actual.as_mut()); + insta::assert_csv_snapshot!(actual.get_channel(0), { "[]" => insta::rounded_redaction(4) }); } #[test] fn test_sawtooth() { let mut saw = Sawtooth::new(8192.0, 10.0); insta::assert_debug_snapshot!(&saw); - let mut actual = [0.0; 8192]; - saw.process_block(&[[]; 8192], slice_to_mono_block_mut(&mut actual)); - insta::assert_csv_snapshot!(&actual as &[_], { "[]" => insta::rounded_redaction(4) }); + let input = AudioBuffer::zeroed(8192); + let mut actual = AudioBuffer::zeroed(8192); + saw.process_block(input.as_ref(), actual.as_mut()); + insta::assert_csv_snapshot!(actual.get_channel(0), { "[]" => insta::rounded_redaction(4) }); } #[test] fn test_square() { let mut square = Square::new(8192.0, 10.0, 0.5); insta::assert_debug_snapshot!(&square); - let mut actual = [0.0; 8192]; - square.process_block(&[[]; 8192], slice_to_mono_block_mut(&mut actual)); - insta::assert_csv_snapshot!(&actual as &[_], { "[]" => insta::rounded_redaction(4) }); + let input = AudioBuffer::zeroed(8192); + let mut actual = AudioBuffer::zeroed(8192); + square.process_block(input.as_ref(), actual.as_mut()); + insta::assert_csv_snapshot!(actual.get_channel(0), { "[]" => insta::rounded_redaction(4) }); } #[test] fn test_square_pw() { let mut square = Square::new(8192.0, 10.0, 0.1); insta::assert_debug_snapshot!(&square); - let mut actual = [0.0; 8192]; - square.process_block(&[[]; 8192], slice_to_mono_block_mut(&mut actual)); - insta::assert_csv_snapshot!(&actual as &[_], { "[]" => insta::rounded_redaction(4) }); + let input = AudioBuffer::zeroed(8192); + let mut actual = AudioBuffer::zeroed(8192); + square.process_block(input.as_ref(), actual.as_mut()); + insta::assert_csv_snapshot!(actual.get_channel(0), { "[]" => insta::rounded_redaction(4) }); } } diff --git a/src/oversample/mod.rs b/src/oversample/mod.rs index b156704..36371b1 100644 --- a/src/oversample/mod.rs +++ b/src/oversample/mod.rs @@ -1,9 +1,9 @@ use std::ops::{Deref, DerefMut}; -use crate::dsp::parameter::{HasParameters, Parameter}; +use crate::dsp::DSP; use crate::dsp::{ - utils::{mono_block_to_slice, mono_block_to_slice_mut, slice_to_mono_block_mut}, - DSP, + buffer::{AudioBufferMut, AudioBufferRef}, + parameter::{HasParameters, Parameter}, }; use crate::saturators::Linear; use crate::Scalar; @@ -67,7 +67,7 @@ impl Oversample { let max_block_size = dsp.max_block_size().unwrap_or(self.os_buffer.len()); // Verify that we satisfy the inner DSPBlock instance's requirement on maximum block size assert!(self.os_buffer.len() <= max_block_size); - let staging_buffer = vec![[T::zero(); 1]; max_block_size].into_boxed_slice(); + let staging_buffer = vec![T::zero(); max_block_size].into_boxed_slice(); Oversampled { oversampling: self, staging_buffer, @@ -134,7 +134,7 @@ impl<'a, T: Scalar> OversampleBlock<'a, T> { pub struct Oversampled { oversampling: Oversample, - staging_buffer: Box<[[T; 1]]>, + staging_buffer: Box<[T]>, pub inner: P, } @@ -180,18 +180,20 @@ where self.inner.reset(); } - fn process_block(&mut self, inputs: &[[Self::Sample; 1]], outputs: &mut [[Self::Sample; 1]]) { - let inputs = mono_block_to_slice(inputs); - let mut os_block = self.oversampling.oversample(inputs); - let inner_outputs = slice_to_mono_block_mut(&mut os_block); - self.staging_buffer[..inner_outputs.len()].copy_from_slice(inner_outputs); - self.inner - .process_block(&self.staging_buffer, inner_outputs); - os_block.finish(mono_block_to_slice_mut(outputs)); + fn process_block(&mut self, inputs: AudioBufferRef, mut outputs: AudioBufferMut) { + let mut os_block = self.oversampling.oversample(inputs.get_channel(0)); + let input = &mut self.staging_buffer[..os_block.len()]; + input.copy_from_slice(&os_block); + self.inner.process_block( + AudioBufferRef::from(&*input), + AudioBufferMut::from(&mut *os_block), + ); + os_block.finish(outputs.get_channel_mut(0)); } fn set_samplerate(&mut self, samplerate: f32) { - self.inner.set_samplerate(self.oversampling.os_factor as f32 * samplerate); + self.inner + .set_samplerate(self.oversampling.os_factor as f32 * samplerate); } } @@ -209,7 +211,10 @@ mod tests { use numeric_literals::replace_float_literals; - use crate::{dsp::DSPBlock as _, Scalar}; + use crate::{ + dsp::{buffer::AudioBufferBox, DSPBlock as _}, + Scalar, + }; use super::Oversample; @@ -256,9 +261,9 @@ mod tests { }; let mut os = Oversample::::new(4, 64).with_dsp(dsp); - let input = [[0.0]; 64]; - let mut output = [[0.0]; 64]; - os.process_block(&input, &mut output); - insta::assert_csv_snapshot!(&output as &[_], { "[][]" => insta::rounded_redaction(3) }); + let input = AudioBufferBox::zeroed(64); + let mut output = AudioBufferBox::zeroed(64); + os.process_block(input.as_ref(), output.as_mut()); + insta::assert_csv_snapshot!(output.get_channel(0), { "[][]" => insta::rounded_redaction(3) }); } } From cd620f48d57e9f9582a8b720eeb166a4bb24510d Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Mon, 26 Feb 2024 20:24:36 +0100 Subject: [PATCH 02/12] fix: change usages of process_block in examples and plugins --- examples/svfmixer/src/dsp.rs | 1 - examples/svfmixer/src/lib.rs | 12 +++++++++--- plugins/abrasive/src/lib.rs | 25 ++++++++++++------------- 3 files changed, 21 insertions(+), 17 deletions(-) diff --git a/examples/svfmixer/src/dsp.rs b/examples/svfmixer/src/dsp.rs index 85ab786..eee679e 100644 --- a/examples/svfmixer/src/dsp.rs +++ b/examples/svfmixer/src/dsp.rs @@ -62,7 +62,6 @@ impl DspInner { Sample::splat(0.0), Sample::splat(0.0), ), - ..ModMatrix::default() }; Self { params, diff --git a/examples/svfmixer/src/lib.rs b/examples/svfmixer/src/lib.rs index 3030ab9..a90f8ef 100644 --- a/examples/svfmixer/src/lib.rs +++ b/examples/svfmixer/src/lib.rs @@ -3,9 +3,12 @@ use std::sync::Arc; use nih_plug::prelude::*; use extend::FloatParamExt; -use valib::dsp::parameter::HasParameters; -use valib::dsp::utils::{slice_to_mono_block, slice_to_mono_block_mut}; + use valib::dsp::DSPBlock; +use valib::dsp::{ + buffer::{AudioBufferMut, AudioBufferRef}, + parameter::HasParameters, +}; use valib::oversample::Oversample; use valib::Scalar; @@ -216,7 +219,10 @@ fn process_buffer_simd< let input = &input[..block.samples()]; let output = &mut output[..block.samples()]; - dsp.process_block(slice_to_mono_block(input), slice_to_mono_block_mut(output)); + dsp.process_block( + AudioBufferRef::from(input), + AudioBufferMut::from(&mut *output), + ); for (i, mut c) in block.iter_samples().enumerate() { for (ch, s) in c.iter_mut().enumerate() { diff --git a/plugins/abrasive/src/lib.rs b/plugins/abrasive/src/lib.rs index 79c95b6..4621d28 100644 --- a/plugins/abrasive/src/lib.rs +++ b/plugins/abrasive/src/lib.rs @@ -11,10 +11,10 @@ use atomic_float::AtomicF32; use nih_plug::{params::persist::PersistentField, prelude::*}; use std::sync::{Arc, Mutex}; use valib::dsp::blocks::Series; -use valib::dsp::utils::{slice_to_mono_block, slice_to_mono_block_mut}; +use valib::dsp::buffer::AudioBuffer; + use valib::dsp::DSPBlock; use valib::simd::{AutoF32x2, SimdValue}; -use valib::Scalar; pub mod editor; mod filter; @@ -187,6 +187,7 @@ impl Abrasive { fn set_filterbank_samplerate(&mut self, _sr: f32) { for filter in self.filters.0.iter_mut() { filter.reset(); + filter.set_samplerate(_sr); } } @@ -205,24 +206,22 @@ impl Abrasive { .scale .smoothed .next_block_exact(&mut scale[..len]); - let mut simd_input = [Sample::from_f64(0.0); BLOCK_SIZE]; - let mut simd_output = simd_input; + let mut simd_input = AudioBuffer::zeroed(BLOCK_SIZE); + let mut simd_output = simd_input.clone(); for (i, mut samples) in block.iter_samples().enumerate() { - simd_input[i] = + simd_input[0][i] = Sample::from([*samples.get_mut(0).unwrap(), *samples.get_mut(1).unwrap()]); - simd_input[i] *= Sample::splat(drive[i]); + simd_input[0][i] *= Sample::splat(drive[i]); } - self.filters.process_block( - slice_to_mono_block(&simd_input[..len]), - slice_to_mono_block_mut(&mut simd_output[..len]), - ); + self.filters + .process_block(simd_input.as_ref(), simd_output.as_mut()); for (i, mut samples) in block.iter_samples().enumerate() { let drive = Sample::splat(drive[i]); - simd_output[i] /= drive; - *samples.get_mut(0).unwrap() = simd_output[i].extract(0); - *samples.get_mut(1).unwrap() = simd_output[i].extract(1); + simd_output[0][i] /= drive; + *samples.get_mut(0).unwrap() = simd_output[0][i].extract(0); + *samples.get_mut(1).unwrap() = simd_output[0][i].extract(1); } } } From 93183d10f4e2abc4b7bf00db11b86f54c34395c7 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Tue, 27 Feb 2024 08:33:49 +0100 Subject: [PATCH 03/12] chore(ide): code style changes --- .idea/codeStyles/codeStyleConfig.xml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .idea/codeStyles/codeStyleConfig.xml diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file From 622af0057252292e5efbbb5825d79fdfd5ef62d5 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Tue, 27 Feb 2024 08:34:24 +0100 Subject: [PATCH 04/12] test: fix tests that use process_block wrongly --- src/dsp/buffer.rs | 27 +++++++++++++++++++++++++++ src/dsp/mod.rs | 4 ++-- src/oversample/mod.rs | 2 +- 3 files changed, 30 insertions(+), 3 deletions(-) diff --git a/src/dsp/buffer.rs b/src/dsp/buffer.rs index 5e0f448..d9ec373 100644 --- a/src/dsp/buffer.rs +++ b/src/dsp/buffer.rs @@ -211,6 +211,19 @@ impl<'a, T> From<&'a [T]> for AudioBufferRef<'a, T, 1> { } } +impl AudioBuffer { + /// Creates a 0-channel empty buffer with the specified buffer size. This constructor is required to provide a non- + /// zero block size that matches the companion buffer passed into `process_block`. + /// + /// Better API design is needed to remove this need. + pub fn empty(block_size: usize) -> Self { + Self { + containers: [], + inner_size: block_size, + } + } +} + pub type AudioBufferMut<'a, T, const CHANNELS: usize> = AudioBuffer<&'a mut [T], CHANNELS>; impl<'a, T> From<&'a mut [T]> for AudioBufferMut<'a, T, 1> { @@ -246,3 +259,17 @@ impl AudioBufferBox { } } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_as_mut_write() { + let mut buffer = AudioBuffer::<_, 1>::zeroed(1); + let mut slice_mut = buffer.as_mut(); + slice_mut[0][0] = 1; + + assert_eq!(1, buffer[0][0]); + } +} diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index a03ee2d..8cf11ea 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -97,7 +97,7 @@ where inputs: AudioBufferRef, mut outputs: AudioBufferMut, ) { - if I == 0 || O == 0 { + if I == 0 && O == 0 { return; } for i in 0..inputs.samples() { @@ -272,7 +272,7 @@ mod tests { // Calling `process_block` but it's actually calling the impl for all `DSP` passing each sample through. adaptor.process_block( - AudioBufferRef::new([]).unwrap(), + AudioBufferRef::empty(8), AudioBufferMut::new([&mut actual]).unwrap(), ); diff --git a/src/oversample/mod.rs b/src/oversample/mod.rs index 36371b1..58de025 100644 --- a/src/oversample/mod.rs +++ b/src/oversample/mod.rs @@ -264,6 +264,6 @@ mod tests { let input = AudioBufferBox::zeroed(64); let mut output = AudioBufferBox::zeroed(64); os.process_block(input.as_ref(), output.as_mut()); - insta::assert_csv_snapshot!(output.get_channel(0), { "[][]" => insta::rounded_redaction(3) }); + insta::assert_csv_snapshot!(output.get_channel(0), { "[]" => insta::rounded_redaction(3) }); } } From 4617b0b6929b9b6ffe2e70d81edcd1221d1d1cf6 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Thu, 29 Feb 2024 12:02:57 +0100 Subject: [PATCH 05/12] chore: ignore DS store files --- .gitignore | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a76f112..1bcd73d 100644 --- a/.gitignore +++ b/.gitignore @@ -12,6 +12,9 @@ # Built Visual Studio Code Extensions *.vsix +### macOS +.DS_Store + ### JetBrains template # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 @@ -103,4 +106,4 @@ target/ # MSVC Windows builds of rustc generate these, which store debugging information *.pdb -*.tsv \ No newline at end of file +*.tsv From 4773b063b7decb709cfe0ef87324b18e8a57ac74 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Fri, 22 Mar 2024 21:47:03 +0100 Subject: [PATCH 06/12] feat(dsp): separate out DSPMeta from DSP/DSPBlock for deduplication BREAKING CHANGE --- examples/diodeclipper/src/lib.rs | 4 +- examples/dirty-biquad/src/lib.rs | 4 +- examples/ladder/src/lib.rs | 4 +- examples/slew/src/lib.rs | 2 +- examples/svfmixer/src/dsp.rs | 4 +- examples/svfmixer/src/lib.rs | 4 +- plugins/abrasive/src/filter.rs | 4 +- plugins/abrasive/src/lib.rs | 2 +- src/contrib/fundsp.rs | 24 +-- src/dsp/analysis.rs | 4 +- src/dsp/blocks.rs | 290 ++++++++++++++++++------------- src/dsp/mod.rs | 135 +++++++------- src/dsp/parameter.rs | 33 ++-- src/dsp/utils.rs | 2 +- src/filters/biquad.rs | 12 +- src/filters/ladder.rs | 35 ++-- src/filters/statespace.rs | 16 +- src/filters/svf.rs | 18 +- src/fir.rs | 9 +- src/oscillators/blit.rs | 40 +++-- src/oscillators/mod.rs | 7 +- src/oscillators/wavetable.rs | 10 +- src/oversample/mod.rs | 48 ++--- src/saturators/clippers/mod.rs | 30 ++-- src/saturators/mod.rs | 6 +- src/voice.rs | 32 ++-- 26 files changed, 436 insertions(+), 343 deletions(-) diff --git a/examples/diodeclipper/src/lib.rs b/examples/diodeclipper/src/lib.rs index 6fc5ea6..e50a74d 100644 --- a/examples/diodeclipper/src/lib.rs +++ b/examples/diodeclipper/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use nih_plug::{buffer::Block, prelude::*}; -use valib::dsp::DSP; +use valib::dsp::DSPProcess; use valib::math::interpolation::{Cubic, Interpolate}; use valib::oversample::Oversample; use valib::{ @@ -150,7 +150,7 @@ fn simd_array_to_block( } } -fn apply>, const N: usize>( +fn apply>, const N: usize>( buffer: &mut Buffer, dsp: &mut P, ) where diff --git a/examples/dirty-biquad/src/lib.rs b/examples/dirty-biquad/src/lib.rs index c0f4e3a..d24cb33 100644 --- a/examples/dirty-biquad/src/lib.rs +++ b/examples/dirty-biquad/src/lib.rs @@ -2,7 +2,7 @@ use std::sync::Arc; use nih_plug::prelude::*; -use valib::dsp::DSP; +use valib::dsp::DSPProcess; use valib::filters::biquad::Biquad; use valib::math::interpolation::{Cubic, Interpolate}; use valib::oversample::Oversample; @@ -175,7 +175,7 @@ fn simd_array_to_block( } #[cfg(never)] // Not used but nice to keep around -fn apply>, const N: usize>( +fn apply>, const N: usize>( buffer: &mut Buffer, dsp: &mut P, ) where diff --git a/examples/ladder/src/lib.rs b/examples/ladder/src/lib.rs index 8cb5b7f..3c32946 100644 --- a/examples/ladder/src/lib.rs +++ b/examples/ladder/src/lib.rs @@ -4,7 +4,7 @@ use nih_plug::prelude::*; use valib::math::interpolation::{Cubic, Interpolate}; use valib::oversample::Oversample; -use valib::{dsp::DSP, Scalar}; +use valib::{dsp::DSPProcess, Scalar}; use valib::{ filters::ladder::{Ideal, Ladder, Transistor, OTA}, saturators::{clippers::DiodeClipperModel, Tanh}, @@ -107,7 +107,7 @@ impl Dsp { } } -impl DSP<1, 1> for Dsp { +impl DSPProcess<1, 1> for Dsp { type Sample = Sample; fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { diff --git a/examples/slew/src/lib.rs b/examples/slew/src/lib.rs index 0639ed0..f01e45c 100644 --- a/examples/slew/src/lib.rs +++ b/examples/slew/src/lib.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use nih_plug::{prelude::*, util::db_to_gain}; use valib::{ - dsp::DSP, + dsp::DSPProcess, saturators::Slew, simd::{AutoF32x2, SimdValue}, }; diff --git a/examples/svfmixer/src/dsp.rs b/examples/svfmixer/src/dsp.rs index eee679e..73facd2 100644 --- a/examples/svfmixer/src/dsp.rs +++ b/examples/svfmixer/src/dsp.rs @@ -3,7 +3,7 @@ use nalgebra::SMatrix; use nih_plug::util::db_to_gain_fast; use valib::dsp::blocks::ModMatrix; use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam}; -use valib::dsp::DSP; +use valib::dsp::DSPProcess; use valib::filters::svf::Svf; use valib::oversample::Oversampled; use valib::saturators::{Clipper, Saturator, Slew}; @@ -79,7 +79,7 @@ impl HasParameters for DspInner { } } -impl DSP<1, 1> for DspInner { +impl DSPProcess<1, 1> for DspInner { type Sample = Sample; fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { diff --git a/examples/svfmixer/src/lib.rs b/examples/svfmixer/src/lib.rs index a90f8ef..74e6cce 100644 --- a/examples/svfmixer/src/lib.rs +++ b/examples/svfmixer/src/lib.rs @@ -4,7 +4,7 @@ use nih_plug::prelude::*; use extend::FloatParamExt; -use valib::dsp::DSPBlock; +use valib::dsp::DSPProcessBlock; use valib::dsp::{ buffer::{AudioBufferMut, AudioBufferRef}, parameter::HasParameters, @@ -202,7 +202,7 @@ impl nih_plug::prelude::Plugin for SvfMixerPlugin { fn process_buffer_simd< T: Scalar + From<[f32; 2]>, - Dsp: DSPBlock<1, 1, Sample = T>, + Dsp: DSPProcessBlock<1, 1, Sample = T>, const BUFSIZE: usize, >( dsp: &mut Dsp, diff --git a/plugins/abrasive/src/filter.rs b/plugins/abrasive/src/filter.rs index 07a1e84..fa52ef4 100644 --- a/plugins/abrasive/src/filter.rs +++ b/plugins/abrasive/src/filter.rs @@ -4,7 +4,7 @@ use nih_plug::prelude::*; use numeric_literals::replace_float_literals; use realfft::num_complex::Complex; -use valib::dsp::DSP; +use valib::dsp::DSPProcess; use valib::filters::svf::Svf; use valib::saturators::{Dynamic, Saturator}; use valib::simd::SimdValue; @@ -200,7 +200,7 @@ impl DspAnalysis<1, 1> for Filter { } } -impl DSP<1, 1> for Filter { +impl DSPProcess<1, 1> for Filter { type Sample = Sample; fn latency(&self) -> usize { diff --git a/plugins/abrasive/src/lib.rs b/plugins/abrasive/src/lib.rs index 4621d28..f7f5b0a 100644 --- a/plugins/abrasive/src/lib.rs +++ b/plugins/abrasive/src/lib.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex}; use valib::dsp::blocks::Series; use valib::dsp::buffer::AudioBuffer; -use valib::dsp::DSPBlock; +use valib::dsp::DSPProcessBlock; use valib::simd::{AutoF32x2, SimdValue}; pub mod editor; diff --git a/src/contrib/fundsp.rs b/src/contrib/fundsp.rs index 41a6abe..c53fec9 100644 --- a/src/contrib/fundsp.rs +++ b/src/contrib/fundsp.rs @@ -1,11 +1,11 @@ //! fundsp integration for statically-defined graphs. -//! +//! //! The integration provides impls for `An` objects, taking their defined input and output counts as the number of -//! input and output channels for the [`DSP`] implementation. -//! -//! Conversly, a [`DspNode`] struct is defined for wrapping [`DSP`] implementations into usable `fundsp` nodes. +//! input and output channels for the [`DSPProcess`] implementation. +//! +//! Conversly, a [`DspNode`] struct is defined for wrapping [`DSPProcess`] implementations into usable `fundsp` nodes. -use crate::dsp::DSP; +use crate::dsp::DSPProcess; use fundsp::audionode::{AudioNode, Frame}; use fundsp::combinator::An; use fundsp::Float; @@ -13,7 +13,7 @@ use numeric_array::ArrayLength; use std::marker::PhantomData; use typenum::Unsigned; -impl DSP<{ Node::Inputs::USIZE }, { Node::Outputs::USIZE }> for An +impl DSPProcess<{ Node::Inputs::USIZE }, { Node::Outputs::USIZE }> for An where Node::Sample: crate::Scalar, { @@ -29,14 +29,14 @@ where } } -/// Wrap a [`DSP`] impl as a `fundsp` node. -/// +/// Wrap a [`DSPProcess`] impl as a `fundsp` node. +/// /// This is the implementation struct; to us this node in `fundsp` graphs, refer to the [`dsp_node`] function. #[derive(Debug, Clone)] pub struct DspNode(PhantomData, PhantomData, P); -/// Wrap a [`DSP`] impl as a [`fundsp`] node. -pub fn dsp_node>( +/// Wrap a [`DSPProcess`] impl as a [`fundsp`] node. +pub fn dsp_node>( dsp: P, ) -> DspNode { DspNode(PhantomData, PhantomData, dsp) @@ -45,7 +45,7 @@ pub fn dsp_node, + P: Send + Sync + DSPProcess<{ In::USIZE }, { Out::USIZE }>, > AudioNode for DspNode where Self: Clone, @@ -71,7 +71,7 @@ where #[cfg(test)] mod tests { - use crate::dsp::{buffer::AudioBufferBox, DSPBlock}; + use crate::dsp::{buffer::AudioBufferBox, DSPProcessBlock}; use crate::dsp::blocks::Integrator; use fundsp::hacker32::*; diff --git a/src/dsp/analysis.rs b/src/dsp/analysis.rs index 0dc8ae9..7bfc22f 100644 --- a/src/dsp/analysis.rs +++ b/src/dsp/analysis.rs @@ -2,7 +2,7 @@ use nalgebra::Complex; use simba::simd::SimdComplexField; -use crate::{dsp::DSP, math::freq_to_z}; +use crate::{dsp::DSPProcess, math::freq_to_z}; /// Trait for DSP structs that have a z-domain transfer function available. /// For processes with nonlinear methods, the transfer function can still be defined by @@ -10,7 +10,7 @@ use crate::{dsp::DSP, math::freq_to_z}; /// /// The goal of this trait is to provide an easy way to compute frequency responses of /// filters for end-user visual feedback and not to be scientifically accurate. -pub trait DspAnalysis: DSP { +pub trait DspAnalysis: DSPProcess { /// Discrete transfer function in the z-domain. fn h_z(&self, z: Complex) -> [[Complex; O]; I]; diff --git a/src/dsp/blocks.rs b/src/dsp/blocks.rs index 0c39e4c..4bd0210 100644 --- a/src/dsp/blocks.rs +++ b/src/dsp/blocks.rs @@ -1,4 +1,4 @@ -//! Small [`DSP`] building blocks for reusability. +//! Small [`DSPProcess`] building blocks for reusability. use std::marker::PhantomData; use nalgebra::{Complex, ComplexField, SMatrix, SVector}; @@ -6,16 +6,18 @@ use num_traits::{One, Zero}; use numeric_literals::replace_float_literals; use crate::dsp::analysis::DspAnalysis; -use crate::dsp::DSP; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::Scalar; /// "Bypass" struct, which simply forwards the input to the output. #[derive(Debug, Copy, Clone, Default)] pub struct Bypass(PhantomData); -impl DSP for Bypass { - type Sample = S; +impl DSPMeta for Bypass { + type Sample = T; +} +impl DSPProcess for Bypass { fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { x } @@ -31,9 +33,19 @@ impl Default for Integrator { } } -impl DSP<1, 1> for Integrator { +impl DSPMeta for Integrator { type Sample = T; + fn latency(&self) -> usize { + 1 + } + + fn reset(&mut self) { + self.0 = T::zero(); + } +} + +impl DSPProcess<1, 1> for Integrator { #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { let x2 = x[0] / 2.0; @@ -54,12 +66,14 @@ impl DspAnalysis<1, 1> for Integrator { #[derive(Debug, Copy, Clone, Default)] pub struct Sum(PhantomData<[T; N]>); -impl DSP for Sum +impl DSPMeta for Sum { + type Sample = T; +} + +impl DSPProcess for Sum where T: Scalar, { - type Sample = T; - fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; 1] { [x.into_iter().fold(T::zero(), |a, b| a + b)] } @@ -67,7 +81,7 @@ where impl DspAnalysis for Sum where - Self: DSP, + Self: DSPProcess, { fn h_z(&self, _z: Complex) -> [[Complex; 1]; N] { [[Complex::one()]; N] @@ -83,6 +97,22 @@ pub struct P1 { s: T, } +impl DSPMeta for P1 { + type Sample = T; + + fn set_samplerate(&mut self, samplerate: f32) { + self.w_step = T::simd_pi() / T::from_f64(samplerate as _) + } + + fn latency(&self) -> usize { + 1 + } + + fn reset(&mut self) { + self.s = T::zero(); + } +} + impl DspAnalysis<1, 3> for P1 where Self::Sample: nalgebra::RealField, @@ -110,9 +140,7 @@ impl P1 { } } -impl DSP<1, 3> for P1 { - type Sample = T; - +impl DSPProcess<1, 3> for P1 { #[inline(always)] #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 3] { @@ -128,30 +156,25 @@ impl DSP<1, 3> for P1 { let ap = 2. * lp - x[0]; [lp, hp, ap] } - - fn set_samplerate(&mut self, samplerate: f32) { - self.w_step = T::simd_pi() / T::from_f64(samplerate as _) - } - - fn latency(&self) -> usize { - 1 - } - - fn reset(&mut self) { - self.s = T::zero(); - } } -/// Process inner DSP blocks in series. `DSP` is implemented for tuples up to 8 elements all of the same I/O configuration. +/// Process inner DSP blocks in series. `DSP` is implemented for tuples up to 8 elements all the same I/O configuration. #[derive(Debug, Copy, Clone)] pub struct Series(pub T); macro_rules! series_tuple { ($($p:ident),*) => { #[allow(non_snake_case)] - impl<__Sample: $crate::Scalar, $($p: $crate::dsp::DSP),*, const N: usize> DSP for $crate::dsp::blocks::Series<($($p),*)> { + impl<__Sample: $crate::Scalar, $($p: $crate::dsp::DSPMeta),*> DSPMeta for $crate::dsp::blocks::Series<($($p),*)> { type Sample = __Sample; + fn set_samplerate(&mut self, samplerate: f32) { + let Self(($($p),*)) = self; + $( + $p.set_samplerate(samplerate); + )* + } + fn latency(&self) -> usize { let Self(($($p),*)) = self; 0 $( @@ -165,7 +188,10 @@ macro_rules! series_tuple { $p.reset(); )* } + } + #[allow(non_snake_case)] + impl<__Sample: $crate::Scalar, $($p: $crate::dsp::DSPProcess),*, const N: usize> DSPProcess for $crate::dsp::blocks::Series<($($p),*)> { #[allow(non_snake_case)] #[inline(always)] fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { @@ -175,13 +201,6 @@ macro_rules! series_tuple { )* x } - - fn set_samplerate(&mut self, samplerate: f32) { - let Self(($($p),*)) = self; - $( - $p.set_samplerate(samplerate); - )* - } } }; } @@ -194,6 +213,44 @@ series_tuple!(A, B, C, D, E, F); series_tuple!(A, B, C, D, E, F, G); series_tuple!(A, B, C, D, E, F, G, H); +impl DSPMeta for Series<[P; C]> { + type Sample = P::Sample; + + fn latency(&self) -> usize { + self.0.iter().map(|p| p.latency()).sum() + } + + fn set_samplerate(&mut self, samplerate: f32) { + for p in &mut self.0 { + p.set_samplerate(samplerate); + } + } + + fn reset(&mut self) { + for p in &mut self.0 { + p.reset(); + } + } +} + +impl, const N: usize, const C: usize> DSPProcess for Series<[P; C]> { + fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { + self.0.iter_mut().fold(x, |x, dsp| dsp.process(x)) + } +} + +impl DspAnalysis for Series<[P; C]> +where + P: DspAnalysis, +{ + fn h_z(&self, z: Complex) -> [[Complex; N]; N] { + self.0.iter().fold([[Complex::one(); N]; N], |acc, f| { + let ret = f.h_z(z); + std::array::from_fn(|i| std::array::from_fn(|j| acc[i][j] * ret[i][j])) + }) + } +} + /// Specialized `Series` struct that doesn't restrict the I/O count of either DSP struct #[derive(Debug, Copy, Clone)] pub struct Series2(A, PhantomData<[(); INNER]>, B); @@ -202,8 +259,8 @@ impl Series2 { /// Construct a new `Series2` instance, with each inner DSP instance given. pub const fn new(a: A, b: B) -> Self where - A: DSP, - B: DSP, + A: DSPProcess, + B: DSPProcess, { Self(a, PhantomData, b) } @@ -229,19 +286,13 @@ impl Series2 { } } -impl DSP for Series2 +impl DSPMeta for Series2 where - A: DSP, - B: DSP, + A: DSPMeta, + B: DSPMeta, { type Sample = A::Sample; - fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { - let Self(a, _, b) = self; - let j = a.process(x); - b.process(j) - } - fn set_samplerate(&mut self, samplerate: f32) { self.0.set_samplerate(samplerate); self.2.set_samplerate(samplerate); @@ -259,39 +310,15 @@ where } } -impl, const N: usize, const C: usize> DSP for Series<[P; C]> { - type Sample = P::Sample; - - fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { - self.0.iter_mut().fold(x, |x, dsp| dsp.process(x)) - } - - fn set_samplerate(&mut self, samplerate: f32) { - for s in &mut self.0 { - s.set_samplerate(samplerate); - } - } - - fn latency(&self) -> usize { - self.0.iter().map(|dsp| dsp.latency()).sum() - } - - fn reset(&mut self) { - for dsp in self.0.iter_mut() { - dsp.reset(); - } - } -} - -impl DspAnalysis for Series<[P; C]> +impl DSPProcess for Series2 where - P: DspAnalysis, + A: DSPProcess, + B: DSPProcess, { - fn h_z(&self, z: Complex) -> [[Complex; N]; N] { - self.0.iter().fold([[Complex::one(); N]; N], |acc, f| { - let ret = f.h_z(z); - std::array::from_fn(|i| std::array::from_fn(|j| acc[i][j] * ret[i][j])) - }) + fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { + let Self(a, _, b) = self; + let j = a.process(x); + b.process(j) } } @@ -302,7 +329,7 @@ pub struct Parallel(pub T); macro_rules! parallel_tuple { ($($p:ident),*) => { #[allow(non_snake_case)] - impl<__Sample: $crate::Scalar, $($p: $crate::dsp::DSP),*, const N: usize> $crate::dsp::DSP for $crate::dsp::blocks::Parallel<($($p),*)> { + impl<__Sample: $crate::Scalar, $($p: $crate::dsp::DSPMeta),*> $crate::dsp::DSPMeta for $crate::dsp::blocks::Parallel<($($p),*)> { type Sample = __Sample; fn latency(&self) -> usize { @@ -314,14 +341,23 @@ macro_rules! parallel_tuple { latency } + fn set_samplerate(&mut self, samplerate: f32) { + let Self(($($p),*)) = self; + $( + $p.set_samplerate(samplerate); + )* + } + fn reset(&mut self) { let Self(($($p),*)) = self; $( $p.reset(); )* } + } - #[allow(non_snake_case)] + #[allow(non_snake_case)] + impl<__Sample: $crate::Scalar, $($p: $crate::dsp::DSPProcess),*, const N: usize> $crate::dsp::DSPProcess for $crate::dsp::blocks::Parallel<($($p),*)> { #[inline(always)] fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { let Self(($($p),*)) = self; @@ -334,13 +370,6 @@ macro_rules! parallel_tuple { )* ret } - - fn set_samplerate(&mut self, samplerate: f32) { - let Self(($($p),*)) = self; - $( - $p.set_samplerate(samplerate); - )* - } } }; } @@ -353,18 +382,9 @@ parallel_tuple!(A, B, C, D, E, F); parallel_tuple!(A, B, C, D, E, F, G); parallel_tuple!(A, B, C, D, E, F, G, H); -impl, const I: usize, const O: usize, const N: usize> DSP for Parallel<[P; N]> { +impl DSPMeta for Parallel<[P; C]> { type Sample = P::Sample; - fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { - self.0 - .iter_mut() - .map(|dsp| dsp.process(x)) - .fold([Self::Sample::from_f64(0.0); O], |out, dsp| { - std::array::from_fn(|i| out[i] + dsp[i]) - }) - } - fn set_samplerate(&mut self, samplerate: f32) { for s in &mut self.0 { s.set_samplerate(samplerate); @@ -382,6 +402,19 @@ impl, const I: usize, const O: usize, const N: usize> DSP for } } +impl, const I: usize, const O: usize, const N: usize> DSPProcess + for Parallel<[P; N]> +{ + fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { + self.0 + .iter_mut() + .map(|dsp| dsp.process(x)) + .fold([Self::Sample::from_f64(0.0); O], |out, dsp| { + std::array::from_fn(|i| out[i] + dsp[i]) + }) + } +} + impl DspAnalysis for Parallel<[P; N]> where P: DspAnalysis, @@ -413,12 +446,17 @@ where } } -impl DSP for ModMatrix +impl DSPMeta for ModMatrix where T: Scalar, { type Sample = T; +} +impl DSPProcess for ModMatrix +where + T: Scalar, +{ fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { let res = self.weights * SVector::from(x); std::array::from_fn(|i| res[i]) @@ -428,7 +466,7 @@ where /// Feedback adapter with a one-sample delay and integrated mixing and summing point. pub struct Feedback where - FF: DSP, + FF: DSPProcess, { memory: [FF::Sample; N], /// Inner DSP instance @@ -438,18 +476,16 @@ where pub mix: [FF::Sample; N], } -impl, const N: usize> DSP for Feedback { +impl DSPMeta for Feedback +where + FF: DSPProcess, + FB: DSPProcess, +{ type Sample = FF::Sample; - fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { - let x = std::array::from_fn(|i| self.memory[i] * self.mix[i] + x[i]); - let y = self.feedforward.process(x); - self.memory = y; - y - } - fn set_samplerate(&mut self, samplerate: f32) { self.feedforward.set_samplerate(samplerate); + self.feedback.set_samplerate(samplerate); } fn latency(&self) -> usize { @@ -457,29 +493,19 @@ impl, const N: usize> DSP for Feedback { } fn reset(&mut self) { - self.memory.fill(Self::Sample::zero()); + self.memory.fill(FB::Sample::from_f64(0.0)); self.feedforward.reset(); + self.feedback.reset(); } } - -impl DSP for Feedback +impl DSPMeta for Feedback where - FF: DSP, - FB: DSP>::Sample>, + FF: DSPProcess, { type Sample = FF::Sample; - fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { - let fb = self.feedback.process(self.memory); - let x = std::array::from_fn(|i| fb[i] * self.mix[i] + x[i]); - let y = self.feedforward.process(x); - self.memory = y; - y - } - fn set_samplerate(&mut self, samplerate: f32) { self.feedforward.set_samplerate(samplerate); - self.feedback.set_samplerate(samplerate); } fn latency(&self) -> usize { @@ -487,13 +513,35 @@ where } fn reset(&mut self) { - self.memory.fill(FB::Sample::from_f64(0.0)); + self.memory.fill(Self::Sample::zero()); self.feedforward.reset(); - self.feedback.reset(); } } -impl, FB, const N: usize> Feedback { +impl, const N: usize> DSPProcess for Feedback { + fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { + let x = std::array::from_fn(|i| self.memory[i] * self.mix[i] + x[i]); + let y = self.feedforward.process(x); + self.memory = y; + y + } +} + +impl DSPProcess for Feedback +where + FF: DSPProcess, + FB: DSPProcess::Sample>, +{ + fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { + let fb = self.feedback.process(self.memory); + let x = std::array::from_fn(|i| fb[i] * self.mix[i] + x[i]); + let y = self.feedforward.process(x); + self.memory = y; + y + } +} + +impl, FB, const N: usize> Feedback { /// Create a new Feedback adapter with the provider inner DSP instance. Sets the mix to 0 by default. pub fn new(feedforward: FF, feedback: FB) -> Self { Self { diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index b20422a..e31ceb0 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -1,8 +1,9 @@ #![doc = include_str!("./README.md")] -use crate::dsp::buffer::{AudioBufferBox, AudioBufferMut, AudioBufferRef}; + use nalgebra::Complex; use num_traits::Zero; +use crate::dsp::buffer::{AudioBufferBox, AudioBufferMut, AudioBufferRef}; use crate::Scalar; use self::analysis::DspAnalysis; @@ -13,25 +14,14 @@ pub mod buffer; pub mod parameter; pub mod utils; -/// DSP trait. This is the main abstraction of the whole library. -/// -/// Implementors of this trait are processes that work on a per-sample basis. -/// -/// Multichannel I/O is supported, and is determined by the `I` and `O` const generics. -/// It's up to each implementor to document what the inputs and outputs mean. -/// There are no restrictions on the number of input or output channels in and of itself. -/// Implementors can support multiple configurations of I/O for different setups. +/// Trait for interacting with a DSP algorithm, outside of processing. Shared by processors of both +/// per-sample algorithms and block-based algorithms. #[allow(unused_variables)] -pub trait DSP { +pub trait DSPMeta { /// Type of the audio sample used by this DSP instance. type Sample: Scalar; - /// Process a single sample of audio. No assumptions are made on the contents of said sample, - /// so it can both work for working with audio data, but also control signals like frequency in Hertz - /// for oscillators, or gate signals that are actually either 0 or 1. - fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O]; - - /// Sets the processing samplerate for this [`DSP`] instance. + /// Sets the processing samplerate for this [`DSPProcess`] instance. fn set_samplerate(&mut self, samplerate: f32) {} /// Report the latency of this DSP instance, that is the time, in samples, it takes for an input sample to be @@ -45,15 +35,27 @@ pub trait DSP { fn reset(&mut self) {} } -/// Trait for DSP processes that take in buffers of audio instead of single-samples. -/// Documentation of [`DSP`] still applies in here; only the process method changes. -#[allow(unused_variables)] -pub trait DSPBlock { - type Sample: Scalar; +/// DSP trait. This is the main abstraction of the whole library. +/// +/// Implementors of this trait are processes that work on a per-sample basis. +/// +/// Multichannel I/O is supported, and is determined by the `I` and `O` const generics. +/// It's up to each implementor to document what the inputs and outputs mean. +/// There are no restrictions on the number of input or output channels in and of itself. +/// Implementors can support multiple configurations of I/O for different setups. +pub trait DSPProcess: DSPMeta { + /// Process a single sample of audio. No assumptions are made on the contents of said sample, + /// so it can both work for working with audio data, but also control signals like frequency in Hertz + /// for oscillators, or gate signals that are actually either 0 or 1. + fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O]; +} +/// Trait for DSP processes that take in buffers of audio instead of single-samples. +/// Documentation of [`DSPProcess`] still applies in here; only the process method changes. +pub trait DSPProcessBlock: DSPMeta { /// Process a block of audio. Implementors should take the values of inputs and produce a stream into outputs, - /// *as if* it was processed sample-by-sample. Ideally there should be no difference between a [`DSP`] and [`DSPBlock`] - /// implementation, when both can exist. Note that [`DSPBlock`] is blanket-implemented for all [`DSP`] implementors, + /// *as if* it was processed sample-by-sample. Ideally there should be no difference between a [`DSPProcess`] and [`DSPProcessBlock`] + /// implementation, when both can exist. Note that [`DSPProcessBlock`] is blanket-implemented for all [`DSPProcess`] implementors, /// and as such, a simultaneous implementation of both onto one struct is impossible. /// /// Implementors should assume inputs and outputs are of the same length, as it is the caller's responsibility to make sure of that. @@ -63,35 +65,18 @@ pub trait DSPBlock { outputs: AudioBufferMut, ); - /// Sets the processing samplerate for this [`DSP`] instance. - fn set_samplerate(&mut self, samplerate: f32) {} - - /// Define an optional maximum buffer size alloed by this [`DSPBlock`] instance. Callers into this instance must + /// Define an optional maximum buffer size alloed by this [`DSPProcessBlock`] instance. Callers into this instance must /// then only provide buffers that are up to this size in samples. #[inline(always)] fn max_block_size(&self) -> Option { None } - - /// Report the latency of this DSP instance, that is the time, in samples, it takes for an input sample to be - /// output back. - #[inline(always)] - fn latency(&self) -> usize { - 0 - } - - /// Reset this instance. Parameters should be kept, but any memory and derived state should be put back to a - /// well-known default value. - #[inline(always)] - fn reset(&mut self) {} } -impl DSPBlock for P +impl DSPProcessBlock for P where - P: DSP, + P: DSPProcess, { - type Sample = >::Sample; - #[inline(never)] fn process_block( &mut self, @@ -105,30 +90,16 @@ where outputs.set_frame(i, self.process(inputs.get_frame(i))) } } - - fn set_samplerate(&mut self, samplerate: f32) { - DSP::set_samplerate(self, samplerate) - } - - #[inline(always)] - fn latency(&self) -> usize { - DSP::latency(self) - } - - #[inline(always)] - fn reset(&mut self) { - DSP::reset(self) - } } -/// Adapt a [`DSPBlock`] instance to be able to used as a [`DSP`]. +/// Adapt a [`DSPProcessBlock`] instance to be able to used as a [`DSPProcess`]. /// /// This introduces as much latency as the internal buffer size is. /// The internal buffer size is determined by either the max accepted buffer size of the inner instance, or is set /// to 64 samples by default. pub struct PerSampleBlockAdapter where - P: DSPBlock, + P: DSPProcessBlock, { input_buffer: AudioBufferBox, input_filled: usize, @@ -140,7 +111,7 @@ where impl std::ops::Deref for PerSampleBlockAdapter where - P: DSPBlock, + P: DSPProcessBlock, { type Target = P; @@ -151,7 +122,7 @@ where impl std::ops::DerefMut for PerSampleBlockAdapter where - P: DSPBlock, + P: DSPProcessBlock, { fn deref_mut(&mut self) -> &mut Self::Target { &mut self.inner @@ -160,7 +131,7 @@ where impl PerSampleBlockAdapter where - P: DSPBlock, + P: DSPProcessBlock, { pub const DEFAULT_BUFFER_SIZE: usize = 64; pub fn new(dsp_block: P) -> Self { @@ -187,12 +158,32 @@ where } } -impl DSP for PerSampleBlockAdapter +impl DSPMeta for PerSampleBlockAdapter where - P: DSPBlock, + P: DSPProcessBlock, { type Sample = P::Sample; + fn set_samplerate(&mut self, samplerate: f32) { + self.inner.set_samplerate(samplerate); + } + + fn latency(&self) -> usize { + (self.inner.latency() + self.input_buffer.samples()).saturating_sub(1) + } + + fn reset(&mut self) { + self.input_filled = 0; + self.output_filled = self.output_buffer.samples(); + self.input_buffer.fill(P::Sample::zero()); + self.output_buffer.fill(P::Sample::zero()); + } +} + +impl DSPProcess for PerSampleBlockAdapter +where + P: DSPProcessBlock, +{ fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { self.input_buffer.set_frame(self.input_filled, x); self.input_filled += 1; @@ -211,16 +202,6 @@ where [Self::Sample::zero(); O] } } - - fn latency(&self) -> usize { - (self.inner.latency() + self.input_buffer.samples()).saturating_sub(1) - } - - fn reset(&mut self) { - self.inner.reset(); - self.input_filled = 0; - self.output_filled = self.output_buffer.samples(); - } } impl DspAnalysis for PerSampleBlockAdapter @@ -248,9 +229,11 @@ mod tests { } } - impl DSPBlock<0, 1> for Counter { + impl DSPMeta for Counter { type Sample = T; + } + impl DSPProcessBlock<0, 1> for Counter { fn process_block( &mut self, inputs: AudioBufferRef, @@ -266,7 +249,7 @@ mod tests { } let mut adaptor = PerSampleBlockAdapter::new_with_max_buffer_size(Counter::::new(), 4); - assert_eq!(3, DSP::latency(&adaptor)); + assert_eq!(3, adaptor.latency()); let expected = [0.0, 0.0, 0.0, 0.0, 1.0, 2.0, 3.0, 0.0]; let mut actual = [0.0; 8]; diff --git a/src/dsp/parameter.rs b/src/dsp/parameter.rs index cd237bc..0b9635a 100644 --- a/src/dsp/parameter.rs +++ b/src/dsp/parameter.rs @@ -1,5 +1,5 @@ //! Shared values for passing parameters into DSP code. -//! +//! //! By design, those parameters are not used by the implementations in this //! library. This is because it would be too limiting and cumbersome to go from //! single-valued parameter values to potentially multi-valied values (eg. a @@ -12,7 +12,6 @@ //! [`Parallel`]: crate::dsp::blocks::Parallel use core::fmt; use std::fmt::Formatter; - use std::ops::Deref; use std::sync::atomic::Ordering; use std::sync::Arc; @@ -22,7 +21,7 @@ use nalgebra::SMatrix; use portable_atomic::{AtomicBool, AtomicF32}; use crate::dsp::blocks::{ModMatrix, Series2, P1}; -use crate::dsp::DSP; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::saturators::Slew; struct ParamImpl { @@ -101,16 +100,18 @@ pub struct FilteredParam

{ pub dsp: P, } -impl> FilteredParam

{ +impl> DSPMeta for FilteredParam

{ + type Sample = f32; +} + +impl> FilteredParam

{ /// Process the next sample generated from the parameter value. pub fn next_sample(&mut self) -> f32 { self.process([])[0] } } -impl> DSP<0, 1> for FilteredParam

{ - type Sample = f32; - +impl> DSPProcess<0, 1> for FilteredParam

{ fn process(&mut self, _x: [Self::Sample; 0]) -> [Self::Sample; 1] { self.dsp.process([self.param.get_value()]) } @@ -133,9 +134,11 @@ impl Smoothing { } } -impl DSP<1, 1> for Smoothing { +impl DSPMeta for Smoothing { type Sample = f32; +} +impl DSPProcess<1, 1> for Smoothing { #[inline] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { match self { @@ -152,17 +155,19 @@ pub struct SmoothedParam { smoothing: Smoothing, } -impl DSP<0, 1> for SmoothedParam { +impl DSPMeta for SmoothedParam { type Sample = f32; + fn set_samplerate(&mut self, samplerate: f32) { + self.smoothing.set_samplerate(samplerate); + } +} + +impl DSPProcess<0, 1> for SmoothedParam { #[inline] fn process(&mut self, _x: [Self::Sample; 0]) -> [Self::Sample; 1] { self.smoothing.process([self.param.get_value()]) } - - fn set_samplerate(&mut self, samplerate: f32) { - self.smoothing.set_samplerate(samplerate); - } } impl SmoothedParam { @@ -225,7 +230,7 @@ pub trait HasParameters { } } -/// Separate controller for controlling parameters of a [`DSP`] instance from another place. +/// Separate controller for controlling parameters of a [`DSPProcess`] instance from another place. pub struct ParamController(EnumMap) where P::Enum: EnumArray; diff --git a/src/dsp/utils.rs b/src/dsp/utils.rs index f39985a..ae26ecb 100644 --- a/src/dsp/utils.rs +++ b/src/dsp/utils.rs @@ -1,4 +1,4 @@ -//! Utilities for [`DSP`](crate::dsp::DSP) implementors and users. +//! Utilities for [`DSP`](crate::dsp::DSPProcess) implementors and users. /// Transmutes a mono block slice into a regular slice. #[inline(always)] diff --git a/src/filters/biquad.rs b/src/filters/biquad.rs index 9339746..9b4f8fe 100644 --- a/src/filters/biquad.rs +++ b/src/filters/biquad.rs @@ -5,7 +5,7 @@ //! # Usage //! //! ```rust -//! use valib::dsp::DSP; +//! use valib::dsp::DSPProcess; //! use valib::filters::biquad::Biquad; //! use valib::saturators::Tanh; //! let mut lowpass = Biquad::::lowpass(0.25 /* normalized frequency */, 0.707 /* Q */); @@ -16,7 +16,7 @@ use nalgebra::Complex; use numeric_literals::replace_float_literals; use crate::dsp::analysis::DspAnalysis; -use crate::dsp::DSP; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::{ saturators::{Dynamic, Saturator}, Scalar, @@ -219,9 +219,11 @@ impl Biquad { } } -impl> DSP<1, 1> for Biquad { +impl> DSPMeta for Biquad { type Sample = T; +} +impl> DSPProcess<1, 1> for Biquad { #[inline] #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { @@ -241,7 +243,7 @@ impl> DSP<1, 1> for Biquad { impl DspAnalysis<1, 1> for Biquad where - Self: DSP<1, 1, Sample = T>, + Self: DSPProcess<1, 1, Sample = T>, { fn h_z(&self, z: Complex) -> [[Complex; 1]; 1] { let num = z.powi(-1).scale(self.b[1]) + z.powi(-2).scale(self.b[2]) + self.b[0]; @@ -255,7 +257,7 @@ mod tests { use crate::{ dsp::{ buffer::{AudioBufferBox, AudioBufferRef}, - DSPBlock, + DSPProcessBlock, }, saturators::clippers::DiodeClipperModel, }; diff --git a/src/filters/ladder.rs b/src/filters/ladder.rs index c683152..affdbf9 100644 --- a/src/filters/ladder.rs +++ b/src/filters/ladder.rs @@ -5,7 +5,7 @@ //! # Example //! //! ```rust -//! use valib::dsp::DSP; +//! use valib::dsp::DSPProcess; //! use valib::filters::ladder::{Ladder, OTA}; //! use valib::saturators::Tanh; //! let mut filter = Ladder::>::new(44100.0, 300.0, 0.5); @@ -17,8 +17,9 @@ use std::fmt; use nalgebra::{Complex, SVector}; use numeric_literals::replace_float_literals; +use crate::dsp::DSPMeta; use crate::{ - dsp::{analysis::DspAnalysis, DSP}, + dsp::{analysis::DspAnalysis, DSPProcess}, math::bilinear_prewarming_bounded, saturators::{Saturator, Tanh}, Scalar, @@ -94,7 +95,7 @@ impl> LadderTopology for Transistor { } } -/// Ladder filter. This [`DSP`] instance implements a saturated 4-pole lowpass filter, with feedback negatively added +/// Ladder filter. This [`DSPProcess`] instance implements a saturated 4-pole lowpass filter, with feedback negatively added /// back into the input. #[derive(Debug, Copy, Clone)] pub struct Ladder> { @@ -188,20 +189,9 @@ impl> Ladder { } } -impl> DSP<1, 1> for Ladder { +impl> DSPMeta for Ladder { type Sample = T; - #[inline(always)] - #[replace_float_literals(T::from_f64(literal))] - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { - let input_gain = if self.compensated { self.k + 1.0 } else { 1.0 }; - let x = input_gain * x[0]; - let y0 = x - self.k * self.s[3]; - let g = self.wc * self.inv_2fs; - self.s = self.topology.next_output(g, y0, self.s); - [self.s[3]] - } - fn set_samplerate(&mut self, samplerate: f32) { self.samplerate = T::from_f64(samplerate as _); self.inv_2fs = T::simd_recip(self.samplerate + self.samplerate); @@ -216,6 +206,19 @@ impl> DSP<1, 1> for Ladder> DSPProcess<1, 1> for Ladder { + #[inline(always)] + #[replace_float_literals(T::from_f64(literal))] + fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { + let input_gain = if self.compensated { self.k + 1.0 } else { 1.0 }; + let x = input_gain * x[0]; + let y0 = x - self.k * self.s[3]; + let g = self.wc * self.inv_2fs; + self.s = self.topology.next_output(g, y0, self.s); + [self.s[3]] + } +} + impl> DspAnalysis<1, 1> for Ladder { #[replace_float_literals(Complex::from(T::from_f64(literal)))] fn h_z(&self, z: nalgebra::Complex) -> [[nalgebra::Complex; 1]; 1] { @@ -236,7 +239,7 @@ mod tests { use rstest::rstest; use simba::simd::SimdComplexField; - use crate::dsp::{buffer::AudioBuffer, DSPBlock}; + use crate::dsp::{buffer::AudioBuffer, DSPProcessBlock}; use crate::saturators::clippers::DiodeClipperModel; use super::*; diff --git a/src/filters/statespace.rs b/src/filters/statespace.rs index 1d8e58c..2651572 100644 --- a/src/filters/statespace.rs +++ b/src/filters/statespace.rs @@ -4,7 +4,7 @@ //! //! ```rust //! use nalgebra::SMatrix; -//! use valib::dsp::DSP; +//! use valib::dsp::DSPProcess; //! use valib::filters::statespace::StateSpace; //! use valib::Scalar; //! @@ -27,7 +27,7 @@ use std::f64::NAN; use nalgebra::{Complex, SMatrix, SVector, SimdComplexField}; use num_traits::Zero; -use crate::dsp::{analysis::DspAnalysis, DSP}; +use crate::dsp::{analysis::DspAnalysis, DSPMeta, DSPProcess}; use crate::Scalar; // TODO: Add saturators @@ -109,11 +109,15 @@ impl DSP +impl DSPMeta for StateSpace { type Sample = T; +} +impl DSPProcess + for StateSpace +{ fn process(&mut self, x: [Self::Sample; IN]) -> [Self::Sample; OUT] { let x = SVector::from(x); let y = self.c * self.state + self.d * x; @@ -128,15 +132,17 @@ mod tests { use numeric_literals::replace_float_literals; use crate::dsp::buffer::AudioBuffer; - use crate::dsp::DSPBlock; + use crate::dsp::DSPProcessBlock; use super::*; struct RC(StateSpace); - impl DSP<1, 1> for RC { + impl DSPMeta for RC { type Sample = T; + } + impl DSPProcess<1, 1> for RC { fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { self.0.process(x) } diff --git a/src/filters/svf.rs b/src/filters/svf.rs index 3778d83..197e253 100644 --- a/src/filters/svf.rs +++ b/src/filters/svf.rs @@ -1,5 +1,5 @@ //! Implementation of various blocks of DSP code from the VA Filter Design book. -//! +//! //! Downloaded from //! All references in this module, unless specified otherwise, are taken from this book. @@ -7,7 +7,7 @@ use nalgebra::Complex; use num_traits::One; use numeric_literals::replace_float_literals; -use crate::dsp::DSP; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::{ dsp::analysis::DspAnalysis, saturators::{Linear, Saturator}, @@ -28,9 +28,16 @@ pub struct Svf { sats: [Mode; 2], } -impl> DSP<1, 3> for Svf { +impl> DSPMeta for Svf { type Sample = T; + fn set_samplerate(&mut self, samplerate: f32) { + self.w_step = T::simd_pi() / T::from_f64(samplerate as _); + self.update_coefficients(); + } +} + +impl> DSPProcess<1, 3> for Svf { #[inline(always)] #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 3] { @@ -54,11 +61,6 @@ impl> DSP<1, 3> for Svf { self.sats[1].update_state(s2 / 10., self.s[1]); [lp, bp, hp] } - - fn set_samplerate(&mut self, samplerate: f32) { - self.w_step = T::simd_pi() / T::from_f64(samplerate as _); - self.update_coefficients(); - } } impl> DspAnalysis<1, 3> for Svf { diff --git a/src/fir.rs b/src/fir.rs index e48cfa8..1abc288 100644 --- a/src/fir.rs +++ b/src/fir.rs @@ -2,7 +2,8 @@ use std::{collections::VecDeque, ops}; use numeric_literals::replace_float_literals; -use crate::{dsp::DSP, Scalar}; +use crate::dsp::DSPMeta; +use crate::{dsp::DSPProcess, Scalar}; fn slice_add>(in1: &[T], in2: &[T], out: &mut [T]) { let len = in1.len().min(in2.len()).min(out.len()); @@ -109,7 +110,7 @@ impl Fir { } } -impl DSP<1, 1> for Fir { +impl DSPMeta for Fir { type Sample = T; fn latency(&self) -> usize { @@ -119,7 +120,9 @@ impl DSP<1, 1> for Fir { fn reset(&mut self) { self.memory = VecDeque::from(vec![T::from_f64(0.0); self.kernel.len()]); } +} +impl DSPProcess<1, 1> for Fir { fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { let [x] = x; self.memory.pop_front(); @@ -172,7 +175,7 @@ pub mod kernels { #[cfg(test)] mod tests { use crate::dsp::buffer::AudioBuffer; - use crate::dsp::DSPBlock; + use crate::dsp::DSPProcessBlock; use super::*; diff --git a/src/oscillators/blit.rs b/src/oscillators/blit.rs index 00d9a76..5d5b9d5 100644 --- a/src/oscillators/blit.rs +++ b/src/oscillators/blit.rs @@ -1,6 +1,7 @@ use numeric_literals::replace_float_literals; -use crate::{dsp::DSP, Scalar}; +use crate::dsp::DSPMeta; +use crate::{dsp::DSPProcess, Scalar}; /// Raw Band-Limited Impulse Train output. To be fed to leaky integrators to reconstruct the oscillator shape. #[derive(Debug, Clone, Copy)] @@ -13,9 +14,18 @@ pub struct Blit { samplerate: f32, } -impl DSP<0, 1> for Blit { +impl DSPMeta for Blit { type Sample = T; + #[replace_float_literals(T::from_f64(literal))] + fn reset(&mut self) { + self.p = 0.0; + self.dp = 1.0; + self.x = 0.0; + } +} + +impl DSPProcess<0, 1> for Blit { #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, _: [Self::Sample; 0]) -> [Self::Sample; 1] { self.p += self.dp; @@ -32,13 +42,6 @@ impl DSP<0, 1> for Blit { self.x = self.x.simd_max(1e-5); [self.x.simd_sin() / self.x] } - - #[replace_float_literals(T::from_f64(literal))] - fn reset(&mut self) { - self.p = 0.0; - self.dp = 1.0; - self.x = 0.0; - } } impl Blit { @@ -76,9 +79,15 @@ pub struct Sawtooth { dc: T, } -impl DSP<0, 1> for Sawtooth { +impl DSPMeta for Sawtooth { type Sample = T; + fn set_samplerate(&mut self, samplerate: f32) { + self.blit.set_samplerate(samplerate); + } +} + +impl DSPProcess<0, 1> for Sawtooth { #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 0]) -> [Self::Sample; 1] { let [x] = self.blit.process(x); @@ -119,9 +128,16 @@ pub struct Square { integrator_state: T, } -impl DSP<0, 1> for Square { +impl DSPMeta for Square { type Sample = T; + fn set_samplerate(&mut self, samplerate: f32) { + self.blit_pos.set_samplerate(samplerate); + self.blit_neg.set_samplerate(samplerate); + } +} + +impl DSPProcess<0, 1> for Square { #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 0]) -> [Self::Sample; 1] { let [xpos] = self.blit_pos.process(x); @@ -172,7 +188,7 @@ impl Square { #[cfg(test)] mod tests { use crate::dsp::buffer::AudioBuffer; - use crate::dsp::DSPBlock; + use crate::dsp::DSPProcessBlock; use super::*; diff --git a/src/oscillators/mod.rs b/src/oscillators/mod.rs index 1d5aff7..7d94abc 100644 --- a/src/oscillators/mod.rs +++ b/src/oscillators/mod.rs @@ -1,6 +1,7 @@ use numeric_literals::replace_float_literals; -use crate::{dsp::DSP, Scalar}; +use crate::dsp::DSPMeta; +use crate::{dsp::DSPProcess, Scalar}; pub mod blit; pub mod wavetable; @@ -11,9 +12,11 @@ pub struct Phasor { step: T, } -impl DSP<0, 1> for Phasor { +impl DSPMeta for Phasor { type Sample = T; +} +impl DSPProcess<0, 1> for Phasor { fn process(&mut self, _: [Self::Sample; 0]) -> [Self::Sample; 1] { let p = self.phase; let new_phase = self.phase + self.step; diff --git a/src/oscillators/wavetable.rs b/src/oscillators/wavetable.rs index 1f80dde..7afed1f 100644 --- a/src/oscillators/wavetable.rs +++ b/src/oscillators/wavetable.rs @@ -2,8 +2,9 @@ use std::{marker::PhantomData, ops::Range}; use num_traits::Num; +use crate::dsp::DSPMeta; use crate::{ - dsp::DSP, + dsp::DSPProcess, math::interpolation::{Interpolate, Linear}, simd::SimdPartialOrd, Scalar, SimdCast, @@ -16,12 +17,15 @@ pub struct Wavetable { __interp: PhantomData, } +impl DSPMeta for Wavetable { + type Sample = T; +} + impl, const N: usize, const I: usize, Interp: Interpolate> - DSP<1, 1> for Wavetable + DSPProcess<1, 1> for Wavetable where >::Output: Copy + Num + SimdPartialOrd, { - type Sample = T; fn process(&mut self, [phase]: [Self::Sample; 1]) -> [Self::Sample; 1] { let y = Interp::interpolate_on_slice(phase.simd_fract(), &self.array); [y] diff --git a/src/oversample/mod.rs b/src/oversample/mod.rs index 58de025..e85422e 100644 --- a/src/oversample/mod.rs +++ b/src/oversample/mod.rs @@ -1,14 +1,14 @@ use std::ops::{Deref, DerefMut}; -use crate::dsp::DSP; use crate::dsp::{ buffer::{AudioBufferMut, AudioBufferRef}, parameter::{HasParameters, Parameter}, }; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::saturators::Linear; use crate::Scalar; use crate::{ - dsp::{blocks::Series, DSPBlock}, + dsp::{blocks::Series, DSPProcessBlock}, filters::biquad::Biquad, }; @@ -39,7 +39,7 @@ impl Oversample { } pub fn latency(&self) -> usize { - 2 * self.os_factor + DSP::latency(&self.pre_filter) + DSP::latency(&self.post_filter) + 2 * self.os_factor + self.pre_filter.latency() + self.post_filter.latency() } pub fn max_block_size(&self) -> usize { @@ -59,11 +59,11 @@ impl Oversample { pub fn reset(&mut self) { self.os_buffer.fill(T::zero()); - DSP::reset(&mut self.pre_filter); - DSP::reset(&mut self.post_filter); + self.pre_filter.reset(); + self.post_filter.reset(); } - pub fn with_dsp>(self, dsp: P) -> Oversampled { + pub fn with_dsp>(self, dsp: P) -> Oversampled { let max_block_size = dsp.max_block_size().unwrap_or(self.os_buffer.len()); // Verify that we satisfy the inner DSPBlock instance's requirement on maximum block size assert!(self.os_buffer.len() <= max_block_size); @@ -151,7 +151,7 @@ where #[deprecated = "Use Oversample::with_dsp"] pub fn new(oversampling: Oversample, inner: P) -> Self where - P: DSP<1, 1, Sample = T>, + P: DSPProcess<1, 1, Sample = T>, { oversampling.with_dsp(inner) } @@ -160,26 +160,29 @@ where } } -impl DSPBlock<1, 1> for Oversampled -where - T: Scalar, - P: DSPBlock<1, 1, Sample = T>, -{ +impl DSPMeta for Oversampled { type Sample = T; - fn latency(&self) -> usize { - self.oversampling.latency() + self.inner.latency() + fn set_samplerate(&mut self, samplerate: f32) { + self.inner + .set_samplerate(self.oversampling.os_factor as f32 * samplerate); } - fn max_block_size(&self) -> Option { - Some(self.oversampling.max_block_size()) + fn latency(&self) -> usize { + self.oversampling.latency() + self.inner.latency() } fn reset(&mut self) { self.oversampling.reset(); self.inner.reset(); } +} +impl DSPProcessBlock<1, 1> for Oversampled +where + T: Scalar, + P: DSPProcessBlock<1, 1, Sample = T>, +{ fn process_block(&mut self, inputs: AudioBufferRef, mut outputs: AudioBufferMut) { let mut os_block = self.oversampling.oversample(inputs.get_channel(0)); let input = &mut self.staging_buffer[..os_block.len()]; @@ -191,9 +194,8 @@ where os_block.finish(outputs.get_channel_mut(0)); } - fn set_samplerate(&mut self, samplerate: f32) { - self.inner - .set_samplerate(self.oversampling.os_factor as f32 * samplerate); + fn max_block_size(&self) -> Option { + Some(self.oversampling.max_block_size()) } } @@ -211,8 +213,9 @@ mod tests { use numeric_literals::replace_float_literals; + use crate::dsp::DSPMeta; use crate::{ - dsp::{buffer::AudioBufferBox, DSPBlock as _}, + dsp::{buffer::AudioBufferBox, DSPProcessBlock as _}, Scalar, }; @@ -238,10 +241,11 @@ mod tests { frequency: T, phase: T, } - - impl crate::dsp::DSP<1, 1> for NaiveSquare { + impl DSPMeta for NaiveSquare { type Sample = T; + } + impl crate::dsp::DSPProcess<1, 1> for NaiveSquare { #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, _: [Self::Sample; 1]) -> [Self::Sample; 1] { let step = self.frequency / self.samplerate; diff --git a/src/saturators/clippers/mod.rs b/src/saturators/clippers/mod.rs index 5f0bee5..f7db719 100644 --- a/src/saturators/clippers/mod.rs +++ b/src/saturators/clippers/mod.rs @@ -1,5 +1,3 @@ -mod diode_clipper_model_data; - use std::fmt; use nalgebra::{SMatrix, SVector}; @@ -7,9 +5,12 @@ use num_traits::Float; use numeric_literals::replace_float_literals; use simba::simd::SimdBool; -use crate::{dsp::DSP, math::newton_rhapson_tol_max_iter}; +use crate::dsp::DSPMeta; +use crate::{dsp::DSPProcess, math::newton_rhapson_tol_max_iter}; use crate::{math::RootEq, saturators::Saturator, Scalar}; +mod diode_clipper_model_data; + #[derive(Debug, Copy, Clone)] pub struct DiodeClipper { pub isat: T, @@ -114,12 +115,14 @@ impl DiodeClipper { } } -impl DSP<1, 1> for DiodeClipper +impl DSPMeta for DiodeClipper { + type Sample = T; +} + +impl DSPProcess<1, 1> for DiodeClipper where T::Element: Float, { - type Sample = T; - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { self.vin = x[0]; let mut vout = SVector::<_, 1>::new(self.last_vout); @@ -137,6 +140,10 @@ pub struct DiodeClipperModel { pub so: T, } +impl DSPMeta for DiodeClipperModel { + type Sample = T; +} + impl DiodeClipperModel { #[replace_float_literals(T::from_f64(literal))] #[inline] @@ -156,9 +163,7 @@ impl Default for DiodeClipperModel { } } -impl DSP<1, 1> for DiodeClipperModel { - type Sample = T; - +impl DSPProcess<1, 1> for DiodeClipperModel { #[inline(always)] fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { [self.eval(x)] @@ -176,12 +181,13 @@ impl Saturator for DiodeClipperModel { mod tests { use std::hint; - use crate::dsp::DSP; use simba::simd::SimdValue; + use crate::dsp::DSPProcess; + use super::{DiodeClipper, DiodeClipperModel}; - fn dc_sweep(name: &str, mut dsp: impl DSP<1, 1, Sample = f32>) { + fn dc_sweep(name: &str, mut dsp: impl DSPProcess<1, 1, Sample = f32>) { let results = Vec::from_iter( (-4800..=4800) .map(|i| i as f64 / 100.) @@ -191,7 +197,7 @@ mod tests { insta::assert_csv_snapshot!(&*full_name, results, { "[]" => insta::rounded_redaction(4) }); } - fn drive_test(name: &str, mut dsp: impl DSP<1, 1, Sample = f32>) { + fn drive_test(name: &str, mut dsp: impl DSPProcess<1, 1, Sample = f32>) { let sine_it = (0..).map(|i| i as f64 / 10.).map(f64::sin); let amp = (0..5000).map(|v| v as f64 / 5000. * 500.); let output = sine_it.zip(amp).map(|(a, b)| a * b).map(|v| { diff --git a/src/saturators/mod.rs b/src/saturators/mod.rs index 3e34130..3917d72 100644 --- a/src/saturators/mod.rs +++ b/src/saturators/mod.rs @@ -1,8 +1,8 @@ -use crate::dsp::DSP; use numeric_literals::replace_float_literals; use clippers::DiodeClipperModel; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::Scalar; pub mod clippers; @@ -158,7 +158,7 @@ impl Default for Slew { } } -impl DSP<1, 1> for Slew { +impl DSPMeta for Slew { type Sample = T; fn latency(&self) -> usize { @@ -168,7 +168,9 @@ impl DSP<1, 1> for Slew { fn reset(&mut self) { self.last_out = T::from_f64(0.0); } +} +impl DSPProcess<1, 1> for Slew { fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { let y = self.slew(x[0]); self.last_out = y; diff --git a/src/voice.rs b/src/voice.rs index 18aa834..4f0c5b2 100644 --- a/src/voice.rs +++ b/src/voice.rs @@ -1,12 +1,14 @@ +use num_traits::{One, Zero}; + +use crate::dsp::DSPMeta; use crate::{ - dsp::DSP, + dsp::DSPProcess, util::{midi_to_freq, semitone_to_ratio}, Scalar, }; -use num_traits::{One, Zero}; #[allow(unused_variables)] -pub trait VoiceManager: DSP<0, N> { +pub trait VoiceManager: DSPProcess<0, N> { // Note trigger fn note_on(&mut self, midi_note: u8, velocity: f32); fn note_off(&mut self, midi_note: u8, velocity: f32); @@ -31,7 +33,7 @@ pub trait VoiceManager: DSP<0, N> { /// - Pressure (unipolar) /// - Velocity (unipolar) /// - Pan (bipolar) -pub trait Voice: DSP<5, 1> { +pub trait Voice: DSPProcess<5, 1> { fn create(freq: f32, pressure: f32, velocity: f32, pan: f32) -> Self; fn done(&self) -> bool; } @@ -49,12 +51,13 @@ pub struct VoiceController { pan: f32, } -impl DSP<2, 1> for VoiceController -where - V::Sample: Scalar, -{ +impl DSPMeta for VoiceController { type Sample = V::Sample; + fn set_samplerate(&mut self, samplerate: f32) { + self.voice.set_samplerate(samplerate); + } + fn latency(&self) -> usize { self.voice.latency() } @@ -62,7 +65,12 @@ where fn reset(&mut self) { self.voice.reset(); } +} +impl DSPProcess<2, 1> for VoiceController +where + V::Sample: Scalar, +{ fn process(&mut self, [bend_st, aftertouch]: [Self::Sample; 2]) -> [Self::Sample; 1] { let freq = self.center_freq * semitone_to_ratio(self.glide_semi); let freq = Self::Sample::from_f64(freq as _) * semitone_to_ratio(bend_st); @@ -79,10 +87,6 @@ where ]); [osc * Self::Sample::from_f64(self.gain as f64)] } - - fn set_samplerate(&mut self, samplerate: f32) { - self.voice.set_samplerate(samplerate); - } } #[derive(Debug, Clone, Copy)] @@ -91,13 +95,15 @@ pub struct Monophonic { aftertouch: f32, } -impl DSP<0, 1> for Monophonic { +impl DSPMeta for Monophonic { type Sample = V::Sample; fn reset(&mut self) { self.voice.take(); } +} +impl DSPProcess<0, 1> for Monophonic { fn process(&mut self, _: [Self::Sample; 0]) -> [Self::Sample; 1] { if self.voice.as_ref().is_some_and(|v| !v.voice.done()) { self.voice.as_mut().unwrap().process([ From 194e3213256094c02e59b0353bc9ef7e7128356a Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 23 Mar 2024 11:01:30 +0100 Subject: [PATCH 07/12] fix: post-merge fixes --- examples/diodeclipper/src/dsp.rs | 106 ++++++++++-------- examples/diodeclipper/src/lib.rs | 2 +- examples/dirty-biquad/src/dsp.rs | 22 ++-- examples/dirty-biquad/src/lib.rs | 3 +- examples/ladder/src/dsp.rs | 47 ++++---- examples/ladder/src/lib.rs | 9 +- examples/slew/src/lib.rs | 1 + examples/svfmixer/src/dsp.rs | 36 +++--- examples/svfmixer/src/lib.rs | 3 +- plugins/abrasive/src/filter.rs | 6 +- src/contrib/fundsp.rs | 17 ++- src/contrib/nih_plug.rs | 46 ++++---- src/dsp/blocks.rs | 58 +++++++--- src/dsp/buffer.rs | 89 ++++++++++++++- src/lib.rs | 1 + src/oversample/mod.rs | 54 ++++++--- .../valib__oversample__tests__os block.snap | 16 +-- .../valib__oversample__tests__post os.snap | 30 ++--- 18 files changed, 366 insertions(+), 180 deletions(-) diff --git a/examples/diodeclipper/src/dsp.rs b/examples/diodeclipper/src/dsp.rs index 4d515af..031b6de 100644 --- a/examples/diodeclipper/src/dsp.rs +++ b/examples/diodeclipper/src/dsp.rs @@ -1,11 +1,12 @@ -use enum_map::Enum; -use nih_plug::util::gain_to_db_fast; -use num_traits::Zero; use std::fmt; use std::fmt::Formatter; +use enum_map::Enum; +use num_traits::Zero; + +use valib::dsp::buffer::{AudioBufferMut, AudioBufferRef}; use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam}; -use valib::dsp::{DSPBlock, DSP}; +use valib::dsp::{DSPMeta, DSPProcess, DSPProcessBlock}; use valib::filters::biquad::Biquad; use valib::oversample::{Oversample, Oversampled}; use valib::saturators::clippers::{DiodeClipper, DiodeClipperModel}; @@ -29,15 +30,11 @@ impl DcBlocker { } } -impl DSP<1, 1> for DcBlocker { +impl DSPMeta for DcBlocker { type Sample = T; - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { - self.0.process(x) - } - fn set_samplerate(&mut self, samplerate: f32) { - DSP::set_samplerate(&mut self.0, samplerate); + self.0.set_samplerate(samplerate); self.0.update_coefficients(&Biquad::highpass( T::from_f64((Self::CUTOFF_HZ / samplerate) as f64), T::from_f64(Self::Q as f64), @@ -45,7 +42,7 @@ impl DSP<1, 1> for DcBlocker { } fn latency(&self) -> usize { - DSP::latency(&self.0) + self.0.latency() } fn reset(&mut self) { @@ -53,6 +50,12 @@ impl DSP<1, 1> for DcBlocker { } } +impl DSPProcess<1, 1> for DcBlocker { + fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { + self.0.process(x) + } +} + type Sample = AutoF32x2; type Sample64 = AutoF64x2; @@ -151,12 +154,32 @@ impl HasParameters for DspInner { } } -impl DSP<1, 1> for DspInner { +impl DSPMeta for DspInner { type Sample = Sample; + fn set_samplerate(&mut self, samplerate: f32) { + self.nr_model.set_samplerate(samplerate); + self.nr_nr.set_samplerate(samplerate); + } + + fn latency(&self) -> usize { + if self.model_switch.get_bool() { + self.nr_model.latency() + } else { + self.nr_nr.latency() + } + } + + fn reset(&mut self) { + self.nr_model.reset(); + self.nr_nr.reset(); + } +} + +impl DSPProcess<1, 1> for DspInner { fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { if self.force_reset.has_changed() { - DSP::reset(self) + self.reset() } self.update_from_params(); @@ -170,24 +193,6 @@ impl DSP<1, 1> for DspInner { .map(|x| x / drive.simd_asinh()) .map(|x| x.cast()) } - - fn set_samplerate(&mut self, samplerate: f32) { - DSP::set_samplerate(&mut self.nr_model, samplerate); - DSP::set_samplerate(&mut self.nr_nr, samplerate); - } - - fn latency(&self) -> usize { - if self.model_switch.get_bool() { - DSP::latency(&self.nr_model) - } else { - DSP::latency(&self.nr_nr) - } - } - - fn reset(&mut self) { - DSP::reset(&mut self.nr_model); - DSP::reset(&mut self.nr_nr); - } } pub struct Dsp { @@ -196,33 +201,38 @@ pub struct Dsp { dc_blocker: DcBlocker, } -impl DSPBlock<1, 1> for Dsp { +impl DSPMeta for Dsp { type Sample = Sample; - fn process_block(&mut self, inputs: &[[Self::Sample; 1]], outputs: &mut [[Self::Sample; 1]]) { - self.inner.process_block(inputs, outputs); - - for o in outputs { - *o = self.dc_blocker.process(*o); - } + fn set_samplerate(&mut self, samplerate: f32) { + self.inner.set_samplerate(samplerate); + self.dc_blocker.set_samplerate(samplerate); } - fn set_samplerate(&mut self, samplerate: f32) { - DSPBlock::set_samplerate(&mut self.inner, samplerate); - DSPBlock::set_samplerate(&mut self.dc_blocker, samplerate); + fn latency(&self) -> usize { + self.inner.latency() + self.dc_blocker.latency() } - fn max_block_size(&self) -> Option { - DSPBlock::max_block_size(&self.inner) + fn reset(&mut self) { + self.inner.reset(); + self.dc_blocker.reset(); } +} - fn latency(&self) -> usize { - DSPBlock::latency(&self.inner) + DSPBlock::latency(&self.dc_blocker) +impl DSPProcessBlock<1, 1> for Dsp { + fn process_block( + &mut self, + inputs: AudioBufferRef, + mut outputs: AudioBufferMut, + ) { + self.inner.process_block(inputs, outputs.as_mut()); + for i in 0..outputs.samples() { + outputs.set_frame(i, self.dc_blocker.process(outputs.get_frame(i))); + } } - fn reset(&mut self) { - DSPBlock::reset(&mut self.inner); - DSPBlock::reset(&mut self.dc_blocker); + fn max_block_size(&self) -> Option { + self.inner.max_block_size() } } diff --git a/examples/diodeclipper/src/lib.rs b/examples/diodeclipper/src/lib.rs index 1cd2278..8153631 100644 --- a/examples/diodeclipper/src/lib.rs +++ b/examples/diodeclipper/src/lib.rs @@ -6,7 +6,7 @@ use nih_plug::util::db_to_gain; use dsp::Dsp; use valib::contrib::nih_plug::{enum_int_param, process_buffer_simd, NihParamsController}; -use valib::dsp::DSPBlock; +use valib::dsp::{DSPMeta, DSPProcessBlock}; use crate::dsp::{create_dsp, DiodeType, DspParams}; diff --git a/examples/dirty-biquad/src/dsp.rs b/examples/dirty-biquad/src/dsp.rs index 0212bfd..8af72ad 100644 --- a/examples/dirty-biquad/src/dsp.rs +++ b/examples/dirty-biquad/src/dsp.rs @@ -4,7 +4,7 @@ use std::fmt::Formatter; use crate::{MAX_BLOCK_SIZE, OVERSAMPLE}; use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam}; -use valib::dsp::DSP; +use valib::dsp::{DSPMeta, DSPProcess}; use valib::filters::biquad::Biquad; use valib::oversample::{Oversample, Oversampled}; use valib::saturators::clippers::DiodeClipperModel; @@ -108,17 +108,9 @@ impl DspInner { } } -impl DSP<1, 1> for DspInner { +impl DSPMeta for DspInner { type Sample = Sample; - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { - self.update_params(); - let drive = Sample::splat(self.drive.next_sample()); - println!("Drive {}", drive.extract(0)); - let x = x.map(|x| x * drive); - self.biquad.process(x).map(|x| x / drive.simd_asinh()) - } - fn set_samplerate(&mut self, samplerate: f32) { self.samplerate = samplerate; self.drive.set_samplerate(samplerate); @@ -139,6 +131,16 @@ impl DSP<1, 1> for DspInner { } } +impl DSPProcess<1, 1> for DspInner { + fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { + self.update_params(); + let drive = Sample::splat(self.drive.next_sample()); + println!("Drive {}", drive.extract(0)); + let x = x.map(|x| x * drive); + self.biquad.process(x).map(|x| x / drive.simd_asinh()) + } +} + impl HasParameters for DspInner { type Enum = DspParameters; diff --git a/examples/dirty-biquad/src/lib.rs b/examples/dirty-biquad/src/lib.rs index 3f66845..7ba79cc 100644 --- a/examples/dirty-biquad/src/lib.rs +++ b/examples/dirty-biquad/src/lib.rs @@ -4,7 +4,8 @@ use nih_plug::prelude::*; use nih_plug::util::db_to_gain; use valib::contrib::nih_plug::{enum_int_param, process_buffer_simd, NihParamsController}; -use valib::dsp::DSPBlock; +use valib::dsp::DSPMeta; + use valib::simd::AutoF32x2; use crate::dsp::{DspParameters, FilterType, SaturatorType}; diff --git a/examples/ladder/src/dsp.rs b/examples/ladder/src/dsp.rs index a1cd111..f8375f4 100644 --- a/examples/ladder/src/dsp.rs +++ b/examples/ladder/src/dsp.rs @@ -1,14 +1,17 @@ -use crate::{MAX_BUFFER_SIZE, OVERSAMPLE}; -use enum_map::Enum; use std::fmt; use std::fmt::Formatter; + +use enum_map::Enum; + use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam}; -use valib::dsp::DSP; +use valib::dsp::{DSPMeta, DSPProcess}; use valib::filters::ladder::{Ideal, Ladder, Transistor, OTA}; use valib::oversample::{Oversample, Oversampled}; use valib::saturators::clippers::DiodeClipperModel; use valib::saturators::Tanh; -use valib::simd::{AutoF32x2, SimdComplexField, SimdValue}; +use valib::simd::{AutoF32x2, SimdValue}; + +use crate::{MAX_BUFFER_SIZE, OVERSAMPLE}; pub type Sample = AutoF32x2; @@ -19,17 +22,9 @@ enum DspLadder { OTA(Ladder>), } -impl DSP<1, 1> for DspLadder { +impl DSPMeta for DspLadder { type Sample = Sample; - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { - match self { - Self::Ideal(ladder) => ladder.process(x), - Self::Transistor(ladder) => ladder.process(x), - Self::OTA(ladder) => ladder.process(x), - } - } - fn set_samplerate(&mut self, samplerate: f32) { match self { Self::Ideal(ladder) => ladder.set_samplerate(samplerate), @@ -51,6 +46,16 @@ impl DSP<1, 1> for DspLadder { } } +impl DSPProcess<1, 1> for DspLadder { + fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { + match self { + Self::Ideal(ladder) => ladder.process(x), + Self::Transistor(ladder) => ladder.process(x), + Self::OTA(ladder) => ladder.process(x), + } + } +} + impl DspLadder { fn set_cutoff(&mut self, fc: Sample) { match self { @@ -148,15 +153,9 @@ impl DspInner { } } -impl DSP<1, 1> for DspInner { +impl DSPMeta for DspInner { type Sample = Sample; - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { - self.update_from_parameters(); - let drive = Sample::splat(self.drive.next_sample() / 4.0); - self.ladder.process(x.map(|x| x * drive)).map(|x| x / drive) - } - fn set_samplerate(&mut self, samplerate: f32) { self.samplerate = samplerate; self.drive.set_samplerate(samplerate); @@ -177,6 +176,14 @@ impl DSP<1, 1> for DspInner { } } +impl DSPProcess<1, 1> for DspInner { + fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { + self.update_from_parameters(); + let drive = Sample::splat(self.drive.next_sample() / 4.0); + self.ladder.process(x.map(|x| x * drive)).map(|x| x / drive) + } +} + impl HasParameters for DspInner { type Enum = DspParameters; diff --git a/examples/ladder/src/lib.rs b/examples/ladder/src/lib.rs index 1e31f23..1d24004 100644 --- a/examples/ladder/src/lib.rs +++ b/examples/ladder/src/lib.rs @@ -1,8 +1,11 @@ -use crate::dsp::{DspParameters, LadderType}; +use std::sync::Arc; + use nih_plug::prelude::*; -use std::sync::{atomic::AtomicBool, Arc}; + use valib::contrib::nih_plug::{enum_int_param, process_buffer_simd, NihParamsController}; -use valib::dsp::DSPBlock; +use valib::dsp::DSPMeta; + +use crate::dsp::{DspParameters, LadderType}; mod dsp; diff --git a/examples/slew/src/lib.rs b/examples/slew/src/lib.rs index f01e45c..03985d0 100644 --- a/examples/slew/src/lib.rs +++ b/examples/slew/src/lib.rs @@ -2,6 +2,7 @@ use std::sync::Arc; use nih_plug::{prelude::*, util::db_to_gain}; +use valib::dsp::DSPMeta; use valib::{ dsp::DSPProcess, saturators::Slew, diff --git a/examples/svfmixer/src/dsp.rs b/examples/svfmixer/src/dsp.rs index 518801b..26017e4 100644 --- a/examples/svfmixer/src/dsp.rs +++ b/examples/svfmixer/src/dsp.rs @@ -3,7 +3,7 @@ use nalgebra::SMatrix; use nih_plug::util::db_to_gain_fast; use valib::dsp::blocks::ModMatrix; use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam}; -use valib::dsp::DSPProcess; +use valib::dsp::{DSPMeta, DSPProcess}; use valib::filters::svf::Svf; use valib::oversample::Oversampled; use valib::saturators::{Clipper, Saturator, Slew}; @@ -79,24 +79,9 @@ impl HasParameters for DspInner { } } -impl DSPProcess<1, 1> for DspInner { +impl DSPMeta for DspInner { type Sample = Sample; - fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { - self.filter - .set_cutoff(Sample::splat(self.params[DspParam::Cutoff].next_sample())); - self.filter.set_r(Sample::splat( - 1.0 - self.params[DspParam::Resonance].next_sample(), - )); - self.mod_matrix.weights.x = Sample::splat(self.params[DspParam::LpGain].next_sample()); - self.mod_matrix.weights.y = Sample::splat(self.params[DspParam::BpGain].next_sample()); - self.mod_matrix.weights.z = Sample::splat(self.params[DspParam::HpGain].next_sample()); - - let drive = Sample::splat(db_to_gain_fast(self.params[DspParam::Drive].next_sample())); - let [out] = self.mod_matrix.process(self.filter.process([x * drive])); - [out / drive] - } - fn set_samplerate(&mut self, samplerate: f32) { for s in self.params.values_mut() { s.set_samplerate(samplerate); @@ -115,6 +100,23 @@ impl DSPProcess<1, 1> for DspInner { } } +impl DSPProcess<1, 1> for DspInner { + fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { + self.filter + .set_cutoff(Sample::splat(self.params[DspParam::Cutoff].next_sample())); + self.filter.set_r(Sample::splat( + 1.0 - self.params[DspParam::Resonance].next_sample(), + )); + self.mod_matrix.weights.x = Sample::splat(self.params[DspParam::LpGain].next_sample()); + self.mod_matrix.weights.y = Sample::splat(self.params[DspParam::BpGain].next_sample()); + self.mod_matrix.weights.z = Sample::splat(self.params[DspParam::HpGain].next_sample()); + + let drive = Sample::splat(db_to_gain_fast(self.params[DspParam::Drive].next_sample())); + let [out] = self.mod_matrix.process(self.filter.process([x * drive])); + [out / drive] + } +} + #[derive(Debug, Clone, Copy)] struct OpAmp(Clipper, Slew); diff --git a/examples/svfmixer/src/lib.rs b/examples/svfmixer/src/lib.rs index 2e52c3d..7169e4f 100644 --- a/examples/svfmixer/src/lib.rs +++ b/examples/svfmixer/src/lib.rs @@ -3,7 +3,8 @@ use std::sync::Arc; use nih_plug::prelude::*; use valib::contrib::nih_plug::{process_buffer_simd, NihParamsController}; -use valib::dsp::DSPBlock; +use valib::dsp::DSPMeta; + use valib::oversample::Oversample; use crate::dsp::{Dsp, DspInner, DspParam}; diff --git a/plugins/abrasive/src/filter.rs b/plugins/abrasive/src/filter.rs index fa52ef4..055e4a8 100644 --- a/plugins/abrasive/src/filter.rs +++ b/plugins/abrasive/src/filter.rs @@ -4,7 +4,7 @@ use nih_plug::prelude::*; use numeric_literals::replace_float_literals; use realfft::num_complex::Complex; -use valib::dsp::DSPProcess; +use valib::dsp::{DSPMeta, DSPProcess}; use valib::filters::svf::Svf; use valib::saturators::{Dynamic, Saturator}; use valib::simd::SimdValue; @@ -200,7 +200,7 @@ impl DspAnalysis<1, 1> for Filter { } } -impl DSPProcess<1, 1> for Filter { +impl DSPMeta for Filter { type Sample = Sample; fn latency(&self) -> usize { @@ -210,7 +210,9 @@ impl DSPProcess<1, 1> for Filter { fn reset(&mut self) { self.svf.reset(); } +} +impl DSPProcess<1, 1> for Filter { fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { let mut x = x[0]; self.update_coefficients_sample(); diff --git a/src/contrib/fundsp.rs b/src/contrib/fundsp.rs index c53fec9..e5b5cb3 100644 --- a/src/contrib/fundsp.rs +++ b/src/contrib/fundsp.rs @@ -5,7 +5,7 @@ //! //! Conversly, a [`DspNode`] struct is defined for wrapping [`DSPProcess`] implementations into usable `fundsp` nodes. -use crate::dsp::DSPProcess; +use crate::dsp::{DSPMeta, DSPProcess}; use fundsp::audionode::{AudioNode, Frame}; use fundsp::combinator::An; use fundsp::Float; @@ -13,12 +13,25 @@ use numeric_array::ArrayLength; use std::marker::PhantomData; use typenum::Unsigned; -impl DSPProcess<{ Node::Inputs::USIZE }, { Node::Outputs::USIZE }> for An +impl DSPMeta for An where Node::Sample: crate::Scalar, { type Sample = Node::Sample; + fn set_samplerate(&mut self, samplerate: f32) { + An::set_sample_rate(self, samplerate as _); + } + + fn reset(&mut self) { + An::reset(self); + } +} + +impl DSPProcess<{ Node::Inputs::USIZE }, { Node::Outputs::USIZE }> for An +where + Node::Sample: crate::Scalar, +{ fn process( &mut self, x: [Self::Sample; Node::Inputs::USIZE], diff --git a/src/contrib/nih_plug.rs b/src/contrib/nih_plug.rs index 7d5b973..5ec1e97 100644 --- a/src/contrib/nih_plug.rs +++ b/src/contrib/nih_plug.rs @@ -4,6 +4,7 @@ use std::fmt; use std::fmt::Formatter; use std::sync::Arc; +use crate::dsp::buffer::{AudioBuffer, AudioBufferMut}; use enum_map::{Enum, EnumArray, EnumMap}; use nih_plug::nih_debug_assert; use nih_plug::params::FloatParam; @@ -12,7 +13,7 @@ use nih_plug::{buffer::Buffer, params::Param}; use crate::dsp::parameter::{HasParameters, Parameter}; use crate::dsp::utils::{slice_to_mono_block, slice_to_mono_block_mut}; -use crate::dsp::DSPBlock; +use crate::dsp::DSPProcessBlock; use crate::Scalar; pub fn enum_int_param( @@ -181,10 +182,14 @@ pub fn process_buffer< dsp: &mut Dsp, buffer: &mut Buffer, ) where - Dsp: DSPBlock, + Dsp: DSPProcessBlock, { - assert!(CHANNELS <= buffer.channels()); - let mut input = [[T::zero(); CHANNELS]; MAX_BUF_SIZE]; + assert_eq!( + CHANNELS, + buffer.channels(), + "Channel mismatch between nih-plug channel count and requested buffer size" + ); + let mut input = AudioBuffer::const_new([[T::zero(); MAX_BUF_SIZE]; CHANNELS]); let mut output = input; let max_buffer_size = dsp .max_block_size() @@ -192,19 +197,21 @@ pub fn process_buffer< .unwrap_or(MAX_BUF_SIZE); for (_, mut block) in buffer.iter_blocks(max_buffer_size) { - let input = &mut input[..block.samples()]; - let output = &mut output[..block.samples()]; + let mut input = input.array_slice_mut(..block.samples()); + let mut output = output.array_slice_mut(..block.samples()); for (i, mut s) in block.iter_samples().enumerate() { + let mut frame = [T::zero(); CHANNELS]; for (ch, s) in s.iter_mut().map(|s| *s).enumerate() { - input[i][ch] = T::splat(s); + frame[ch] = T::splat(s); } + input.set_frame(i, frame); } - dsp.process_block(input, output); + dsp.process_block(input.as_ref(), output.as_mut()); for (i, mut s) in block.iter_samples().enumerate() { for (ch, s) in s.iter_mut().enumerate() { - *s = output[i][ch].extract(0); + *s = output.get_frame(i)[ch].extract(0); } } } @@ -224,7 +231,7 @@ pub fn process_buffer< /// panics if the scalar type has more channels than the buffer holds. pub fn process_buffer_simd< T: Scalar, - Dsp: DSPBlock<1, 1, Sample = T>, + Dsp: DSPProcessBlock<1, 1, Sample = T>, const MAX_BUF_SIZE: usize, >( dsp: &mut Dsp, @@ -232,26 +239,27 @@ pub fn process_buffer_simd< ) { let channels = buffer.channels(); assert!(T::lanes() <= channels); - let mut input = [T::from_f64(0.0); MAX_BUF_SIZE]; + let mut input = AudioBuffer::const_new([[T::from_f64(0.0); MAX_BUF_SIZE]]); let mut output = input; let max_buffer_size = dsp.max_block_size().unwrap_or(MAX_BUF_SIZE); nih_debug_assert!(max_buffer_size <= MAX_BUF_SIZE); for (_, mut block) in buffer.iter_blocks(max_buffer_size) { + let mut input = input.array_slice_mut(..block.samples()); + let mut output = output.array_slice_mut(..block.samples()); for (i, mut c) in block.iter_samples().enumerate() { - for ch in 0..channels { - input[i].replace(ch, c.get_mut(ch).copied().unwrap()); + let mut frame = T::zero(); + for (ch, s) in c.iter_mut().enumerate() { + frame.replace(ch, *s); } - output[i] = input[i]; + input.set_frame(i, [frame]); } + output.copy_from(input.as_ref()); - let input = &input[..block.samples()]; - let output = &mut output[..block.samples()]; - - dsp.process_block(slice_to_mono_block(input), slice_to_mono_block_mut(output)); + dsp.process_block(input.as_ref(), output.as_mut()); for (i, mut c) in block.iter_samples().enumerate() { for (ch, s) in c.iter_mut().enumerate() { - *s = output[i].extract(ch); + *s = output.get_frame(i)[0].extract(ch); } } } diff --git a/src/dsp/blocks.rs b/src/dsp/blocks.rs index 1129b28..b210ea1 100644 --- a/src/dsp/blocks.rs +++ b/src/dsp/blocks.rs @@ -17,7 +17,10 @@ impl DSPMeta for Bypass { type Sample = T; } -impl DSPProcess for Bypass { +impl DSPProcess for Bypass +where + Self: DSPMeta, +{ fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { x } @@ -45,7 +48,10 @@ impl DSPMeta for Integrator { } } -impl DSPProcess<1, 1> for Integrator { +impl DSPProcess<1, 1> for Integrator +where + Self: DSPMeta, +{ #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { let x2 = x[0] / 2.0; @@ -55,7 +61,10 @@ impl DSPProcess<1, 1> for Integrator { } } -impl DspAnalysis<1, 1> for Integrator { +impl DspAnalysis<1, 1> for Integrator +where + Self: DSPProcess<1, 1, Sample = T>, +{ #[replace_float_literals(Complex::from(T::from_f64(literal)))] fn h_z(&self, z: Complex) -> [[Complex; 1]; 1] { [[1. / 2. * (z + 1.) / (z - 1.)]] @@ -72,6 +81,7 @@ impl DSPMeta for Sum { impl DSPProcess for Sum where + Self: DSPMeta, T: Scalar, { fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; 1] { @@ -115,6 +125,7 @@ impl DSPMeta for P1 { impl DspAnalysis<1, 3> for P1 where + Self: DSPProcess<1, 3, Sample = T>, Self::Sample: nalgebra::RealField, { #[replace_float_literals(Complex::from_real(< T as Scalar >::from_f64(literal)))] @@ -145,7 +156,10 @@ impl P1 { } } -impl DSPProcess<1, 3> for P1 { +impl DSPProcess<1, 3> for P1 +where + Self: DSPMeta, +{ #[inline(always)] #[replace_float_literals(T::from_f64(literal))] fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 3] { @@ -238,7 +252,10 @@ impl DSPMeta for Series<[P; C]> { } } -impl, const N: usize, const C: usize> DSPProcess for Series<[P; C]> { +impl, const N: usize, const C: usize> DSPProcess for Series<[P; C]> +where + Self: DSPMeta, +{ fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { self.0.iter_mut().fold(x, |x, dsp| dsp.process(x)) } @@ -246,6 +263,7 @@ impl, const N: usize, const C: usize> DSPProcess for S impl DspAnalysis for Series<[P; C]> where + Self: DSPProcess, P: DspAnalysis, { fn h_z(&self, z: Complex) -> [[Complex; N]; N] { @@ -317,6 +335,7 @@ where impl DSPProcess for Series2 where + Self: DSPMeta, A: DSPProcess, B: DSPProcess, { @@ -327,15 +346,17 @@ where } } -impl DspAnalysis for Series> +impl DspAnalysis for Series2 where - P: DspAnalysis, + Self: DSPProcess, + A: DspAnalysis, + B: DspAnalysis, { - fn h_z(&self, z: Complex) -> [[Complex; N]; N] { - self.0.iter().fold([[Complex::one(); N]; N], |acc, f| { - let ret = f.h_z(z); - std::array::from_fn(|i| std::array::from_fn(|j| acc[i][j] * ret[i][j])) - }) + fn h_z(&self, z: Complex) -> [[Complex; O]; I] { + let ha = SMatrix::<_, J, I>::from(self.0.h_z(z)); + let hb = SMatrix::<_, O, J>::from(self.2.h_z(z)); + let res = hb * ha; + res.into() } } @@ -421,6 +442,8 @@ impl DSPMeta for Parallel<[P; C]> { impl, const I: usize, const O: usize, const N: usize> DSPProcess for Parallel<[P; N]> +where + Self: DSPMeta, { fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { self.0 @@ -434,6 +457,7 @@ impl, const I: usize, const O: usize, const N: usize> DSPPro impl DspAnalysis for Parallel<[P; N]> where + Self: DSPProcess, P: DspAnalysis, { fn h_z(&self, z: Complex) -> [[Complex; O]; I] { @@ -472,6 +496,7 @@ where impl DSPProcess for ModMatrix where + Self: DSPMeta, T: Scalar, { fn process(&mut self, x: [Self::Sample; I]) -> [Self::Sample; O] { @@ -515,6 +540,7 @@ where self.feedback.reset(); } } + impl DSPMeta for Feedback where FF: DSPProcess, @@ -535,7 +561,10 @@ where } } -impl, const N: usize> DSPProcess for Feedback { +impl, const N: usize> DSPProcess for Feedback +where + Self: DSPMeta, +{ fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { let x = std::array::from_fn(|i| self.memory[i] * self.mix[i] + x[i]); let y = self.feedforward.process(x); @@ -546,8 +575,9 @@ impl, const N: usize> DSPProcess for Feedback DSPProcess for Feedback where + Self: DSPMeta, FF: DSPProcess, - FB: DSPProcess::Sample>, + FB: DSPProcess, { fn process(&mut self, x: [Self::Sample; N]) -> [Self::Sample; N] { let fb = self.feedback.process(self.memory); diff --git a/src/dsp/buffer.rs b/src/dsp/buffer.rs index d9ec373..c2c60f3 100644 --- a/src/dsp/buffer.rs +++ b/src/dsp/buffer.rs @@ -1,11 +1,12 @@ use num_traits::Zero; -use std::ops::{Deref, DerefMut, Index, IndexMut}; +use std::collections::Bound; +use std::ops::{Deref, DerefMut, Index, IndexMut, Range, RangeBounds}; use crate::Scalar; /// AudioBuffer abstraction over containers of contiguous slices. This supports owned and non-owned, /// immutable and mutable slices. -#[derive(Debug, Clone)] +#[derive(Debug, Copy, Clone)] pub struct AudioBuffer { containers: [C; CHANNELS], inner_size: usize, @@ -60,8 +61,59 @@ impl AudioBuffer { } } +fn bounds_into_range( + bounds: impl RangeBounds + Sized, + max_bounds: Range, +) -> Range { + let start = match bounds.start_bound().cloned() { + Bound::Included(i) => i, + Bound::Excluded(i) => i - 1, + Bound::Unbounded => max_bounds.start, + } + .max(max_bounds.start); + let end = match bounds.end_bound().cloned() { + Bound::Included(i) => i + 1, + Bound::Excluded(i) => i, + Bound::Unbounded => max_bounds.end, + } + .min(max_bounds.end); + + let range = start..end; + range +} + +impl AudioBuffer<[T; LENGTH], CHANNELS> { + pub const fn const_new(containers: [[T; LENGTH]; CHANNELS]) -> Self { + Self { + containers, + inner_size: LENGTH, + } + } + + pub fn array_slice(&self, bounds: impl RangeBounds) -> AudioBufferRef { + let range = bounds_into_range(bounds, 0..self.inner_size); + AudioBuffer { + containers: std::array::from_fn(|i| &self.containers[i][range.clone()]), + inner_size: range.len(), + } + } + + pub fn array_slice_mut( + &mut self, + bounds: impl RangeBounds, + ) -> AudioBufferMut { + let range = bounds_into_range(bounds, 0..self.inner_size); + AudioBuffer { + containers: self.containers.each_mut().map(|i| &mut i[range.clone()]), + inner_size: range.len(), + } + } +} + impl, const CHANNELS: usize> AudioBuffer { - /// Create an audio buffer supported by the given containers of audio slices + /// Create an audio buffer supported by the given containers of audio slices. + /// + /// This method returns `None` when the channels have mismatching lengths. pub fn new(containers: [C; CHANNELS]) -> Option { if CHANNELS == 0 { return Some(Self { @@ -91,7 +143,7 @@ impl, const CHANNELS: usize> AudioBuffer std::array::from_fn(|ch| &self.containers[ch][index]) } - /// Get a multi-channel sample at the given index + /// Get a multi-channel sample at the given index. pub fn get_frame(&self, index: usize) -> [T; CHANNELS] where T: Clone, @@ -110,6 +162,14 @@ impl, const CHANNELS: usize> AudioBuffer inner_size: self.inner_size, } } + + pub fn slice(&self, bounds: impl RangeBounds) -> AudioBufferRef { + let range = bounds_into_range(bounds, 0..self.inner_size); + AudioBuffer { + inner_size: range.len(), + containers: std::array::from_fn(|i| &self.containers[i][range.clone()]), + } + } } impl, const CHANNELS: usize> AudioBuffer { @@ -162,15 +222,34 @@ impl, const CHANNELS: usize> AudioBuffer) -> AudioBufferMut { + let range = bounds_into_range(bounds, 0..self.inner_size); + AudioBuffer { + inner_size: range.len(), + containers: self.containers.each_mut().map(|i| &mut i[range.clone()]), + } + } } impl, const CHANNELS: usize> AudioBuffer { /// Copy a slice into a specific channel of this audio buffer. + /// + /// The buffers must match length, as reported by [`Self::samples()`]. pub fn copy_from_slice(&mut self, ch: usize, slice: &[T]) { self.containers[ch].copy_from_slice(slice); } - /// Set a multi-channel sample at the given index + /// Copy a buffer into this buffer. + /// + /// The buffers must match length, as reported by [`Self::samples()`]. + pub fn copy_from(&mut self, buffer: AudioBufferRef) { + for i in 0..CHANNELS { + self.containers[i].copy_from_slice(buffer.containers[i]); + } + } + + /// Set a multi-channel sample at the given index. pub fn set_frame(&mut self, index: usize, frame: [T; CHANNELS]) { for (channel, sample) in self.containers.iter_mut().zip(frame.iter().copied()) { channel[index] = sample; diff --git a/src/lib.rs b/src/lib.rs index 66dfc15..d431f7d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,3 +1,4 @@ +#![feature(array_methods)] #![cfg_attr(feature = "biquad-design", feature(iter_array_chunks))] #![cfg_attr(feature = "fundsp", feature(generic_const_exprs))] #![doc = include_str!("./README.md")] diff --git a/src/oversample/mod.rs b/src/oversample/mod.rs index 993a6dd..14f0d7b 100644 --- a/src/oversample/mod.rs +++ b/src/oversample/mod.rs @@ -1,13 +1,11 @@ +use az::UnwrappedAs; use std::ops::{Deref, DerefMut}; use nalgebra::Complex; use simba::simd::SimdComplexField; +use crate::dsp::buffer::{AudioBufferMut, AudioBufferRef}; use crate::dsp::parameter::{HasParameters, Parameter}; -use crate::dsp::{ - buffer::{AudioBufferMut, AudioBufferRef}, - parameter::{HasParameters, Parameter}, -}; use crate::dsp::{DSPMeta, DSPProcess}; use crate::saturators::Linear; use crate::Scalar; @@ -72,7 +70,11 @@ impl Oversample { self.post_filter.reset(); } - pub fn with_dsp>(self, samplerate: f32, mut dsp: P) -> Oversampled { + pub fn with_dsp>( + self, + samplerate: f32, + mut dsp: P, + ) -> Oversampled { let max_block_size = dsp.max_block_size().unwrap_or(self.os_buffer.len()); // Verify that we satisfy the inner DSPBlock instance's requirement on maximum block size assert!(self.os_buffer.len() <= max_block_size); @@ -168,7 +170,7 @@ where impl Oversampled where T: Scalar, - P: DSPBlock<1, 1, Sample = T>, + P: DSPProcessBlock<1, 1, Sample = T>, { pub fn set_oversampling_amount(&mut self, amt: usize) { assert!(amt > 1); @@ -177,17 +179,41 @@ where } } -impl DSPMeta for Oversampled { +impl> DSPMeta for Oversampled { type Sample = T; - fn process_block(&mut self, inputs: &[[Self::Sample; 1]], outputs: &mut [[Self::Sample; 1]]) { - let inputs = mono_block_to_slice(inputs); - let mut os_block = self.oversampling.oversample(inputs); - let inner_outputs = slice_to_mono_block_mut(&mut os_block); - self.staging_buffer[..inner_outputs.len()].copy_from_slice(inner_outputs); + fn set_samplerate(&mut self, samplerate: f32) { self.inner - .process_block(&self.staging_buffer[..inner_outputs.len()], inner_outputs); - os_block.finish(mono_block_to_slice_mut(outputs)); + .set_samplerate(self.os_factor() as f32 * samplerate); + } + + fn latency(&self) -> usize { + self.inner.latency() + } + + fn reset(&mut self) { + self.oversampling.reset(); + self.inner.reset(); + } +} + +impl DSPProcessBlock<1, 1> for Oversampled +where + Self: DSPMeta, + T: Scalar, + P: DSPProcessBlock<1, 1, Sample = T>, +{ + fn process_block(&mut self, inputs: AudioBufferRef, mut outputs: AudioBufferMut) { + let mut os_block = self.oversampling.oversample(inputs.get_channel(0)); + let mut inner_input = + AudioBufferMut::new([&mut self.staging_buffer[..os_block.len()]]).unwrap(); + inner_input.copy_from_slice(0, &os_block); + { + let mut inner_output = AudioBufferMut::new([&mut os_block]).unwrap(); + self.inner + .process_block(inner_input.as_ref(), inner_output.as_mut()); + } + os_block.finish(outputs.get_channel_mut(0)); } fn max_block_size(&self) -> Option { diff --git a/src/oversample/snapshots/valib__oversample__tests__os block.snap b/src/oversample/snapshots/valib__oversample__tests__os block.snap index 44c7bd9..a01a87b 100644 --- a/src/oversample/snapshots/valib__oversample__tests__os block.snap +++ b/src/oversample/snapshots/valib__oversample__tests__os block.snap @@ -129,7 +129,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.64 +0.641 0.621 0.602 0.582 @@ -257,7 +257,7 @@ expression: "&*osblock" -0.695 -0.677 -0.659 --0.64 +-0.641 -0.621 -0.602 -0.582 @@ -385,7 +385,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.64 +0.641 0.621 0.602 0.582 @@ -513,7 +513,7 @@ expression: "&*osblock" -0.695 -0.677 -0.659 --0.64 +-0.641 -0.621 -0.602 -0.582 @@ -641,7 +641,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.64 +0.641 0.621 0.602 0.582 @@ -769,7 +769,7 @@ expression: "&*osblock" -0.695 -0.677 -0.659 --0.64 +-0.641 -0.621 -0.602 -0.582 @@ -1153,7 +1153,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.64 +0.641 0.621 0.602 0.582 @@ -1409,7 +1409,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.64 +0.641 0.621 0.602 0.582 diff --git a/src/oversample/snapshots/valib__oversample__tests__post os.snap b/src/oversample/snapshots/valib__oversample__tests__post os.snap index d7ff072..6f55e61 100644 --- a/src/oversample/snapshots/valib__oversample__tests__post os.snap +++ b/src/oversample/snapshots/valib__oversample__tests__post os.snap @@ -39,7 +39,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -71,7 +71,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -103,7 +103,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -135,7 +135,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -167,7 +167,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -199,7 +199,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -231,7 +231,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -263,7 +263,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -295,7 +295,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -327,7 +327,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -359,7 +359,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -391,7 +391,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -423,7 +423,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 @@ -455,7 +455,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.751 +-0.752 -0.683 -0.608 -0.528 @@ -487,7 +487,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.751 +0.752 0.683 0.608 0.528 From 64ea3c6d9e4499419103bbb404d190750c0fce88 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 23 Mar 2024 11:16:51 +0100 Subject: [PATCH 08/12] fix: post-merge fixes --- examples/saturators/src/dsp.rs | 117 +++++++++++++++++++-------------- examples/saturators/src/lib.rs | 2 +- src/contrib/nih_plug.rs | 1 - src/dsp/mod.rs | 1 - src/dsp/utils.rs | 25 ------- src/saturators/adaa.rs | 36 ++++++---- 6 files changed, 90 insertions(+), 92 deletions(-) delete mode 100644 src/dsp/utils.rs diff --git a/examples/saturators/src/dsp.rs b/examples/saturators/src/dsp.rs index 00ef727..10564dc 100644 --- a/examples/saturators/src/dsp.rs +++ b/examples/saturators/src/dsp.rs @@ -1,9 +1,9 @@ use enum_map::{Enum, EnumArray}; use num_traits::Zero; +use valib::dsp::buffer::{AudioBufferMut, AudioBufferRef}; use valib::dsp::parameter::{HasParameters, Parameter, SmoothedParam}; -use valib::dsp::utils::slice_to_mono_block_mut; -use valib::dsp::{DSPBlock, DSP}; +use valib::dsp::{DSPMeta, DSPProcess, DSPProcessBlock}; use valib::filters::biquad::Biquad; use valib::oversample::{Oversample, Oversampled}; use valib::saturators::adaa::{Adaa, Antiderivative, Antiderivative2}; @@ -28,27 +28,29 @@ impl DcBlocker { } } -impl DSP<1, 1> for DcBlocker { +impl DSPMeta for DcBlocker { type Sample = T; - fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { - self.0.process(x) + fn set_samplerate(&mut self, samplerate: f32) { + self.0.set_samplerate(samplerate); + self.0.update_coefficients(&Biquad::highpass( + T::from_f64((Self::CUTOFF_HZ / samplerate) as f64), + T::from_f64(Self::Q as f64), + )); } - fn reset(&mut self) { - DSP::reset(&mut self.0); + fn latency(&self) -> usize { + self.0.latency() } - fn latency(&self) -> usize { - DSP::latency(&self.0) + fn reset(&mut self) { + self.0.reset(); } +} - fn set_samplerate(&mut self, samplerate: f32) { - DSP::set_samplerate(&mut self.0, samplerate); - self.0.update_coefficients(&Biquad::highpass( - T::from_f64((Self::CUTOFF_HZ / samplerate) as f64), - T::from_f64(Self::Q as f64), - )); +impl DSPProcess<1, 1> for DcBlocker { + fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { + self.0.process(x) } } @@ -71,9 +73,11 @@ enum DspSaturatorDirect { Diode(DiodeClipperModel), } -impl DSP<1, 1> for DspSaturatorDirect { +impl DSPMeta for DspSaturatorDirect { type Sample = Sample64; +} +impl DSPProcess<1, 1> for DspSaturatorDirect { fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { let y = match self { Self::HardClip => Clipper.saturate(x), @@ -145,9 +149,11 @@ enum DspSaturator { Adaa2(Adaa), } -impl DSP<1, 1> for DspSaturator { +impl DSPMeta for DspSaturator { type Sample = Sample64; +} +impl DSPProcess<1, 1> for DspSaturator { fn process(&mut self, x: [Self::Sample; 1]) -> [Self::Sample; 1] { match self { DspSaturator::Direct(sat) => sat.process(x), @@ -255,9 +261,24 @@ impl HasParameters for DspInner { } } -impl DSP<1, 1> for DspInner { +impl DSPMeta for DspInner { type Sample = Sample; + fn set_samplerate(&mut self, samplerate: f32) { + self.drive.set_samplerate(samplerate); + self.feedback.set_samplerate(samplerate); + } + + fn latency(&self) -> usize { + self.cur_saturator.latency() + } + + fn reset(&mut self) { + self.cur_saturator.reset(); + } +} + +impl DSPProcess<1, 1> for DspInner { fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { self.update_from_params(); @@ -269,19 +290,6 @@ impl DSP<1, 1> for DspInner { let yout = y / drive.simd_asinh(); [yout.cast()] } - - fn set_samplerate(&mut self, samplerate: f32) { - DSP::set_samplerate(&mut self.drive, samplerate); - DSP::set_samplerate(&mut self.feedback, samplerate); - } - - fn latency(&self) -> usize { - DSP::latency(&self.cur_saturator) - } - - fn reset(&mut self) { - DSP::reset(&mut self.cur_saturator); - } } #[derive(Debug, Copy, Clone, Eq, PartialEq)] @@ -327,39 +335,46 @@ pub struct Dsp { dc_blocker_staging: Box<[Sample]>, } -impl DSPBlock<1, 1> for Dsp { +impl DSPMeta for Dsp { type Sample = Sample; - fn process_block(&mut self, inputs: &[[Self::Sample; 1]], outputs: &mut [[Self::Sample; 1]]) { - let staging = slice_to_mono_block_mut(&mut self.dc_blocker_staging[..inputs.len()]); - - self.inner - .set_oversampling_amount(self.oversample_amount.get_value() as _); - self.inner.process_block(inputs, staging); - if self.use_dc_blocker.get_bool() { - self.dc_blocker.process_block(staging, outputs); - } else { - outputs.copy_from_slice(staging); - } - } - fn set_samplerate(&mut self, samplerate: f32) { - DSPBlock::set_samplerate(&mut self.inner, samplerate); - DSPBlock::set_samplerate(&mut self.dc_blocker, samplerate); + self.inner.set_samplerate(samplerate); + self.dc_blocker.set_samplerate(samplerate); } fn latency(&self) -> usize { - let inner_latency = DSPBlock::latency(&self.inner); + let inner_latency = self.inner.latency(); if self.use_dc_blocker.get_bool() { - inner_latency + DSPBlock::latency(&self.dc_blocker) + inner_latency + self.dc_blocker.latency() } else { inner_latency } } fn reset(&mut self) { - DSPBlock::reset(&mut self.inner); - DSPBlock::reset(&mut self.dc_blocker); + self.inner.reset(); + self.dc_blocker.reset(); + } +} + +impl DSPProcessBlock<1, 1> for Dsp { + fn process_block( + &mut self, + inputs: AudioBufferRef, + mut outputs: AudioBufferMut, + ) { + let mut staging = AudioBufferMut::from(&mut self.dc_blocker_staging[..inputs.samples()]); + + self.inner + .set_oversampling_amount(self.oversample_amount.get_value() as _); + self.inner.process_block(inputs, staging.as_mut()); + if self.use_dc_blocker.get_bool() { + self.dc_blocker + .process_block(staging.as_ref(), outputs.as_mut()); + } else { + outputs.copy_from(staging.as_ref()); + } } } diff --git a/examples/saturators/src/lib.rs b/examples/saturators/src/lib.rs index d81e622..c77ebd3 100644 --- a/examples/saturators/src/lib.rs +++ b/examples/saturators/src/lib.rs @@ -6,7 +6,7 @@ use nih_plug::util::db_to_gain; use dsp::Dsp; use valib::contrib::nih_plug::{process_buffer_simd, NihParamsController}; -use valib::dsp::DSPBlock; +use valib::dsp::DSPMeta; use crate::dsp::{create_dsp, DspInnerParams, DspParams, SaturatorType}; diff --git a/src/contrib/nih_plug.rs b/src/contrib/nih_plug.rs index 5ec1e97..9909817 100644 --- a/src/contrib/nih_plug.rs +++ b/src/contrib/nih_plug.rs @@ -12,7 +12,6 @@ use nih_plug::prelude::*; use nih_plug::{buffer::Buffer, params::Param}; use crate::dsp::parameter::{HasParameters, Parameter}; -use crate::dsp::utils::{slice_to_mono_block, slice_to_mono_block_mut}; use crate::dsp::DSPProcessBlock; use crate::Scalar; diff --git a/src/dsp/mod.rs b/src/dsp/mod.rs index e31ceb0..95acf19 100644 --- a/src/dsp/mod.rs +++ b/src/dsp/mod.rs @@ -12,7 +12,6 @@ pub mod analysis; pub mod blocks; pub mod buffer; pub mod parameter; -pub mod utils; /// Trait for interacting with a DSP algorithm, outside of processing. Shared by processors of both /// per-sample algorithms and block-based algorithms. diff --git a/src/dsp/utils.rs b/src/dsp/utils.rs deleted file mode 100644 index ae26ecb..0000000 --- a/src/dsp/utils.rs +++ /dev/null @@ -1,25 +0,0 @@ -//! Utilities for [`DSP`](crate::dsp::DSPProcess) implementors and users. - -/// Transmutes a mono block slice into a regular slice. -#[inline(always)] -pub fn mono_block_to_slice(inputs: [&[T]; 1]) -> &[T] { - inputs[0] -} - -/// Transmutes a mutable mono block slice into a regular slice. -#[inline(always)] -pub fn mono_block_to_slice_mut(inputs: [&mut [T]; 1]) -> &mut [T] { - inputs[0] -} - -/// Transmutes a slice into a mono block slice. -#[inline(always)] -pub fn slice_to_mono_block(inputs: &[T]) -> [&[T]; 1] { - [inputs] -} - -/// Transmutes a mutable slice into a mutable mono block slice. -#[inline(always)] -pub fn slice_to_mono_block_mut(inputs: &mut [T]) -> [&mut [T]; 1] { - [inputs] -} diff --git a/src/saturators/adaa.rs b/src/saturators/adaa.rs index ecf977e..6a0c5e2 100644 --- a/src/saturators/adaa.rs +++ b/src/saturators/adaa.rs @@ -2,7 +2,7 @@ use num_traits::Zero; use numeric_literals::replace_float_literals; use simba::simd::SimdBool; -use crate::dsp::DSP; +use crate::dsp::{DSPMeta, DSPProcess}; use crate::saturators::{Asinh, Blend, Clipper, Saturator, Tanh}; use crate::Scalar; @@ -193,9 +193,14 @@ impl + Saturator> Saturator for Adaa> DSP<1, 1> for Adaa { +impl DSPMeta for Adaa { type Sample = T; +} +impl> DSPProcess<1, 1> for Adaa +where + Self: DSPMeta, +{ fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { [self.next_sample(x)] } @@ -217,14 +222,9 @@ impl + Saturator> Saturator for Adaa> DSP<1, 1> for Adaa { +impl DSPMeta for Adaa { type Sample = T; - fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { - let y = self.next_sample(x); - [y] - } - fn latency(&self) -> usize { 1 } @@ -234,6 +234,16 @@ impl> DSP<1, 1> for Adaa { } } +impl> DSPProcess<1, 1> for Adaa +where + Self: DSPMeta, +{ + fn process(&mut self, [x]: [Self::Sample; 1]) -> [Self::Sample; 1] { + let y = self.next_sample(x); + [y] + } +} + #[cfg(test)] mod tests { use super::*; @@ -241,9 +251,9 @@ mod tests { use std::f64::consts::TAU; #[rstest] - #[case("tanh", Adaa::<_, Tanh, 1>::default())] - #[case("asinh", Adaa::<_, Asinh, 1>::default())] - #[case("clipper", Adaa::<_, Clipper, 1>::default())] + #[case("tanh", Adaa::< _, Tanh, 1 >::default())] + #[case("asinh", Adaa::< _, Asinh, 1 >::default())] + #[case("clipper", Adaa::< _, Clipper, 1 >::default())] fn test_adaa1 + Saturator>( #[case] name: &str, #[case] mut adaa: Adaa, @@ -263,8 +273,8 @@ mod tests { } #[rstest] - #[case("clipper", Adaa::<_, Clipper, 2>::default())] - #[case("asinh", Adaa::<_, Asinh, 2>::default())] + #[case("clipper", Adaa::< _, Clipper, 2 >::default())] + #[case("asinh", Adaa::< _, Asinh, 2 >::default())] fn test_adaa2 + Saturator>( #[case] name: &str, #[case] mut adaa: Adaa, From 2d63430d5a37b4c626bc22a62d18f50c9bfc11cf Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 23 Mar 2024 11:24:09 +0100 Subject: [PATCH 09/12] fix(abrasive): post-merge fixes --- plugins/abrasive/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/abrasive/src/lib.rs b/plugins/abrasive/src/lib.rs index f7f5b0a..44c8368 100644 --- a/plugins/abrasive/src/lib.rs +++ b/plugins/abrasive/src/lib.rs @@ -13,7 +13,7 @@ use std::sync::{Arc, Mutex}; use valib::dsp::blocks::Series; use valib::dsp::buffer::AudioBuffer; -use valib::dsp::DSPProcessBlock; +use valib::dsp::{DSPMeta, DSPProcessBlock}; use valib::simd::{AutoF32x2, SimdValue}; pub mod editor; From c6c641506098a3039308d24a2a404fbd01f10ed6 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 23 Mar 2024 14:21:37 +0100 Subject: [PATCH 10/12] test: fix snapshots --- .../valib__oversample__tests__os block.snap | 16 +++++----- .../valib__oversample__tests__post os.snap | 30 +++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/oversample/snapshots/valib__oversample__tests__os block.snap b/src/oversample/snapshots/valib__oversample__tests__os block.snap index a01a87b..44c7bd9 100644 --- a/src/oversample/snapshots/valib__oversample__tests__os block.snap +++ b/src/oversample/snapshots/valib__oversample__tests__os block.snap @@ -129,7 +129,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.641 +0.64 0.621 0.602 0.582 @@ -257,7 +257,7 @@ expression: "&*osblock" -0.695 -0.677 -0.659 --0.641 +-0.64 -0.621 -0.602 -0.582 @@ -385,7 +385,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.641 +0.64 0.621 0.602 0.582 @@ -513,7 +513,7 @@ expression: "&*osblock" -0.695 -0.677 -0.659 --0.641 +-0.64 -0.621 -0.602 -0.582 @@ -641,7 +641,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.641 +0.64 0.621 0.602 0.582 @@ -769,7 +769,7 @@ expression: "&*osblock" -0.695 -0.677 -0.659 --0.641 +-0.64 -0.621 -0.602 -0.582 @@ -1153,7 +1153,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.641 +0.64 0.621 0.602 0.582 @@ -1409,7 +1409,7 @@ expression: "&*osblock" 0.695 0.677 0.659 -0.641 +0.64 0.621 0.602 0.582 diff --git a/src/oversample/snapshots/valib__oversample__tests__post os.snap b/src/oversample/snapshots/valib__oversample__tests__post os.snap index 6f55e61..d7ff072 100644 --- a/src/oversample/snapshots/valib__oversample__tests__post os.snap +++ b/src/oversample/snapshots/valib__oversample__tests__post os.snap @@ -39,7 +39,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -71,7 +71,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -103,7 +103,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -135,7 +135,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -167,7 +167,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -199,7 +199,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -231,7 +231,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -263,7 +263,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -295,7 +295,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -327,7 +327,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -359,7 +359,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -391,7 +391,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -423,7 +423,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 @@ -455,7 +455,7 @@ expression: "&out as &[_]" -0.911 -0.866 -0.813 --0.752 +-0.751 -0.683 -0.608 -0.528 @@ -487,7 +487,7 @@ expression: "&out as &[_]" 0.911 0.866 0.813 -0.752 +0.751 0.683 0.608 0.528 From 0db83a439d219f7e169077da34899ca88c6ddf3c Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 23 Mar 2024 18:23:11 +0100 Subject: [PATCH 11/12] perf: increase bandwidth of oversampling filter --- src/oversample/mod.rs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/oversample/mod.rs b/src/oversample/mod.rs index 00d547e..8eeb012 100644 --- a/src/oversample/mod.rs +++ b/src/oversample/mod.rs @@ -1,4 +1,5 @@ use az::UnwrappedAs; +use std::f64::consts::FRAC_1_SQRT_2; use std::ops::{Deref, DerefMut}; use nalgebra::Complex; @@ -32,17 +33,14 @@ impl Oversample { { assert!(os_factor > 1); let os_buffer = vec![T::zero(); max_block_size * os_factor].into_boxed_slice(); - let fc = f64::recip(2.0 * os_factor as f64); - let filter = Biquad::lowpass( - T::from_f64(fc), - T::from_f64(std::f64::consts::FRAC_1_SQRT_2), - ); + let fc = 1.5 * f64::recip(2.0 * os_factor as f64); + let filter = Biquad::lowpass(T::from_f64(fc), T::from_f64(FRAC_1_SQRT_2)); let filters = Series([filter; CASCADE]); Self { max_factor: os_factor, os_factor, os_buffer, - pre_filter: filters.clone(), + pre_filter: filters, post_filter: filters, } } @@ -51,8 +49,8 @@ impl Oversample { assert!(amt <= self.max_factor); self.os_factor = amt; let new_biquad = Biquad::lowpass( - T::from_f64(2.0 * amt as f64).simd_recip(), - T::from_f64(0.707), + T::from_f64(2.0 * amt as f64).simd_recip() * T::from_f64(1.5), + T::from_f64(FRAC_1_SQRT_2), ); for filt in self .pre_filter @@ -188,7 +186,7 @@ where { pub fn set_oversampling_amount(&mut self, amt: usize) { assert!(amt > 1); - self.oversampling.os_factor = amt; + self.oversampling.set_oversampling_amount(amt); self.set_samplerate(self.samplerate); } } From 9b40487d27ef164caf9041c3502e6c36da035002 Mon Sep 17 00:00:00 2001 From: Nathan Graule Date: Sat, 23 Mar 2024 18:32:08 +0100 Subject: [PATCH 12/12] test: fix snapshots for oversampling --- .../valib__oversample__tests__os block.snap | 3902 ++++++++--------- ...rsample__tests__oversampled_dsp_block.snap | 108 +- .../valib__oversample__tests__post os.snap | 974 ++-- 3 files changed, 2492 insertions(+), 2492 deletions(-) diff --git a/src/oversample/snapshots/valib__oversample__tests__os block.snap b/src/oversample/snapshots/valib__oversample__tests__os block.snap index 44c7bd9..fca4c3d 100644 --- a/src/oversample/snapshots/valib__oversample__tests__os block.snap +++ b/src/oversample/snapshots/valib__oversample__tests__os block.snap @@ -16,2037 +16,2037 @@ expression: "&*osblock" 0.0 0.0 0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 -0.0 0.001 0.002 -0.003 -0.006 +0.005 0.01 -0.017 -0.026 -0.038 -0.054 -0.073 -0.096 -0.122 -0.15 -0.178 -0.207 -0.234 -0.261 -0.286 -0.31 -0.333 -0.355 -0.377 -0.398 -0.42 -0.442 -0.464 -0.485 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +0.02 +0.036 +0.058 +0.086 +0.117 +0.148 +0.177 +0.202 +0.224 +0.245 +0.267 +0.29 +0.314 +0.338 +0.362 +0.385 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.575 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.575 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.575 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.575 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.575 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.575 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.575 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.575 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.575 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.574 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.574 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.574 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.575 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 --0.621 --0.602 --0.582 --0.562 --0.542 --0.521 --0.5 --0.478 --0.457 --0.435 --0.412 --0.39 --0.367 --0.344 --0.321 --0.298 --0.274 --0.251 --0.227 --0.203 --0.179 --0.155 --0.13 --0.106 --0.081 --0.057 --0.032 --0.008 -0.017 -0.041 -0.066 -0.09 -0.115 -0.139 -0.163 -0.187 -0.211 -0.235 -0.259 -0.283 -0.306 -0.329 -0.352 -0.375 -0.398 -0.42 -0.443 -0.464 -0.486 -0.507 -0.528 -0.549 -0.569 -0.589 -0.609 -0.628 -0.647 -0.666 -0.684 -0.701 -0.719 -0.736 -0.752 -0.768 -0.783 -0.798 -0.813 -0.827 -0.841 -0.854 -0.866 -0.878 -0.89 -0.901 -0.911 -0.921 -0.93 -0.939 -0.947 -0.955 -0.962 -0.968 -0.974 -0.979 -0.984 -0.988 -0.991 -0.994 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.574 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 +-0.404 +-0.381 +-0.358 +-0.335 +-0.312 +-0.289 +-0.265 +-0.241 +-0.218 +-0.194 +-0.169 +-0.145 +-0.121 +-0.096 +-0.072 +-0.047 +-0.023 +0.002 +0.026 +0.051 +0.075 +0.1 +0.124 +0.148 +0.173 +0.197 +0.221 +0.245 +0.268 +0.292 +0.315 +0.338 +0.361 +0.384 +0.407 +0.429 +0.451 +0.473 +0.494 +0.515 +0.536 +0.557 +0.577 +0.597 +0.616 +0.636 +0.654 +0.673 +0.691 +0.708 +0.725 +0.742 +0.758 +0.774 +0.789 +0.804 +0.819 +0.832 +0.846 +0.859 +0.871 +0.883 +0.894 +0.905 +0.915 +0.924 +0.934 +0.942 +0.95 +0.957 +0.964 +0.97 +0.976 +0.981 +0.986 +0.989 +0.993 +0.995 0.997 -0.998 0.999 1.0 1.0 +1.0 0.999 -0.998 -0.996 -0.993 -0.99 -0.987 -0.982 -0.977 -0.972 -0.966 -0.959 -0.952 -0.944 -0.936 -0.927 -0.917 -0.907 -0.897 -0.886 -0.874 -0.862 -0.849 -0.836 -0.822 -0.808 -0.793 -0.778 -0.762 -0.746 -0.73 -0.713 -0.695 -0.677 -0.659 -0.64 -0.621 -0.602 -0.582 -0.562 -0.542 -0.521 -0.5 -0.478 -0.457 -0.435 -0.412 -0.39 -0.367 -0.344 -0.321 -0.298 -0.274 -0.251 -0.227 -0.203 -0.179 -0.155 -0.13 -0.106 -0.081 -0.057 -0.032 -0.008 --0.017 --0.041 --0.066 --0.09 --0.115 --0.139 --0.163 --0.187 --0.211 --0.235 --0.259 --0.283 --0.306 --0.329 --0.352 --0.375 --0.398 --0.42 --0.443 --0.464 --0.486 --0.507 --0.528 --0.549 --0.569 --0.589 --0.609 --0.628 --0.647 --0.666 --0.684 --0.701 --0.719 --0.736 --0.752 --0.768 --0.783 --0.798 --0.813 --0.827 --0.841 --0.854 --0.866 --0.878 --0.89 --0.901 --0.911 --0.921 --0.93 --0.939 --0.947 --0.955 --0.962 --0.968 --0.974 --0.979 --0.984 --0.988 --0.991 --0.994 +0.997 +0.995 +0.992 +0.989 +0.985 +0.98 +0.975 +0.97 +0.963 +0.956 +0.949 +0.941 +0.932 +0.923 +0.914 +0.903 +0.893 +0.881 +0.869 +0.857 +0.844 +0.831 +0.817 +0.802 +0.787 +0.772 +0.756 +0.74 +0.723 +0.706 +0.688 +0.67 +0.652 +0.633 +0.614 +0.594 +0.574 +0.554 +0.534 +0.513 +0.492 +0.47 +0.448 +0.426 +0.404 +0.381 +0.358 +0.335 +0.312 +0.289 +0.265 +0.241 +0.218 +0.194 +0.169 +0.145 +0.121 +0.096 +0.072 +0.047 +0.023 +-0.002 +-0.026 +-0.051 +-0.075 +-0.1 +-0.124 +-0.148 +-0.173 +-0.197 +-0.221 +-0.245 +-0.268 +-0.292 +-0.315 +-0.338 +-0.361 +-0.384 +-0.407 +-0.429 +-0.451 +-0.473 +-0.494 +-0.515 +-0.536 +-0.557 +-0.577 +-0.597 +-0.616 +-0.636 +-0.654 +-0.673 +-0.691 +-0.708 +-0.725 +-0.742 +-0.758 +-0.774 +-0.789 +-0.804 +-0.819 +-0.832 +-0.846 +-0.859 +-0.871 +-0.883 +-0.894 +-0.905 +-0.915 +-0.924 +-0.934 +-0.942 +-0.95 +-0.957 +-0.964 +-0.97 +-0.976 +-0.981 +-0.986 +-0.989 +-0.993 +-0.995 -0.997 --0.998 -0.999 -1.0 -1.0 +-1.0 -0.999 --0.998 --0.996 --0.993 --0.99 --0.987 --0.982 --0.977 --0.972 --0.966 --0.959 --0.952 --0.944 --0.936 --0.927 --0.917 --0.907 --0.897 --0.886 --0.874 --0.862 --0.849 --0.836 --0.822 --0.808 --0.793 --0.778 --0.762 --0.746 --0.73 --0.713 --0.695 --0.677 --0.659 --0.64 +-0.997 +-0.995 +-0.992 +-0.989 +-0.985 +-0.98 +-0.975 +-0.97 +-0.963 +-0.956 +-0.949 +-0.941 +-0.932 +-0.923 +-0.914 +-0.903 +-0.893 +-0.881 +-0.869 +-0.857 +-0.844 +-0.831 +-0.817 +-0.802 +-0.787 +-0.772 +-0.756 +-0.74 +-0.723 +-0.706 +-0.688 +-0.67 +-0.652 +-0.633 +-0.614 +-0.594 +-0.574 +-0.554 +-0.534 +-0.513 +-0.492 +-0.47 +-0.448 +-0.426 diff --git a/src/oversample/snapshots/valib__oversample__tests__oversampled_dsp_block.snap b/src/oversample/snapshots/valib__oversample__tests__oversampled_dsp_block.snap index 05ade91..77e8149 100644 --- a/src/oversample/snapshots/valib__oversample__tests__oversampled_dsp_block.snap +++ b/src/oversample/snapshots/valib__oversample__tests__oversampled_dsp_block.snap @@ -1,68 +1,68 @@ --- source: src/oversample/mod.rs -expression: "&output as &[_]" +expression: output.get_channel(0) --- -0.0 -0.0 -0.0 --0.0 --0.001 --0.015 --0.133 --0.52 --1.014 --1.135 --0.996 --0.976 --1.006 --1.002 --0.999 +-0.019 +-0.298 +-1.034 +-1.064 +-0.97 +-1.009 +-0.998 +-1.0 +-1.0 +-1.0 -1.0 -1.0 --0.997 --0.943 --0.599 -0.306 -1.18 -1.209 -0.951 -0.971 -1.014 +-0.998 +-0.912 +-0.07 +1.28 +0.991 +0.99 +1.002 1.0 -0.999 1.0 -0.999 -0.984 -0.832 -0.208 --0.821 --1.296 --1.054 --0.936 --1.004 --1.007 --0.997 +1.0 +1.0 +1.0 +1.0 +0.985 +0.653 +-0.728 +-1.269 +-0.902 +-1.029 +-0.993 +-1.002 -1.0 -1.0 --0.997 --0.943 --0.599 -0.306 -1.18 -1.209 -0.951 -0.971 -1.014 +-1.0 +-1.0 +-0.998 +-0.912 +-0.07 +1.28 +0.991 +0.99 +1.002 +1.0 +1.0 +1.0 1.0 -0.999 1.0 -0.999 -0.984 -0.832 -0.208 --0.821 --1.296 --1.054 --0.936 --1.004 --1.007 +1.0 +0.985 +0.653 +-0.728 +-1.269 +-0.902 +-1.029 +-0.993 +-1.002 +-1.0 +-1.0 +-1.0 diff --git a/src/oversample/snapshots/valib__oversample__tests__post os.snap b/src/oversample/snapshots/valib__oversample__tests__post os.snap index d7ff072..1a97ab3 100644 --- a/src/oversample/snapshots/valib__oversample__tests__post os.snap +++ b/src/oversample/snapshots/valib__oversample__tests__post os.snap @@ -10,507 +10,507 @@ expression: "&out as &[_]" 0.0 0.0 0.0 -0.0 -0.0 -0.0 -0.0 -0.002 -0.01 +0.005 0.04 -0.109 -0.214 -0.325 -0.418 -0.5 -0.582 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +0.139 +0.253 +0.338 +0.431 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 +0.999 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.999 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 +0.999 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 +-0.999 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.999 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 +-0.999 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.999 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 +-0.999 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 +0.999 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.999 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 +0.999 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.999 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 +0.999 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 +-0.999 -0.999 --0.991 --0.974 --0.947 --0.911 --0.866 --0.813 --0.751 --0.683 --0.608 --0.528 --0.442 --0.352 --0.258 --0.162 --0.065 -0.033 -0.131 -0.228 -0.322 -0.413 -0.5 -0.583 -0.66 -0.73 -0.794 -0.849 -0.897 -0.936 -0.966 -0.987 -0.998 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801 +-0.739 +-0.669 +-0.593 +-0.511 +-0.425 +-0.334 +-0.24 +-0.144 +-0.046 +0.052 +0.15 +0.246 +0.34 +0.43 +0.517 +0.598 +0.674 +0.743 +0.805 +0.859 +0.905 +0.943 +0.971 +0.99 0.999 -0.991 -0.974 -0.947 -0.911 -0.866 -0.813 -0.751 -0.683 -0.608 -0.528 -0.442 -0.352 -0.258 -0.162 -0.065 --0.033 --0.131 --0.228 --0.322 --0.413 --0.5 --0.583 --0.66 --0.73 --0.794 --0.849 --0.897 --0.936 --0.966 --0.987 --0.998 +0.999 +0.989 +0.969 +0.94 +0.903 +0.856 +0.801 +0.739 +0.669 +0.593 +0.511 +0.425 +0.334 +0.24 +0.144 +0.046 +-0.052 +-0.15 +-0.246 +-0.34 +-0.43 +-0.517 +-0.598 +-0.674 +-0.743 +-0.805 +-0.859 +-0.905 +-0.943 +-0.971 +-0.99 +-0.999 -0.999 --0.991 +-0.989 +-0.969 +-0.94 +-0.903 +-0.856 +-0.801