Skip to main content

rustradio/
fir.rs

1//! Finite impulse response filter.
2//!
3//! If using many taps, [`FftFilter`](crate::blocks::FftFilter) probably has
4//! better performance.
5//!
6//! TODO: Change taps to return error instead of assert?
7/*
8 * TODO:
9 * * Only handles case where input, output, and tap type are all the same.
10 */
11use log::error;
12use std::borrow::Borrow;
13
14use crate::block::{Block, BlockRet};
15use crate::stream::{ReadStream, WriteStream};
16use crate::window::{Window, WindowType};
17use crate::{Complex, Float, Result, Sample};
18
19#[doc(hidden)]
20pub trait FrequencyTranslate {
21    /// Per-block translation state.
22    ///
23    /// Most sample types do not support translation and use `()`. Complex FIRs
24    /// use a rotator that advances once per output sample.
25    type Translator: Send;
26
27    /// Return the no-op translator for blocks that do not translate frequency.
28    fn no_translation() -> Self::Translator;
29
30    /// Configure frequency translation before the FIR is built.
31    ///
32    /// Implementations may modify `taps` to fold fixed per-tap phase terms into
33    /// the filter, then return any state needed to finish translation while
34    /// samples are produced.
35    fn new_translator(
36        taps: &mut [Self],
37        samp_rate: Float,
38        freq: Float,
39        deci: usize,
40    ) -> Self::Translator
41    where
42        Self: Sized;
43
44    /// Apply the continuing part of frequency translation to produced samples.
45    fn translate_output(out: &mut [Self], translator: &mut Self::Translator)
46    where
47        Self: Sized;
48}
49
50/// Finite impulse response filter.
51pub struct Fir<T> {
52    taps: Vec<T>,
53}
54
55#[cfg(all(
56    target_feature = "avx",
57    target_feature = "sse3",
58    target_feature = "sse"
59))]
60#[allow(unreachable_code)]
61fn sum_product_avx(vec1: &[f32], vec2: &[f32]) -> f32 {
62    // SAFETY: Pointer arithmetic "should be fine". And as for instruction availability, that could
63    // be checked by the macro above.
64    unsafe {
65        use core::arch::x86_64::*;
66        assert!(vec2.len() >= vec1.len());
67        let len = vec1.len() - vec1.len() % 8;
68
69        // AVX.
70        let mut sum = _mm256_setzero_ps(); // Initialize sum vector to zeros.
71
72        for i in (0..len).step_by(8) {
73            // AVX.
74            let a = _mm256_loadu_ps(vec1.as_ptr().add(i));
75            let b = _mm256_loadu_ps(vec2.as_ptr().add(i));
76
77            // Multiply and accumulate.
78            // AVX.
79            let prod = _mm256_mul_ps(a, b);
80            sum = _mm256_add_ps(sum, prod);
81        }
82
83        // Split.
84        // AVX.
85        let low = _mm256_extractf128_ps(sum, 0);
86        let high = _mm256_extractf128_ps(sum, 1);
87
88        // Compact step 1 => 4 floats.
89        // SSE3.
90        let m128 = _mm_hadd_ps(low, high);
91
92        // Compact step 2 => 2 floats.
93        // SSE3.
94        let m128 = _mm_hadd_ps(m128, low);
95
96        // Compact step 3 => 1 floats.
97        // SSE3.
98        let m128 = _mm_hadd_ps(m128, low);
99        // SSE.
100        let partial = _mm_cvtss_f32(m128);
101        let skip = vec1.len() - vec1.len() % 8;
102        vec1[skip..]
103            .iter()
104            .zip(vec2[skip..].iter())
105            .fold(partial, |acc, (&f, &x)| acc + x * f)
106    }
107}
108
109impl Fir<Float> {
110    /// Run filter once, creating one sample from the taps and an
111    /// equal number of input samples.
112    #[must_use]
113    pub fn filter_float(&self, input: &[Float]) -> Float {
114        #[cfg(all(target_arch = "wasm32", target_feature = "simd128",))]
115        return sum_product_float_wasm(input, &self.taps);
116        // AVX is faster, when available.
117        #[cfg(all(
118            target_feature = "avx",
119            target_feature = "sse3",
120            target_feature = "sse"
121        ))]
122        return sum_product_avx(&self.taps, input);
123        // Second fastest is generic simd.
124        #[cfg(feature = "simd")]
125        #[allow(unreachable_code)]
126        {
127            use std::simd::num::SimdFloat;
128            type Batch = std::simd::f32x8;
129
130            let batch_n = 8;
131            // How will this work if Float is f64?
132            let partial = input
133                .chunks_exact(batch_n)
134                .zip(self.taps.chunks_exact(batch_n))
135                .map(|(a, b)| Batch::from_slice(a) * Batch::from_slice(b))
136                .fold(Batch::splat(0.0), |acc, x| acc + x)
137                .reduce_sum();
138            // Maybe even faster if doing a second round with f32x4.
139            let skip = self.taps.len() - self.taps.len() % batch_n;
140            return input[skip..]
141                .iter()
142                .zip(self.taps[skip..].iter())
143                .fold(partial, |acc, (&f, &x)| acc + x * f);
144        }
145        #[allow(unreachable_code)]
146        self.filter(input)
147    }
148}
149
150impl<T> Fir<T>
151where
152    T: Sample + std::ops::Mul<T, Output = T> + std::ops::Add<T, Output = T>,
153{
154    /// Create new FIR.
155    #[must_use]
156    pub fn new(taps: impl AsRef<[T]>) -> Self {
157        let taps = taps.as_ref();
158        assert!(!taps.is_empty());
159        Self {
160            taps: taps.iter().copied().rev().collect(),
161        }
162    }
163    /// Run filter once, creating one sample from the taps and an
164    /// equal number of input samples.
165    #[must_use]
166    pub fn filter(&self, input: &[T]) -> T {
167        assert!(
168            input.len() >= self.taps.len(),
169            "input {} < taps {}",
170            input.len(),
171            self.taps.len()
172        );
173        input
174            .iter()
175            .zip(self.taps.iter())
176            .fold(T::default(), |acc, (&f, &x)| acc + x * f)
177    }
178
179    /// Call `filter()` multiple times, across an input range.
180    #[must_use]
181    pub fn filter_n(&self, input: &[T], deci: usize) -> Vec<T> {
182        assert_ne!(deci, 0);
183        assert!(input.len() >= self.taps.len());
184        let n = input.len() - self.taps.len();
185        (0..=n)
186            .step_by(deci)
187            .map(|i| self.filter(&input[i..]))
188            .collect()
189    }
190
191    /// Like `filter_n`, but avoids a copy when there's a destination in mind.
192    pub fn filter_n_inplace(&self, input: &[T], deci: usize, out: &mut [T]) {
193        assert_ne!(deci, 0);
194        out.iter_mut()
195            .enumerate()
196            .for_each(|(i, o)| *o = self.filter(&input[(i * deci)..]));
197    }
198}
199
200#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
201fn horizontal_sum_f32x4(v: core::arch::wasm32::v128) -> Float {
202    use core::arch::wasm32::f32x4_extract_lane;
203
204    f32x4_extract_lane::<0>(v)
205        + f32x4_extract_lane::<1>(v)
206        + f32x4_extract_lane::<2>(v)
207        + f32x4_extract_lane::<3>(v)
208}
209
210// As of 2026-06-27, this specialization shaves off about 20% of float FIR CPU.
211#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
212fn sum_product_float_wasm(input: &[Float], taps: &[Float]) -> Float {
213    use core::arch::wasm32::*;
214
215    assert!(
216        input.len() >= taps.len(),
217        "input {} < taps {}",
218        input.len(),
219        taps.len()
220    );
221
222    let len = taps.len() - taps.len() % 4;
223    let mut acc = f32x4_splat(0.0);
224    for n in (0..len).step_by(4) {
225        // SAFETY: `len` is rounded down to a multiple of four and no larger
226        // than `taps.len()`, while the caller already checked
227        // `input.len() >= taps.len()`. `v128_load` is valid for unaligned wasm
228        // memory, and each iteration loads exactly four f32s from both slices.
229        let (input_v, taps_v) = unsafe {
230            (
231                v128_load(input.as_ptr().add(n).cast()),
232                v128_load(taps.as_ptr().add(n).cast()),
233            )
234        };
235        acc = f32x4_add(acc, f32x4_mul(input_v, taps_v));
236    }
237
238    input[len..]
239        .iter()
240        .zip(taps[len..].iter())
241        .fold(horizontal_sum_f32x4(acc), |acc, (&input, &tap)| {
242            acc + tap * input
243        })
244}
245
246// This ended up not being faster. Leaving it here for now.
247#[cfg(all(target_arch = "wasm32", target_feature = "simd128"))]
248#[allow(unused)]
249fn sum_product_complex_wasm(input: &[Complex], taps: &[Complex]) -> Complex {
250    use core::arch::wasm32::*;
251
252    assert!(
253        input.len() >= taps.len(),
254        "input {} < taps {}",
255        input.len(),
256        taps.len()
257    );
258    assert_eq!(
259        std::mem::size_of::<Complex>(),
260        2 * std::mem::size_of::<Float>()
261    );
262
263    let len = taps.len() - taps.len() % 2;
264    let input_ptr = input.as_ptr().cast::<Float>();
265    let taps_ptr = taps.as_ptr().cast::<Float>();
266
267    // Each v128 holds two Complex<f32> values as [re0, im0, re1, im1].
268    // Accumulate real and imaginary terms separately, then reduce lanes once.
269    let mut real_acc = f32x4_splat(0.0);
270    let mut imag_acc = f32x4_splat(0.0);
271    let real_signs = f32x4(1.0, -1.0, 1.0, -1.0);
272    for n in (0..len).step_by(2) {
273        // SAFETY: `len` is even and no larger than `taps.len()`, while the
274        // caller already checked `input.len() >= taps.len()`. `v128_load` is
275        // valid for unaligned wasm memory, and each iteration loads exactly two
276        // complex samples from both slices.
277        let (input_v, taps_v) = unsafe {
278            (
279                v128_load(input_ptr.add(2 * n).cast()),
280                v128_load(taps_ptr.add(2 * n).cast()),
281            )
282        };
283        let products = f32x4_mul(input_v, taps_v);
284        real_acc = f32x4_add(real_acc, f32x4_mul(products, real_signs));
285
286        let input_swapped = i32x4_shuffle::<1, 0, 3, 2>(input_v, input_v);
287        imag_acc = f32x4_add(imag_acc, f32x4_mul(input_swapped, taps_v));
288    }
289
290    let mut ret = Complex::new(
291        horizontal_sum_f32x4(real_acc),
292        horizontal_sum_f32x4(imag_acc),
293    );
294    for (&input, &tap) in input[len..].iter().zip(taps[len..].iter()) {
295        ret += tap * input;
296    }
297    ret
298}
299
300/// Builder for a FIR filter block.
301///
302/// A builder is needed to create a decimating FIR filter block.
303pub struct FirFilterBuilder<T> {
304    taps: Vec<T>,
305    deci: usize,
306    // Optional `(sample_rate, frequency)` requested by `translate()`.
307    translate: Option<(Float, Float)>,
308}
309
310impl<T> FirFilterBuilder<T>
311where
312    T: Sample + std::ops::Mul<T, Output = T> + std::ops::Add<T, Output = T> + FrequencyTranslate,
313{
314    /// Set the decimation to the given value.
315    ///
316    /// The default is 1, meaning no decimation.
317    #[must_use]
318    pub fn deci(mut self, deci: usize) -> Self {
319        assert_ne!(deci, 0);
320        self.deci = deci;
321        self
322    }
323
324    /// Build a `FirFilter` with the provided settings.
325    #[must_use]
326    pub fn build(self, src: ReadStream<T>) -> (FirFilter<T>, ReadStream<T>) {
327        let FirFilterBuilder {
328            mut taps,
329            deci,
330            translate,
331        } = self;
332        let translator = translate.map_or_else(T::no_translation, |(samp_rate, freq)| {
333            T::new_translator(&mut taps, samp_rate, freq, deci)
334        });
335        let (mut block, stream) = FirFilter::new(src, &taps);
336        block.deci = deci;
337        block.translator = translator;
338        (block, stream)
339    }
340}
341
342/// Finite impulse response filter block.
343#[derive(rustradio_macros::Block)]
344#[rustradio(crate)]
345pub struct FirFilter<T: Sample + FrequencyTranslate> {
346    fir: Fir<T>,
347    ntaps: usize,
348    deci: usize,
349    // Per-block rotator state for fused frequency translation.
350    translator: T::Translator,
351    #[rustradio(in)]
352    src: ReadStream<T>,
353    #[rustradio(out)]
354    dst: WriteStream<T>,
355}
356
357impl<T> FirFilter<T>
358where
359    T: Sample + std::ops::Mul<T, Output = T> + std::ops::Add<T, Output = T> + FrequencyTranslate,
360{
361    /// Create new `FirFilterBuilder`, with the supplied taps.
362    pub fn builder(taps: impl Into<Vec<T>>) -> FirFilterBuilder<T> {
363        FirFilterBuilder {
364            taps: taps.into(),
365            deci: 1,
366            translate: None,
367        }
368    }
369    /// Create Fir block given taps.
370    pub fn new(src: ReadStream<T>, taps: impl AsRef<[T]>) -> (Self, ReadStream<T>) {
371        let taps = taps.as_ref();
372        assert!(!taps.is_empty());
373        let (dst, dr) = crate::stream::new_stream();
374        (
375            Self {
376                src,
377                dst,
378                ntaps: taps.len(),
379                deci: 1,
380                translator: T::no_translation(),
381                fir: Fir::new(taps),
382            },
383            dr,
384        )
385    }
386}
387
388macro_rules! impl_no_frequency_translate {
389    ($($ty:ty),* $(,)?) => {
390        $(
391            impl FrequencyTranslate for $ty {
392                type Translator = ();
393                fn no_translation() -> Self::Translator {}
394
395                fn new_translator(
396                    _taps: &mut [Self],
397                    _samp_rate: Float,
398                    _freq: Float,
399                    _deci: usize,
400                ) -> Self::Translator {
401                    error!("FirFilter asked to translate on non-Complex");
402                }
403
404                fn translate_output(_out: &mut [Self], _translator: &mut Self::Translator) {}
405            }
406        )*
407    };
408}
409
410// Enable FftFilter on these types, even though only `Complex` actually supports
411// frequency translation. This is type restricted in the builder, so it should
412// not be possible to actually instantiate a translator anyway.
413impl_no_frequency_translate!(f32, f64, u8, u32, i32);
414
415#[doc(hidden)]
416pub struct ComplexFrequencyTranslator {
417    /// Current complex oscillator value for the next output sample.
418    phase: Complex,
419    /// Per-output oscillator step, including decimation.
420    step: Complex,
421}
422
423impl FrequencyTranslate for Complex {
424    type Translator = Option<ComplexFrequencyTranslator>;
425
426    fn no_translation() -> Self::Translator {
427        None
428    }
429
430    fn new_translator(
431        taps: &mut [Self],
432        samp_rate: Float,
433        freq: Float,
434        deci: usize,
435    ) -> Self::Translator {
436        assert!(samp_rate > 0.0);
437        assert_ne!(deci, 0);
438        if freq == 0.0 {
439            return None;
440        }
441        let input_step = 2.0 * std::f64::consts::PI * f64::from(freq) / f64::from(samp_rate);
442        let tap_step = Complex::new(input_step.cos() as Float, input_step.sin() as Float);
443        let mut phase = Complex::new(1.0, 0.0);
444        // Pre-rotate taps by their relative delay so filtering plus one output
445        // rotation matches explicitly mixing each input sample by `-freq`.
446        for tap in &mut *taps {
447            *tap *= phase;
448            phase *= tap_step;
449        }
450
451        // The first produced FIR output is aligned with the newest sample in
452        // the first input window; after that, outputs advance by `deci` inputs.
453        let first_output_phase = -input_step * (taps.len() - 1) as f64;
454        let output_step = -input_step * deci as f64;
455        Some(ComplexFrequencyTranslator {
456            phase: Complex::new(
457                first_output_phase.cos() as Float,
458                first_output_phase.sin() as Float,
459            ),
460            step: Complex::new(output_step.cos() as Float, output_step.sin() as Float),
461        })
462    }
463
464    fn translate_output(out: &mut [Self], translator: &mut Self::Translator) {
465        // TODO: do we need to reset this periodically, to get around rounding
466        // errors?
467        if let Some(translator) = translator {
468            for sample in out {
469                *sample *= translator.phase;
470                translator.phase *= translator.step;
471            }
472        }
473    }
474}
475
476impl FirFilterBuilder<Complex> {
477    /// Mix by `-freq` Hz while filtering.
478    ///
479    /// A tone at `freq` Hz is translated to DC. The implementation folds the
480    /// input mixer into the FIR taps and keeps one rotator per output sample.
481    #[must_use]
482    pub fn translate(mut self, samp_rate: Float, freq: Float) -> Self {
483        self.translate = Some((samp_rate, freq));
484        self
485    }
486}
487
488impl<T> Block for FirFilter<T>
489where
490    T: Sample + std::ops::Mul<T, Output = T> + std::ops::Add<T, Output = T> + FrequencyTranslate,
491{
492    fn work(&mut self) -> Result<BlockRet<'_>> {
493        let (input, mut tags) = self.src.read_buf()?;
494
495        // Get number of input samples we intend to consume.
496        let n = {
497            // Carefully avoid underflow.
498            let absolute_minimum = self.ntaps + self.deci - 1;
499            if input.len() < absolute_minimum {
500                return Ok(BlockRet::WaitForStream(&self.src, absolute_minimum));
501            }
502            self.deci * ((input.len() - self.ntaps + 1) / self.deci)
503        };
504        assert_ne!(n, 0);
505
506        // To consume `n`, we may need more input samples than that.
507        let need = n + self.ntaps - 1;
508        assert!(input.len() >= need, "need {need}, have {}", input.len());
509
510        // Output must have room for at least one sample.
511        let mut out = self.dst.write_buf()?;
512        let need_out = 1;
513        if out.len() < need_out {
514            return Ok(BlockRet::WaitForStream(&self.dst, need_out));
515        }
516
517        // Cap by output capacity.
518        let n = std::cmp::min(n, out.len() * self.deci);
519
520        // Final `n` (samples to consume) calculated. Sanity check it.
521        assert_eq!(n % self.deci, 0);
522        assert_ne!(n, 0, "input: {} out: {}", input.len(), out.len());
523
524        // Run the FIR.
525        let out_n = n / self.deci;
526        self.fir
527            .filter_n_inplace(&input.slice()[..need], self.deci, &mut out.slice()[..out_n]);
528
529        // Frequency translate. This is an empty function call if translation is
530        // zero.
531        T::translate_output(&mut out.slice()[..out_n], &mut self.translator);
532
533        // Sanity check the generated output.
534        assert!(out_n <= out.len());
535
536        tags.retain(|tag| tag.pos() < n);
537        input.consume(n);
538        if self.deci == 1 {
539            out.produce(out_n, &tags);
540        } else {
541            for t in &mut tags {
542                t.set_pos(t.pos() / self.deci);
543            }
544            out.produce(out_n, &tags);
545        }
546        // While we could keep track of which stream is the constraining factor,
547        // the code is simpler if work() is just called again, and the right
548        // WaitForStream is returned above instead.
549        Ok(BlockRet::Again)
550    }
551}
552
553/// Create a multiband filter.
554///
555/// TODO: this is untested.
556#[must_use]
557pub fn multiband(bands: &[(Float, Float)], taps: usize, window: &Window) -> Option<Vec<Complex>> {
558    use rustfft::FftPlanner;
559
560    if taps == 0 || taps != window.0.len() {
561        return None;
562    }
563
564    let mut ideal = vec![Complex::new(0.0, 0.0); taps];
565    let scale = (taps as Float) / 2.0;
566    for (low, high) in bands {
567        let a = (low * scale).floor() as usize;
568        let b = (high * scale).ceil() as usize;
569        if a > b || a > taps || b > taps {
570            return None;
571        }
572        for n in a..b {
573            ideal[n] = Complex::new(1.0, 0.0);
574            ideal[taps - n - 1] = Complex::new(1.0, 0.0);
575        }
576    }
577    let fft_size = taps;
578    let mut planner = FftPlanner::new();
579    let ifft = planner.plan_fft_inverse(fft_size);
580    ifft.process(&mut ideal);
581    ideal.rotate_right(taps / 2);
582    let scale = (fft_size as Float).sqrt();
583    Some(
584        ideal
585            .into_iter()
586            .enumerate()
587            .map(|(n, v)| v * window.0[n] / Complex::new(scale, 0.0))
588            .collect(),
589    )
590}
591
592/// Create taps for a low pass filter as complex taps.
593#[must_use]
594pub fn low_pass_complex(
595    samp_rate: Float,
596    cutoff: Float,
597    twidth: Float,
598    window_type: impl Borrow<WindowType>,
599) -> Vec<Complex> {
600    low_pass(samp_rate, cutoff, twidth, window_type)
601        .into_iter()
602        .map(|t| Complex::new(t, 0.0))
603        .collect()
604}
605
606fn compute_ntaps(samp_rate: Float, twidth: Float, window_type: &WindowType) -> usize {
607    let a = window_type.max_attenuation();
608    let t = (a * samp_rate / (22.0 * twidth)) as usize;
609    if (t & 1) == 0 { t + 1 } else { t }
610}
611
612/// Create taps for a low pass filter.
613///
614/// TODO: this could be faster if we supported filtering a Complex by a Float.
615/// A low pass filter doesn't actually need complex taps.
616#[must_use]
617pub fn low_pass(
618    samp_rate: Float,
619    cutoff: Float,
620    twidth: Float,
621    window_type: impl Borrow<WindowType>,
622) -> Vec<Float> {
623    assert!(samp_rate > 0.0);
624    assert!(cutoff > 0.0);
625    assert!(twidth > 0.0);
626    let window_type = window_type.borrow();
627
628    let pi = std::f64::consts::PI as Float;
629    let ntaps = compute_ntaps(samp_rate, twidth, window_type);
630    let window = window_type.make_window(ntaps);
631    let m = (ntaps - 1) / 2;
632    let fwt0 = 2.0 * pi * cutoff / samp_rate;
633    let taps: Vec<_> = window
634        .0
635        .iter()
636        .enumerate()
637        .map(|(nm, win)| {
638            let n = nm as i64 - m as i64;
639            let nf = n as Float;
640            if n == 0 {
641                fwt0 / pi * win
642            } else {
643                ((nf * fwt0).sin() / (nf * pi)) * win
644            }
645        })
646        .collect();
647    let gain = {
648        let gain: Float = 1.0;
649        let mut fmax = taps[m];
650        for n in 1..=m {
651            fmax += 2.0 * taps[n + m];
652        }
653        gain / fmax
654    };
655    taps.into_iter().map(|t| t * gain).collect()
656}
657
658/// Generate hilbert transformer filter.
659#[must_use]
660pub fn hilbert(window: &Window) -> Vec<Float> {
661    assert!(!window.0.is_empty());
662    assert_ne!(window.0.len(), 1);
663    let ntaps = window.0.len();
664    let mid = (ntaps - 1) / 2;
665    let mut gain = 0.0;
666    let mut taps = vec![0.0; ntaps];
667    for i in 1..=mid {
668        if i & 1 == 1 {
669            let x = 1.0 / (i as Float);
670            taps[mid + i] = x * window.0[mid + i];
671            taps[mid - i] = -x * window.0[mid - i];
672            gain = taps[mid + i] - gain;
673        } else {
674            taps[mid + i] = 0.0;
675            taps[mid - i] = 0.0;
676        }
677    }
678    let gain = 1.0 / (2.0 * gain.abs());
679    taps.iter().map(|e| gain * *e).collect()
680}
681
682#[cfg(test)]
683#[cfg_attr(coverage_nightly, coverage(off))]
684mod tests {
685    use super::*;
686    use crate::Repeat;
687    use crate::blocks::VectorSource;
688    use crate::stream::{Tag, TagValue};
689    use crate::tests::assert_almost_equal_complex;
690
691    #[test]
692    fn test_identity() -> Result<()> {
693        let input = vec![
694            Complex::new(1.0, 0.0),
695            Complex::new(2.0, 0.0),
696            Complex::new(3.0, 0.2),
697            Complex::new(4.1, 0.0),
698            Complex::new(5.0, 0.0),
699            Complex::new(6.0, 0.2),
700        ];
701        let taps = vec![Complex::new(1.0, 0.0)];
702        for deci in 1..=(3 * input.len()) {
703            let (mut src, src_out) = VectorSource::builder(input.clone())
704                .repeat(Repeat::finite(2))
705                .build()?;
706            assert!(matches![src.work()?, BlockRet::Again]);
707            assert!(matches![src.work()?, BlockRet::EOF]);
708
709            eprintln!("Testing identity with decimation {deci}");
710            let (mut b, os) = FirFilter::builder(taps.clone()).deci(deci).build(src_out);
711            if deci <= 2 * input.len() {
712                assert!(matches![b.work()?, BlockRet::Again]);
713            }
714            assert!(matches![b.work()?, BlockRet::WaitForStream(_, _)]);
715            let (res, tags) = os.read_buf()?;
716            let max = 2 * input.len() / deci;
717            if !res.is_empty() {
718                assert_eq!(
719                    &tags,
720                    &[
721                        Tag::new(0, "VectorSource::start", TagValue::Bool(true)),
722                        Tag::new(0, "VectorSource::repeat", TagValue::U64(0)),
723                        Tag::new(0, "VectorSource::first", TagValue::Bool(true)),
724                        Tag::new(6 / deci, "VectorSource::start", TagValue::Bool(true)),
725                        Tag::new(6 / deci, "VectorSource::repeat", TagValue::U64(1)),
726                    ]
727                );
728            }
729            assert_almost_equal_complex(
730                res.slice(),
731                &input
732                    .iter()
733                    .chain(input.iter())
734                    .copied()
735                    .step_by(deci)
736                    .take(max)
737                    .collect::<Vec<_>>(),
738            );
739        }
740        Ok(())
741    }
742
743    // Compare frequency translation against manual mixing and filtering.
744    #[test]
745    fn translate_matches_mixed_input() -> Result<()> {
746        let input: Vec<_> = (0..32)
747            .map(|i| Complex::new(i as Float, (i as Float) * 0.25))
748            .collect();
749        let taps = vec![
750            Complex::new(0.5, -0.1),
751            Complex::new(1.0, 0.2),
752            Complex::new(-0.25, 0.05),
753            Complex::new(0.125, -0.3),
754        ];
755        let samp_rate = 8.0;
756        let freq = 2.0;
757        let deci = 3;
758        let phase_step = -2.0 * std::f64::consts::PI * f64::from(freq) / f64::from(samp_rate);
759        let rot = Complex::new(phase_step.cos() as Float, phase_step.sin() as Float);
760        let mut phase = Complex::new(1.0, 0.0);
761        let mixed_input = input
762            .iter()
763            .map(|&sample| {
764                let out = sample * phase;
765                phase *= rot;
766                out
767            })
768            .collect::<Vec<_>>();
769
770        let (mut src_a, src_a_out) = VectorSource::new(input.clone());
771        assert!(matches![src_a.work()?, BlockRet::EOF]);
772        let (mut translated, translated_out) = FirFilter::builder(taps.clone())
773            .deci(deci)
774            .translate(samp_rate, freq)
775            .build(src_a_out);
776        assert!(matches![translated.work()?, BlockRet::Again]);
777        assert!(matches![translated.work()?, BlockRet::WaitForStream(_, _)]);
778
779        let (mut src_b, src_b_out) = VectorSource::new(mixed_input);
780        assert!(matches![src_b.work()?, BlockRet::EOF]);
781        let (mut manual, manual_out) = FirFilter::builder(taps).deci(deci).build(src_b_out);
782        assert!(matches![manual.work()?, BlockRet::Again]);
783        assert!(matches![manual.work()?, BlockRet::WaitForStream(_, _)]);
784
785        let (translated_res, _) = translated_out.read_buf()?;
786        let (manual_res, _) = manual_out.read_buf()?;
787        assert_almost_equal_complex(translated_res.slice(), manual_res.slice());
788        Ok(())
789    }
790
791    fn tone(len: usize, samp_rate: Float, freq: Float) -> Vec<Complex> {
792        let step = 2.0 * std::f64::consts::PI * f64::from(freq) / f64::from(samp_rate);
793        (0..len)
794            .map(|i| {
795                let phase = step * i as f64;
796                Complex::new(phase.cos() as Float, phase.sin() as Float)
797            })
798            .collect()
799    }
800
801    #[test]
802    fn translated_offset_tone_passes_low_pass() -> Result<()> {
803        let samp_rate = 1024.0;
804        let freq = 60.0;
805        let taps = low_pass_complex(samp_rate, 20.0, 10.0, &WindowType::Hamming);
806        let input = tone(4096, samp_rate, freq);
807
808        let (mut src, src_out) = VectorSource::new(input);
809        assert!(matches![src.work()?, BlockRet::EOF]);
810        let (mut filter, out) = FirFilter::builder(taps)
811            .translate(samp_rate, freq)
812            .build(src_out);
813        assert!(matches![filter.work()?, BlockRet::Again]);
814        assert!(matches![filter.work()?, BlockRet::WaitForStream(_, _)]);
815
816        let (res, _) = out.read_buf()?;
817        let mean = res.iter().map(|sample| sample.norm()).sum::<Float>() / res.len() as Float;
818        assert!(mean > 0.95, "translated tone mean magnitude: {mean}");
819        Ok(())
820    }
821
822    #[test]
823    fn translated_dc_is_rejected_by_low_pass() -> Result<()> {
824        let samp_rate = 1024.0;
825        let freq = 60.0;
826        let taps = low_pass_complex(samp_rate, 20.0, 10.0, &WindowType::Hamming);
827        let input = vec![Complex::new(1.0, 0.0); 4096];
828
829        let (mut src, src_out) = VectorSource::new(input);
830        assert!(matches![src.work()?, BlockRet::EOF]);
831        let (mut filter, out) = FirFilter::builder(taps)
832            .translate(samp_rate, freq)
833            .build(src_out);
834        assert!(matches![filter.work()?, BlockRet::Again]);
835        assert!(matches![filter.work()?, BlockRet::WaitForStream(_, _)]);
836
837        let (res, _) = out.read_buf()?;
838        let mean = res.iter().map(|sample| sample.norm()).sum::<Float>() / res.len() as Float;
839        assert!(mean < 0.01, "translated DC mean magnitude: {mean}");
840        Ok(())
841    }
842
843    #[test]
844    fn test_invert() -> Result<()> {
845        let input = vec![
846            Complex::new(1.0, 0.0),
847            Complex::new(2.0, 0.0),
848            Complex::new(3.0, 0.2),
849            Complex::new(4.1, 0.0),
850            Complex::new(5.0, 0.0),
851            Complex::new(6.0, 0.2),
852        ];
853        let taps = vec![Complex::new(-1.0, 0.0)];
854        for deci in 1..=(input.len() + 1) {
855            let (mut src, src_out) = VectorSource::new(input.clone());
856            src.work()?;
857
858            eprintln!("Testing identity with decimation {deci}");
859            let (mut b, os) = FirFilter::builder(taps.clone()).deci(deci).build(src_out);
860            if deci <= input.len() {
861                assert!(matches![b.work()?, BlockRet::Again]);
862            }
863            assert!(matches![b.work()?, BlockRet::WaitForStream(_, _)]);
864            let (res, _) = os.read_buf()?;
865            let max = input.len() / deci;
866            assert_almost_equal_complex(
867                res.slice(),
868                &input
869                    .iter()
870                    .copied()
871                    .step_by(deci)
872                    .take(max)
873                    .map(|v| -v)
874                    .collect::<Vec<_>>(),
875            );
876        }
877        Ok(())
878    }
879
880    #[test]
881    fn moving_avg() -> Result<()> {
882        let input = vec![
883            Complex::new(1.0, 0.0),
884            Complex::new(2.0, 0.0),
885            Complex::new(3.0, 0.2),
886            Complex::new(4.1, 0.0),
887            Complex::new(5.0, 0.0),
888            Complex::new(6.0, 0.2),
889        ];
890        let taps = vec![Complex::new(0.5, 0.0), Complex::new(0.5, 0.0)];
891        for deci in 1..=(input.len() + 1) {
892            let (mut src, src_out) = VectorSource::new(input.clone());
893            src.work()?;
894
895            eprintln!("Testing identity with decimation {deci}");
896            let (mut b, os) = FirFilter::builder(taps.clone()).deci(deci).build(src_out);
897            if deci < input.len() {
898                assert!(matches![b.work()?, BlockRet::Again]);
899            }
900            assert!(matches![b.work()?, BlockRet::WaitForStream(_, _)]);
901            let (res, _) = os.read_buf()?;
902            let max = (input.len() - 1) / deci;
903            assert_almost_equal_complex(
904                res.slice(),
905                &[
906                    Complex::new(1.5, 0.0),
907                    Complex::new(2.5, 0.1),
908                    Complex::new(3.55, 0.1),
909                    Complex::new(4.55, 0.0),
910                    Complex::new(5.5, 0.1),
911                ]
912                .into_iter()
913                .step_by(deci)
914                .take(max)
915                .collect::<Vec<_>>(),
916            );
917        }
918        Ok(())
919    }
920
921    #[test]
922    fn test_complex() {
923        let input = vec![
924            Complex::new(1.0, 0.0),
925            Complex::new(2.0, 0.0),
926            Complex::new(3.0, 0.2),
927            Complex::new(4.1, 0.0),
928            Complex::new(5.0, 0.0),
929            Complex::new(6.0, 0.2),
930        ];
931        let taps = vec![
932            Complex::new(0.1, 0.0),
933            Complex::new(1.0, 0.0),
934            Complex::new(0.0, 0.2),
935        ];
936        let filter = Fir::new(&taps);
937        assert_almost_equal_complex(
938            &filter.filter_n(&input, 1),
939            &[
940                Complex::new(2.3, 0.22),
941                Complex::new(3.41, 0.6),
942                Complex::new(4.56, 0.6),
943                Complex::new(5.6, 0.84),
944            ],
945        );
946        assert_almost_equal_complex(
947            &filter.filter_n(&input, 2),
948            &[Complex::new(2.3, 0.22), Complex::new(4.56, 0.6)],
949        );
950    }
951
952    #[test]
953    fn test_filter_generator() {
954        let taps = low_pass_complex(10000.0, 1000.0, 1000.0, WindowType::Hamming);
955        assert_eq!(taps.len(), 25);
956        assert_almost_equal_complex(
957            &taps,
958            &[
959                Complex::new(0.002010403, 0.0),
960                Complex::new(0.0016210203, 0.0),
961                Complex::new(7.851862e-10, 0.0),
962                Complex::new(-0.0044467063, 0.0),
963                Complex::new(-0.011685465, 0.0),
964                Complex::new(-0.018134259, 0.0),
965                Complex::new(-0.016773716, 0.0),
966                Complex::new(-3.6538055e-9, 0.0),
967                Complex::new(0.0358771, 0.0),
968                Complex::new(0.08697697, 0.0),
969                Complex::new(0.14148787, 0.0),
970                Complex::new(0.18345332, 0.0),
971                Complex::new(0.19922684, 0.0),
972                Complex::new(0.1834533, 0.0),
973                Complex::new(0.14148785, 0.0),
974                Complex::new(0.08697697, 0.0),
975                Complex::new(0.035877097, 0.0),
976                Complex::new(-3.6538053e-9, 0.0),
977                Complex::new(-0.016773716, 0.0),
978                Complex::new(-0.018134257, 0.0),
979                Complex::new(-0.011685458, 0.0),
980                Complex::new(-0.0044467044, 0.0),
981                Complex::new(7.851859e-10, 0.0),
982                Complex::new(0.0016210207, 0.0),
983                Complex::new(0.002010403, 0.0),
984            ],
985        );
986    }
987
988    #[test]
989    fn multiband_rejects_invalid_ranges() {
990        assert!(multiband(&[(0.0, 1.0)], 0, &Window(vec![])).is_none());
991        assert!(multiband(&[(0.0, 3.0)], 8, &Window(vec![1.0; 8])).is_none());
992        assert!(multiband(&[(0.5, 0.25)], 8, &Window(vec![1.0; 8])).is_none());
993    }
994}