diff --git a/Cargo.lock b/Cargo.lock index 91f43cd..1a71041 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -334,15 +334,6 @@ version = "0.4.25" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04cbf5b083de1c7e0222a7a51dbfdba1cbe1c6ab0b15e29fff3f6c077fd9cd9f" -[[package]] -name = "mach2" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "19b955cdeb2a02b9117f121ce63aa52d08ade45de53e48fe6a38b39c10f6f709" -dependencies = [ - "libc", -] - [[package]] name = "matrixmultiply" version = "0.3.9" @@ -621,7 +612,6 @@ dependencies = [ "num-complex", "num-traits", "sameplace", - "slice-ring-buffer", ] [[package]] @@ -662,17 +652,6 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56199f7ddabf13fe5074ce809e7d3f42b42ae711800501b5b16ea82ad029c39d" -[[package]] -name = "slice-ring-buffer" -version = "0.3.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "84ae312bda09b2368f79f985fdb4df4a0b5cbc75546b511303972d195f8c27d6" -dependencies = [ - "libc", - "mach2", - "winapi", -] - [[package]] name = "strsim" version = "0.11.1" @@ -834,22 +813,6 @@ dependencies = [ "safe_arch", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - [[package]] name = "winapi-util" version = "0.1.9" @@ -859,12 +822,6 @@ dependencies = [ "windows-sys 0.59.0", ] -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-core" version = "0.52.0" diff --git a/crates/sameold/Cargo.toml b/crates/sameold/Cargo.toml index a220bd8..f91d941 100644 --- a/crates/sameold/Cargo.toml +++ b/crates/sameold/Cargo.toml @@ -18,7 +18,6 @@ log = "0.4" nalgebra = "^0.33.2" num-complex = "^0.4" num-traits = "^0.2" -slice-ring-buffer = "^0.3" [dev-dependencies] assert_approx_eq = "1.1.0" diff --git a/crates/sameold/src/receiver/dcblock.rs b/crates/sameold/src/receiver/dcblock.rs index f572cec..302d6b2 100644 --- a/crates/sameold/src/receiver/dcblock.rs +++ b/crates/sameold/src/receiver/dcblock.rs @@ -168,7 +168,7 @@ mod tests { output_history.push_scalar(uut.filter(100.0f32 + clk)); clk = -1.0 * clk; } - assert_approx_eq!(output_history.as_slice()[0], 1.0f32, 1.0e-2); - assert_approx_eq!(output_history.as_slice()[1], -1.0f32, 1.0e-2); + assert_approx_eq!(output_history.inner()[0], 1.0f32, 1.0e-2); + assert_approx_eq!(output_history.inner()[1], -1.0f32, 1.0e-2); } } diff --git a/crates/sameold/src/receiver/demod.rs b/crates/sameold/src/receiver/demod.rs index 3fe717f..4825ca8 100644 --- a/crates/sameold/src/receiver/demod.rs +++ b/crates/sameold/src/receiver/demod.rs @@ -155,9 +155,8 @@ impl FskDemod { // * `< 0` for space fn demod_now(&self) -> f32 { // matched filter - let window = self.window_input.as_slice(); - let mark = self.coeff_mark.filter(window); - let space = self.coeff_space.filter(window); + let mark = self.coeff_mark.filter(&self.window_input); + let space = self.coeff_space.filter(&self.window_input); // non-coherently sum matched filter powers to obtain // the symbol estimate diff --git a/crates/sameold/src/receiver/equalize.rs b/crates/sameold/src/receiver/equalize.rs index 9c7d128..8abc8ea 100644 --- a/crates/sameold/src/receiver/equalize.rs +++ b/crates/sameold/src/receiver/equalize.rs @@ -133,6 +133,9 @@ impl Equalizer { let feedforward_wind = Window::new(nfeedforward); let feedback_wind = Window::new(nfeedback); + assert_eq!(feedforward_coeff.inner().len(), feedforward_wind.len()); + assert_eq!(feedback_coeff.inner().len(), feedback_wind.len()); + Self { relaxation, regularization, @@ -314,14 +317,16 @@ impl Equalizer { self.relaxation, self.regularization, error, - self.feedforward_wind.as_ref(), + &self.feedforward_wind, self.feedforward_coeff.as_mut(), ); + + assert_eq!(self.feedback_wind.inner().len(), self.feedback_coeff.len()); nlms_update( self.relaxation, self.regularization, -error, - self.feedback_wind.as_ref(), + &self.feedback_wind, self.feedback_coeff.as_mut(), ); } @@ -346,17 +351,14 @@ enum EqualizerState { // variable gain. The gain is computed based on the power // of the input. The given `filter` taps are updated based // on the input samples in `window`. -fn nlms_update( - relaxation: f32, - regularization: f32, - error: f32, - window: &[f32], - filter: &mut [f32], -) { - assert_eq!(window.len(), filter.len()); - - let gain = nlms_gain(relaxation, regularization, window); - for (coeff, data) in filter.iter_mut().zip(window.iter()) { +fn nlms_update(relaxation: f32, regularization: f32, error: f32, window: W, filter: &mut [f32]) +where + W: IntoIterator, + W::IntoIter: DoubleEndedIterator + Clone, +{ + let window = window.into_iter(); + let gain = nlms_gain(relaxation, regularization, window.clone()); + for (coeff, data) in filter.iter_mut().zip(window.rev()) { *coeff += gain * error * data; } } @@ -371,7 +373,10 @@ fn nlms_update( // // where `norm(x, 2)` is the L² norm of `x` #[inline] -fn nlms_gain(relaxation: f32, regularization: f32, window: &[f32]) -> f32 { +fn nlms_gain<'a, W>(relaxation: f32, regularization: f32, window: W) -> f32 +where + W: IntoIterator, +{ let mut sumsq = 0.0f32; for w in window { sumsq += w * w; @@ -473,7 +478,7 @@ mod tests { RELAXATION, REGULARIZATION, err, - inverse_wind.as_ref(), + &inverse_wind, inverse_coeff.as_mut(), ); } diff --git a/crates/sameold/src/receiver/filter.rs b/crates/sameold/src/receiver/filter.rs index 1477509..336e9bc 100644 --- a/crates/sameold/src/receiver/filter.rs +++ b/crates/sameold/src/receiver/filter.rs @@ -62,12 +62,12 @@ //! wind.push(&[1.0f32, 2.0f32, 3.0f32, 4.0f32]); //! ``` +use std::collections::VecDeque; use std::convert::AsRef; use nalgebra::base::Scalar; use nalgebra::DVector; use num_traits::{One, Zero}; -use slice_ring_buffer::SliceRingBuffer; /// FIR filter coefficients #[derive(Debug, Clone, PartialEq, PartialOrd, Eq)] @@ -85,18 +85,12 @@ where /// Creates FIR filter coefficients with the specified impulse /// response `h`. The coefficients `h` use the same representation /// as GNU Octave's `filter()` function. - /// - /// Internally, the coefficients are stored reversed. This improves - /// performance against most types of queues for the input signal. pub fn from_slice(h: S) -> Self where S: AsRef<[T]>, { let inp = h.as_ref(); - FilterCoeff(DVector::from_iterator( - inp.len(), - inp.iter().rev().map(|d| *d), - )) + FilterCoeff(DVector::from_iterator(inp.len(), inp.iter().copied())) } /// Create an identity filter @@ -107,7 +101,7 @@ where len, std::iter::repeat(T::zero()).take(len), )); - out.0[len - 1] = T::one(); + out.0[0] = T::one(); out } @@ -116,25 +110,26 @@ where self.0.len() } - /// Perform FIR filtering with the given sample history slice + /// Perform FIR filtering with the given sample history /// /// Computes the current output sample of the filter assuming - /// the given `history`. `history` should be a slice where - /// `history[N-1]` is the most recent sample and `history[0]` - /// is the least recent/oldest sample. + /// the given `history`. `history` must be a + /// `DoubledEndedIterator` which outputs the oldest sample + /// first and the newest sample last. The newest sample is + /// used for feedforward lag 0. `history` SHOULD contain at + /// least `self.len()` samples, but no error is raised if it + /// is shorter. /// - /// The caller should maintain a deque of history. New samples - /// should be pushed to the end of the queue, and old samples - /// should age off the head of the queue. The queue *should* - /// contain `self.len()` samples, but it is not an error if - /// it contains more or less. - pub fn filter(&self, history: I) -> Out + /// The caller is encouraged to use a deque for `history`, + /// but this is not enforced. + pub fn filter(&self, history: W) -> Out where - I: AsRef<[In]>, + W: IntoIterator, + W::IntoIter: DoubleEndedIterator, In: Copy + Scalar + std::ops::Mul, Out: Copy + Scalar + Zero + std::ops::AddAssign, { - multiply_accumulate(history.as_ref(), self.as_ref()) + multiply_accumulate(history, self.as_ref()) } /// Reset to identity filter @@ -142,42 +137,31 @@ where /// The filter coefficients are reset to a "no-op" identity /// filter. pub fn identity(&mut self) { - let len = self.0.len(); for coeff in self.0.iter_mut() { *coeff = T::zero(); } - self.0[len - 1] = T::one(); + self.0[0] = T::one(); } /// Return filter coefficients as slice - /// - /// The filter coefficients are in *reverse* order - /// from their Octave representation. #[inline] pub fn as_slice(&self) -> &[T] { self.0.as_slice() } /// Return filter coefficients as mutable slice - /// - /// The filter coefficients are in *reverse* order - /// from their Octave representation. #[inline] pub fn as_mut_slice(&mut self) -> &mut [T] { self.0.as_mut_slice() } /// Obtain filter coefficients - /// - /// The coefficients are output in reverse order. #[inline] pub fn inner(&self) -> &DVector { &self.0 } /// Obtain filter coefficients (mutable) - /// - /// The coefficients are output in reverse order. #[inline] pub fn inner_mut(&mut self) -> &mut DVector { &mut self.0 @@ -231,7 +215,7 @@ where /// Implements a fixed-size lookback window for FIR filters /// or other purposes. #[derive(Clone, Debug)] -pub struct Window(SliceRingBuffer) +pub struct Window(VecDeque) where T: Copy + Scalar + Zero; @@ -242,27 +226,19 @@ where { /// Create empty window, filling it with zeros /// - /// Creates a new `Window` with the given `len`gth, with - /// `len > 0`. + /// Creates a new `Window` with the given `len`gth pub fn new(len: usize) -> Self { - assert!(len > 0); - - let mut out = Self(SliceRingBuffer::with_capacity(len)); - for _i in 0..len { - out.0.push_front(T::zero()); - } - assert_eq!(len, out.0.len()); - out + let mut q = VecDeque::with_capacity(len); + q.resize(len, T::zero()); + Self(q) } /// Reset to zero initial conditions /// /// Clear the window, filling it with zeros pub fn reset(&mut self) { - let len = self.0.len(); - self.0.clear(); - for _i in 0..len { - self.0.push_front(T::zero()); + for s in &mut self.0 { + *s = T::zero() } } @@ -275,7 +251,7 @@ where /// /// Appends the `input` slice to the right side of the Window. /// The last sample of `input` becomes the right-most / most recent - /// sample of the Window slice. + /// sample of the Window. /// /// If the length of `input` exceeds the length of the Window, /// then the right-most chunk of `input` will be taken. @@ -295,35 +271,42 @@ where std::mem::drop(self.0.drain(0..input.len())); // add new - self.0.extend_from_slice(input.as_ref()); + self.0.extend(input.as_ref()); } /// Append a scalar to the sample window /// /// Appends the `input` scalar to the right side of the Window. - /// It becomes the last / most recent sample of the Window - /// slice. Returns the sample that was formerly the oldest + /// It becomes the last / most recent sample of Window. + /// Returns the sample that was formerly the oldest /// sample in the Window. #[inline] pub fn push_scalar(&mut self, input: T) -> T { - let out = self.0.pop_front().unwrap(); + let out = self.0.pop_front().unwrap_or(T::zero()); self.0.push_back(input); out } - /// Obtain the inner SliceRingBuffer - pub fn inner(&self) -> &SliceRingBuffer { - &self.0 + /// Iterator over window contents + /// + /// The iterator outputs the least recent sample first. + /// The most recent sample is output last. + pub fn iter(&self) -> <&Window as IntoIterator>::IntoIter { + self.into_iter() } - /// Obtain current window contents, as a slice + /// Convert window contents to a vector /// - /// The zeroth sample of the slice is the least recent - /// sample in the window. The last sample of the slice - /// is the most recent sample in the window. - #[inline] - pub fn as_slice(&self) -> &[T] { - self.0.as_slice() + /// Copy the current contents of the window to a freshly-allocated + /// vector. Vector elements are ordered from least recent sample + /// to most recent sample. + pub fn to_vec(&self) -> Vec { + self.iter().collect() + } + + /// Obtain the inner SliceRingBuffer + pub fn inner(&self) -> &VecDeque { + &self.0 } /// Most recent element pushed into the Window @@ -339,12 +322,16 @@ where } } -impl AsRef<[T]> for Window +impl<'a, T> IntoIterator for &'a Window where T: Copy + Scalar + Zero, { - fn as_ref(&self) -> &[T] { - self.as_slice() + type Item = T; + + type IntoIter = std::iter::Copied>; + + fn into_iter(self) -> Self::IntoIter { + self.0.iter().copied() } } @@ -360,11 +347,11 @@ where // `history` contains the sample history. The most recent sample // is stored in `history[N-1]`, and the least recent sample in the // history is stored in `history[0]`. The filter coefficients are -// stored *reversed* in `rev_coeff`, with `rev_coeff[N-1]` being -// the zeroth filter coefficient. +// stored in `coeff`, with `coeff[0]` being the zeroth filter +// coefficient. // -// The two slices need not be the same length. If `history` is shorter -// than `rev_coeff`, then the sample history is assumed to be zero +// The two inputs need not be the same length. If `history` is shorter +// than `coeff`, then the sample history is assumed to be zero // outside of its range. // // To perform FIR filtering, new samples are shifted onto the end of @@ -373,19 +360,18 @@ where // // The output value is returned. Any compatible arithmetic types may // be used, including complex numbers. -fn multiply_accumulate(history: &[In], rev_coeff: &[Coeff]) -> Out +fn multiply_accumulate(history: W, coeff: &[Coeff]) -> Out where + W: IntoIterator, + W::IntoIter: DoubleEndedIterator, In: Copy + Scalar + std::ops::Mul, Coeff: Copy + Scalar, Out: Copy + Scalar + Zero + std::ops::AddAssign, { - let mul_len = usize::min(history.len(), rev_coeff.len()); - let history = &history[history.len() - mul_len..]; - let rev_coeff = &rev_coeff[rev_coeff.len() - mul_len..]; - + let history = history.into_iter(); let mut out = Out::zero(); - for (hi, co) in history.iter().zip(rev_coeff.iter()) { - out += *hi * *co; + for (hi, co) in history.rev().zip(coeff.iter()) { + out += hi * *co; } out } @@ -407,11 +393,11 @@ mod tests { // simple multiplies; we clip to the end let out = multiply_accumulate(&[20.0f32, 1.0f32], &[1.0f32]); assert_eq!(1.0f32, out); - let out = multiply_accumulate(&[1.0f32], &[20.0f32, 1.0f32]); + let out = multiply_accumulate(&[1.0f32], &[1.0f32, 20.0f32]); assert_eq!(1.0f32, out); // more complicated multiply - let out = multiply_accumulate(&[20.0f32, 20.0f32], &[-1.0f32, 1.0f32]); + let out = multiply_accumulate(&[20.0f32, 20.0f32], &[1.0f32, -1.0f32]); assert_approx_eq!(0.0f32, out); } @@ -431,13 +417,13 @@ mod tests { #[test] fn test_filter_identity() { - const EXPECT: &[f32] = &[0.0f32, 0.0f32, 0.0f32, 1.0f32]; + const EXPECT: &[f32] = &[1.0f32, 0.0f32, 0.0f32, 0.0f32]; let mut filter = FilterCoeff::::from_identity(4); assert_eq!(EXPECT, filter.as_ref()); assert_eq!(10.0f32, filter.filter(&[10.0f32])); - filter[2] = 5.0f32; + filter[3] = 5.0f32; filter.identity(); assert_eq!(EXPECT, filter.as_ref()); } @@ -446,15 +432,17 @@ mod tests { fn test_window() { let mut wind: Window = Window::new(4); assert_eq!(4, wind.len()); - assert_eq!(&[0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.to_vec()); wind.push(&[1.0f32]); - assert_eq!(&[0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.to_vec()); + wind.push(&[]); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 1.0f32], wind.to_vec()); wind.push(&[2.0f32]); - assert_eq!(&[0.0f32, 0.0f32, 1.0f32, 2.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 1.0f32, 2.0f32], wind.to_vec()); wind.push(&[-1.0f32, -2.0f32, 1.0f32, 2.0f32, 3.0f32, 4.0f32]); - assert_eq!(&[1.0f32, 2.0f32, 3.0f32, 4.0f32], wind.as_slice()); + assert_eq!(vec![1.0f32, 2.0f32, 3.0f32, 4.0f32], wind.to_vec()); assert_eq!(4.0f32, wind.back()); assert_eq!(1.0f32, wind.front()); assert_eq!(4, wind.len()); @@ -462,10 +450,14 @@ mod tests { // push individual samples works too assert_eq!(1.0f32, wind.push_scalar(10.0f32)); assert_eq!(4, wind.len()); - assert_eq!(&[2.0f32, 3.0f32, 4.0f32, 10.0f32], wind.as_slice()); + assert_eq!(vec![2.0f32, 3.0f32, 4.0f32, 10.0f32], wind.to_vec()); + + // exactly enough to fill + wind.push(&[5.0f32, 4.0f32, 3.0f32, 2.0f32]); + assert_eq!(vec![5.0f32, 4.0f32, 3.0f32, 2.0f32], wind.to_vec()); wind.reset(); assert_eq!(4, wind.len()); - assert_eq!(&[0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.as_slice()); + assert_eq!(vec![0.0f32, 0.0f32, 0.0f32, 0.0f32], wind.to_vec()); } }