1use 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 type Translator: Send;
26
27 fn no_translation() -> Self::Translator;
29
30 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 fn translate_output(out: &mut [Self], translator: &mut Self::Translator)
46 where
47 Self: Sized;
48}
49
50pub 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 unsafe {
65 use core::arch::x86_64::*;
66 assert!(vec2.len() >= vec1.len());
67 let len = vec1.len() - vec1.len() % 8;
68
69 let mut sum = _mm256_setzero_ps(); for i in (0..len).step_by(8) {
73 let a = _mm256_loadu_ps(vec1.as_ptr().add(i));
75 let b = _mm256_loadu_ps(vec2.as_ptr().add(i));
76
77 let prod = _mm256_mul_ps(a, b);
80 sum = _mm256_add_ps(sum, prod);
81 }
82
83 let low = _mm256_extractf128_ps(sum, 0);
86 let high = _mm256_extractf128_ps(sum, 1);
87
88 let m128 = _mm_hadd_ps(low, high);
91
92 let m128 = _mm_hadd_ps(m128, low);
95
96 let m128 = _mm_hadd_ps(m128, low);
99 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 #[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 #[cfg(all(
118 target_feature = "avx",
119 target_feature = "sse3",
120 target_feature = "sse"
121 ))]
122 return sum_product_avx(&self.taps, input);
123 #[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 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 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 #[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 #[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 #[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 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#[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 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#[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 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 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
300pub struct FirFilterBuilder<T> {
304 taps: Vec<T>,
305 deci: usize,
306 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 #[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 #[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#[derive(rustradio_macros::Block)]
344#[rustradio(crate)]
345pub struct FirFilter<T: Sample + FrequencyTranslate> {
346 fir: Fir<T>,
347 ntaps: usize,
348 deci: usize,
349 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 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 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
410impl_no_frequency_translate!(f32, f64, u8, u32, i32);
414
415#[doc(hidden)]
416pub struct ComplexFrequencyTranslator {
417 phase: Complex,
419 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 for tap in &mut *taps {
447 *tap *= phase;
448 phase *= tap_step;
449 }
450
451 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 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 #[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 let n = {
497 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 let need = n + self.ntaps - 1;
508 assert!(input.len() >= need, "need {need}, have {}", input.len());
509
510 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 let n = std::cmp::min(n, out.len() * self.deci);
519
520 assert_eq!(n % self.deci, 0);
522 assert_ne!(n, 0, "input: {} out: {}", input.len(), out.len());
523
524 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 T::translate_output(&mut out.slice()[..out_n], &mut self.translator);
532
533 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 Ok(BlockRet::Again)
550 }
551}
552
553#[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#[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#[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#[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 #[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}