├── .gitignore ├── Cargo.toml ├── src ├── lib.rs ├── downconverter.rs ├── agc.rs ├── fm.rs ├── cic.rs └── fir.rs ├── README.md └── examples ├── dc.rs ├── cic.rs └── fir.rs /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "sdr" 3 | description = "Rust SDR DSP functionality" 4 | version = "0.7.0" 5 | authors = ["Adam Greig "] 6 | homepage = "https://github.com/adamgreig/sdr-rs" 7 | repository = "https://github.com/adamgreig/sdr-rs" 8 | readme = "README.md" 9 | keywords = ["sdr", "dsp"] 10 | license = "MIT" 11 | 12 | [dependencies] 13 | num = "0.2" 14 | 15 | [dev-dependencies] 16 | time = "0.1.39" 17 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate num; 2 | use num::Complex; 3 | 4 | pub type IQ = Complex; 5 | pub type Real = T; 6 | 7 | #[macro_export] 8 | macro_rules! chain_blocks { 9 | ($x:ident, $($f:ident),*) => {{ 10 | let x = $x; 11 | $( 12 | let x = $f.process(&x); 13 | )* 14 | x 15 | }} 16 | } 17 | 18 | pub mod downconverter; 19 | pub use downconverter::RealFs4Downconverter; 20 | 21 | pub mod cic; 22 | pub use cic::CIC; 23 | 24 | pub mod fir; 25 | pub use fir::FIR; 26 | 27 | pub mod fm; 28 | pub use fm::FMDemod; 29 | 30 | pub mod agc; 31 | pub use agc::agc; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sdr-rs 2 | 3 | An SDR DSP library for Rust. 4 | 5 | Currently includes: 6 | * FIR filtering/decimation 7 | * CIC filtering/decimation 8 | * Downconversion by Fs/4 9 | 10 | To build the tests and examples, `cargo test --release` and check 11 | `target/release/examples/`. Currently the examples are really more like 12 | benchmarks, but those aren't shipped in stable Rust at the moment. 13 | 14 | An emphasis is placed on performance. Everything is implemented in a reasonably 15 | fast fashion. The filters can operate on tens or hundreds of millions of 16 | samples per second, even on a single core, for reasonable filter sizes and data 17 | widths. 18 | 19 | Use alongside airspy-rs, rtlsdr-rs and hackrf-rs to act as SDR sources and 20 | sinks. 21 | -------------------------------------------------------------------------------- /examples/dc.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | extern crate sdr; 3 | 4 | use sdr::RealFs4Downconverter; 5 | 6 | #[cfg_attr(test, allow(dead_code))] 7 | fn main() { 8 | let n_samples = 1<<23 as usize; 9 | let n_repeats = 10u64; 10 | 11 | let dc = RealFs4Downconverter::new(); 12 | let mut x: Vec = Vec::with_capacity(n_samples); 13 | for _ in 0..(n_samples/8) { 14 | x.push(2048+3); x.push(2048+7); x.push(2048+4); x.push(2048+6); 15 | x.push(2048-3); x.push(2048-7); x.push(2048-4); x.push(2048-6); 16 | } 17 | 18 | let t0 = time::precise_time_ns(); 19 | for _ in 0..n_repeats { 20 | dc.process(&x); 21 | } 22 | let t1 = time::precise_time_ns(); 23 | let total_samples = n_samples as f64 * n_repeats as f64; 24 | let total_time = (t1 - t0) as f64 / 1e9; 25 | let throughput = total_samples / total_time; 26 | println!("{} blocks of {} samples, {:.2}Msps", 27 | n_repeats, n_samples, throughput / 1000000.0_f64); 28 | } 29 | -------------------------------------------------------------------------------- /examples/cic.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | extern crate sdr; 3 | use sdr::CIC; 4 | use sdr::IQ; 5 | 6 | #[cfg_attr(test, allow(dead_code))] 7 | fn main() { 8 | bench_scalar(); 9 | bench_complex(); 10 | } 11 | 12 | #[cfg_attr(test, allow(dead_code))] 13 | fn bench_scalar() { 14 | let q = 5usize; 15 | let r = 16usize; 16 | let n_samples = 1<<24 as usize; 17 | let n_repeats = 10u64; 18 | 19 | let mut cic = CIC::new(q, r, 12); 20 | let mut x: Vec = Vec::with_capacity(n_samples); 21 | for _ in 0..(n_samples/8) { 22 | x.push(3); x.push(7); x.push(4); x.push(6); 23 | x.push(3); x.push(7); x.push(4); x.push(6); 24 | } 25 | let t0 = time::precise_time_ns(); 26 | for _ in 0..n_repeats { 27 | cic.process(&x); 28 | } 29 | let t1 = time::precise_time_ns(); 30 | let total_samples = n_samples as f64 * n_repeats as f64; 31 | let total_time = (t1 - t0) as f64 / 1e9; 32 | let throughput = total_samples / total_time; 33 | println!("i16: {} blocks of {} samples, {:.2}Msps", 34 | n_repeats, n_samples, throughput / 1000000.0_f64); 35 | } 36 | 37 | #[cfg_attr(test, allow(dead_code))] 38 | fn bench_complex() { 39 | let q = 5usize; 40 | let r = 16usize; 41 | let n_samples = 1<<24 as usize; 42 | let n_repeats = 10u64; 43 | 44 | let mut cic = CIC::new(q, r, 12); 45 | let mut x: Vec> = Vec::with_capacity(n_samples); 46 | for _ in 0..(n_samples/8) { 47 | x.push(IQ{re:3, im:-3}); x.push(IQ{re:7, im:-7}); 48 | x.push(IQ{re:4, im:-4}); x.push(IQ{re:6, im:-6}); 49 | x.push(IQ{re:3, im:-3}); x.push(IQ{re:7, im:-7}); 50 | x.push(IQ{re:4, im:-4}); x.push(IQ{re:6, im:-6}); 51 | } 52 | let t0 = time::precise_time_ns(); 53 | for _ in 0..n_repeats { 54 | cic.process(&x); 55 | } 56 | let t1 = time::precise_time_ns(); 57 | let total_samples = n_samples as f64 * n_repeats as f64; 58 | let total_time = (t1 - t0) as f64 / 1e9; 59 | let throughput = total_samples / total_time; 60 | println!("IQ: {} blocks of {} samples, {:.2}Msps", 61 | n_repeats, n_samples, throughput / 1000000.0_f64); 62 | } 63 | -------------------------------------------------------------------------------- /src/downconverter.rs: -------------------------------------------------------------------------------- 1 | use std::mem::transmute; 2 | use super::IQ; 3 | 4 | /// Downconvert by Fs/4 5 | /// 6 | /// Takes an input stream of 12-bit u16 reals and outputs i16 IQ values, 7 | /// with Fs/4 downconverted to DC. Does not decimate or antialias, so the 8 | /// IQ sample rate equals the input sample rate, and you will likely want to 9 | /// antialias and then decimate by at least 2 afterwards. 10 | /// 11 | /// Input block length must be a multiple of 4 for this process. 12 | #[derive(Default)] 13 | pub struct RealFs4Downconverter; 14 | impl RealFs4Downconverter { 15 | pub fn new() -> RealFs4Downconverter { 16 | RealFs4Downconverter 17 | } 18 | 19 | pub fn process(&self, x: &[u16]) -> Vec> { 20 | let n = x.len(); 21 | assert_eq!(n % 4, 0); 22 | 23 | // Cast input to i16 (we'll clear the sign bit in the loop, in case) 24 | let x: &[i16] = unsafe { transmute(x) }; 25 | 26 | // Allocate output 27 | let mut y: Vec> = Vec::with_capacity(n); 28 | unsafe { y.set_len(n) }; 29 | 30 | // Grab pointers 31 | let ip = &x[0] as *const i16; 32 | let op = &mut y[0] as *mut IQ; 33 | 34 | // Mask and scale to zero-centered i16 values, 35 | // and flip alternate signs to perform digital downconversion by Fs/4. 36 | let mut k = 0isize; 37 | while k < (n as isize) { 38 | unsafe { 39 | (*op.offset(k+0)).re = (*ip.offset(k+0) & 0xFFF) - 2048; 40 | (*op.offset(k+0)).im = 0; 41 | 42 | (*op.offset(k+1)).re = 0; 43 | (*op.offset(k+1)).im = -(*ip.offset(k+1) & 0xFFF) + 2048; 44 | 45 | (*op.offset(k+2)).re = -(*ip.offset(k+2) & 0xFFF) + 2048; 46 | (*op.offset(k+2)).im = 0; 47 | 48 | (*op.offset(k+3)).re = 0; 49 | (*op.offset(k+3)).im = (*ip.offset(k+3) & 0xFFF) - 2048; 50 | } 51 | k += 4; 52 | } 53 | 54 | y 55 | } 56 | } 57 | 58 | #[cfg(test)] 59 | mod tests { 60 | use super::super::IQ; 61 | use super::RealFs4Downconverter; 62 | 63 | #[test] 64 | fn test_downconvert_fs_4() { 65 | let dc = RealFs4Downconverter::new(); 66 | let x = vec!{2049u16, 2047, 2050, 2046, 2051, 2045, 2052, 2044}; 67 | let y = dc.process(&x); 68 | assert_eq!(y, vec!{ 69 | IQ::new(1, 0), IQ::new(0, 1), IQ::new(-2, 0), IQ::new(0, -2), 70 | IQ::new(3, 0), IQ::new(0, 3), IQ::new(-4, 0), IQ::new(0, -4) 71 | }); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/agc.rs: -------------------------------------------------------------------------------- 1 | use num::{Complex}; 2 | 3 | pub trait SampleType: Copy { 4 | fn power(&self) -> f32; 5 | } 6 | 7 | macro_rules! impl_scalar_sampletype { 8 | ($t:ty, $max:expr) => { 9 | impl SampleType for $t { 10 | #[inline] 11 | fn power(&self) -> f32 { 12 | (*self as f32 * *self as f32) / ($max * $max) 13 | } 14 | } 15 | } 16 | } 17 | 18 | macro_rules! impl_complex_sampletype { 19 | ($t:ty, $max:expr) => { 20 | impl SampleType for Complex<$t> { 21 | #[inline] 22 | fn power(&self) -> f32 { 23 | ((*self).im as f32 * (*self).im as f32 + 24 | (*self).re as f32 * (*self).re as f32) 25 | / ($max * $max) 26 | } 27 | } 28 | } 29 | } 30 | 31 | macro_rules! impl_sampletype { 32 | ($t:ty, $max:expr) => { 33 | impl_scalar_sampletype!($t, $max); 34 | impl_complex_sampletype!($t, $max); 35 | } 36 | } 37 | 38 | impl_sampletype!(i8, (1u8 <<7) as f32); 39 | impl_sampletype!(i16, (1u16<<15) as f32); 40 | impl_sampletype!(i32, (1u32<<31) as f32); 41 | impl_sampletype!(i64, (1u64<<63) as f32); 42 | impl_sampletype!(f32, 1.0); 43 | impl_sampletype!(f64, 1.0); 44 | 45 | /// Compute the required gain in dB required to bring the input block `x` 46 | /// up to the reference level `r`, multiplying by sub-unity constant `a` to 47 | /// prevent large shifts. 48 | /// 49 | /// This is a fairly simple and slow implementation which is not suitable for 50 | /// high data rates. 51 | pub fn agc(x: &[T], a: f32, r: f32) -> f32 { 52 | let avg = x.iter().fold(0.0_f32, |s, v| s + v.power()) / x.len() as f32; 53 | let err = 10.0 * r.log10() - 10.0 * avg.log10(); 54 | a * err 55 | } 56 | 57 | #[cfg(test)] 58 | mod tests { 59 | use super::agc; 60 | use num::Complex; 61 | 62 | #[test] 63 | fn test_agc_f32() { 64 | let x: Vec = vec!{0.1, 0.1, 0.1, 0.1}; 65 | let gain = agc(&x, 1.0, 1.0); 66 | assert_eq!(gain, 20.0); 67 | 68 | let rt2 = 2.0_f32.sqrt(); 69 | let x: Vec = vec!{0.1 * rt2, 0.0, -0.1 * rt2, 0.0}; 70 | let gain = agc(&x, 1.0, 1.0); 71 | assert_eq!(gain, 20.0); 72 | 73 | let x: Vec = vec!{0.5, 0.5, 0.5, 0.5}; 74 | let gain = agc(&x, 1.0, 1.0); 75 | assert_eq!(gain, 6.0206003); 76 | 77 | let x: Vec = vec!{0.1, 0.1, 0.1, 0.1}; 78 | let gain = agc(&x, 1.0, 0.5); 79 | assert_eq!(gain, 20.0 - 3.0103); 80 | } 81 | 82 | #[test] 83 | fn test_agc_i16() { 84 | let x: Vec = vec!{3277, 3277, 3277, 3276}; 85 | let gain = agc(&x, 1.0, 1.0); 86 | assert_eq!(gain, 20.000134); 87 | } 88 | 89 | #[test] 90 | fn test_agc_complex() { 91 | let rt2 = 2.0_f32.sqrt(); 92 | let x: Vec> = vec!{ 93 | Complex::new(0.1, 0.0), Complex::new(0.0, 0.1), 94 | Complex::new(-0.1, 0.0), Complex::new(0.0, -0.1), 95 | }; 96 | let gain = agc(&x, 1.0, 1.0); 97 | assert_eq!(gain, 20.0); 98 | 99 | let x: Vec> = vec!{ 100 | Complex::new(0.1/rt2, 0.1/rt2), 101 | Complex::new(0.1/rt2, 0.1/rt2), 102 | Complex::new(0.1/rt2, 0.1/rt2), 103 | Complex::new(0.1/rt2, 0.1/rt2), 104 | }; 105 | let gain = agc(&x, 1.0, 1.0); 106 | assert_eq!(gain, 20.0); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /examples/fir.rs: -------------------------------------------------------------------------------- 1 | extern crate time; 2 | extern crate sdr; 3 | use sdr::FIR; 4 | use sdr::IQ; 5 | 6 | #[cfg_attr(test, allow(dead_code))] 7 | fn main() { 8 | bench_scalar_fixed(); 9 | bench_scalar_float(); 10 | bench_complex_fixed(); 11 | bench_complex_floating(); 12 | } 13 | 14 | #[cfg_attr(test, allow(dead_code))] 15 | fn bench_scalar_fixed() { 16 | let q = 5usize; 17 | let r = 16usize; 18 | let n_taps = 63; 19 | let n_samples = 1<<24 as usize; 20 | let n_repeats = 10u64; 21 | 22 | let mut fir = FIR::cic_compensator(n_taps, q, r, 2); 23 | let mut x: Vec = Vec::with_capacity(n_samples); 24 | for _ in 0..(n_samples/8) { 25 | x.push(3); x.push(7); x.push(4); x.push(6); 26 | x.push(3); x.push(7); x.push(4); x.push(6); 27 | } 28 | let t0 = time::precise_time_ns(); 29 | for _ in 0..n_repeats { 30 | fir.process(&x); 31 | } 32 | let t1 = time::precise_time_ns(); 33 | let total_samples = n_samples as f64 * n_repeats as f64; 34 | let total_time = (t1 - t0) as f64 / 1e9; 35 | let throughput = total_samples / total_time; 36 | println!("i16: {} blocks of {} samples, {:.2}Msps", 37 | n_repeats, n_samples, throughput / 1000000.0_f64); 38 | } 39 | 40 | #[cfg_attr(test, allow(dead_code))] 41 | fn bench_scalar_float() { 42 | let q = 5usize; 43 | let r = 16usize; 44 | let n_taps = 63; 45 | let n_samples = 1<<24 as usize; 46 | let n_repeats = 10u64; 47 | 48 | let mut fir = FIR::cic_compensator(n_taps, q, r, 2); 49 | let mut x: Vec = Vec::with_capacity(n_samples); 50 | for _ in 0..(n_samples/8) { 51 | x.push(3.0); x.push(7.0); x.push(4.0); x.push(6.0); 52 | x.push(3.0); x.push(7.0); x.push(4.0); x.push(6.0); 53 | } 54 | let t0 = time::precise_time_ns(); 55 | for _ in 0..n_repeats { 56 | fir.process(&x); 57 | } 58 | let t1 = time::precise_time_ns(); 59 | let total_samples = n_samples as f64 * n_repeats as f64; 60 | let total_time = (t1 - t0) as f64 / 1e9; 61 | let throughput = total_samples / total_time; 62 | println!("f32: {} blocks of {} samples, {:.2}Msps", 63 | n_repeats, n_samples, throughput / 1000000.0_f64); 64 | } 65 | 66 | #[cfg_attr(test, allow(dead_code))] 67 | fn bench_complex_fixed() { 68 | let q = 5usize; 69 | let r = 16usize; 70 | let n_taps = 63; 71 | let n_samples = 1<<24 as usize; 72 | let n_repeats = 10u64; 73 | 74 | let mut fir = FIR::cic_compensator(n_taps, q, r, 2); 75 | let mut x: Vec> = Vec::with_capacity(n_samples); 76 | for _ in 0..(n_samples/8) { 77 | x.push(IQ{re:3, im:-3}); x.push(IQ{re:7, im:-7}); 78 | x.push(IQ{re:4, im:-4}); x.push(IQ{re:6, im:-6}); 79 | x.push(IQ{re:3, im:-3}); x.push(IQ{re:7, im:-7}); 80 | x.push(IQ{re:4, im:-4}); x.push(IQ{re:6, im:-6}); 81 | } 82 | let t0 = time::precise_time_ns(); 83 | for _ in 0..n_repeats { 84 | fir.process(&x); 85 | } 86 | let t1 = time::precise_time_ns(); 87 | let total_samples = n_samples as f64 * n_repeats as f64; 88 | let total_time = (t1 - t0) as f64 / 1e9; 89 | let throughput = total_samples / total_time; 90 | println!("IQ: {} blocks of {} samples, {:.2}Msps", 91 | n_repeats, n_samples, throughput / 1000000.0_f64); 92 | } 93 | 94 | #[cfg_attr(test, allow(dead_code))] 95 | fn bench_complex_floating() { 96 | let q = 5usize; 97 | let r = 16usize; 98 | let n_taps = 63; 99 | let n_samples = 1<<24 as usize; 100 | let n_repeats = 10u64; 101 | 102 | let mut fir = FIR::cic_compensator(n_taps, q, r, 2); 103 | let mut x: Vec> = Vec::with_capacity(n_samples); 104 | for _ in 0..(n_samples/8) { 105 | x.push(IQ{re:3.0, im:-3.0}); x.push(IQ{re:7.0, im:-7.0}); 106 | x.push(IQ{re:4.0, im:-4.0}); x.push(IQ{re:6.0, im:-6.0}); 107 | x.push(IQ{re:3.0, im:-3.0}); x.push(IQ{re:7.0, im:-7.0}); 108 | x.push(IQ{re:4.0, im:-4.0}); x.push(IQ{re:6.0, im:-6.0}); 109 | } 110 | let t0 = time::precise_time_ns(); 111 | for _ in 0..n_repeats { 112 | fir.process(&x); 113 | } 114 | let t1 = time::precise_time_ns(); 115 | let total_samples = n_samples as f64 * n_repeats as f64; 116 | let total_time = (t1 - t0) as f64 / 1e9; 117 | let throughput = total_samples / total_time; 118 | println!("IQ: {} blocks of {} samples, {:.2}Msps", 119 | n_repeats, n_samples, throughput / 1000000.0_f64); 120 | } 121 | -------------------------------------------------------------------------------- /src/fm.rs: -------------------------------------------------------------------------------- 1 | use num::{Complex, Num, Zero}; 2 | 3 | pub type IQ = Complex; 4 | 5 | /// A trait for types the FM Demodulator can process. 6 | /// Always IQ in and out. 7 | pub trait SampleType: Copy + Num + Zero { 8 | type Reg: Copy + Num + Zero; 9 | unsafe fn set_reg(x: *const Complex, reg: *mut Complex); 10 | unsafe fn set_out(d0re: *const Self::Reg, d0im: *const Self::Reg, 11 | d1re: *const Self::Reg, d1im: *const Self::Reg, 12 | d2re: *const Self::Reg, d2im: *const Self::Reg, 13 | op: *mut Self); 14 | } 15 | 16 | macro_rules! impl_fixed_sampletype { 17 | ($t: ty, $tt: ty, $bits: expr) => { 18 | impl SampleType for $t { 19 | type Reg = $tt; 20 | 21 | #[inline] 22 | unsafe fn set_reg(x: *const Complex<$t>, reg: *mut Complex<$tt>) { 23 | (*reg).re = (*x).re as $tt; 24 | (*reg).im = (*x).im as $tt; 25 | } 26 | 27 | #[inline] 28 | unsafe fn set_out(d0re: *const Self::Reg, d0im: *const Self::Reg, 29 | d1re: *const Self::Reg, d1im: *const Self::Reg, 30 | d2re: *const Self::Reg, d2im: *const Self::Reg, 31 | op: *mut Self) { 32 | let top = *d1re * (*d0im - *d2im) - *d1im * (*d0re - *d2re); 33 | let bottom = (*d1re * *d1re + *d1im * *d1im) >> $bits; 34 | if bottom == 0 { 35 | *op = top as $t; 36 | } else { 37 | *op = (top / bottom) as $t; 38 | } 39 | } 40 | } 41 | } 42 | } 43 | 44 | macro_rules! impl_floating_sampletype { 45 | ($t: ty) => { 46 | impl SampleType for $t { 47 | type Reg = $t; 48 | #[inline] 49 | unsafe fn set_reg(x: *const Complex<$t>, reg: *mut Complex<$t>) { 50 | *reg = *x; 51 | } 52 | 53 | #[inline] 54 | unsafe fn set_out(d0re: *const Self::Reg, d0im: *const Self::Reg, 55 | d1re: *const Self::Reg, d1im: *const Self::Reg, 56 | d2re: *const Self::Reg, d2im: *const Self::Reg, 57 | op: *mut Self) { 58 | let top = *d1re * (*d0im - *d2im) - *d1im * (*d0re - *d2re); 59 | let bottom = *d1re * *d1re + *d1im * *d1im; 60 | if bottom == 0.0 { 61 | *op = top; 62 | } else { 63 | *op = top / bottom; 64 | } 65 | } 66 | } 67 | } 68 | } 69 | 70 | impl_fixed_sampletype!(i8, i16, 8-2); 71 | impl_fixed_sampletype!(i16, i32, 16-2); 72 | impl_fixed_sampletype!(i32, i64, 32-2); 73 | impl_floating_sampletype!(f32); 74 | impl_floating_sampletype!(f64); 75 | 76 | /// FM signal demodulator 77 | #[derive(Default)] 78 | pub struct FMDemod { 79 | d: [IQ; 3] 80 | } 81 | 82 | impl FMDemod { 83 | /// Create a new FM demodulator 84 | pub fn new() -> FMDemod { 85 | let d: [IQ; 3] = [IQ::new(T::Reg::zero(), T::Reg::zero()); 3]; 86 | FMDemod{d } 87 | } 88 | 89 | /// FM demodulate input block x, containing baseband IQ data that has been 90 | /// frequency modulated. Outputs a block of real-valued samples. 91 | pub fn process(&mut self, x: &[IQ]) -> Vec { 92 | let n = x.len(); 93 | let mut d = self.d; 94 | 95 | // Allocate output 96 | let mut out: Vec = Vec::with_capacity(n); 97 | unsafe { out.set_len(n) }; 98 | 99 | // Grab pointers to everything for speedy access 100 | let xp = &x[0] as *const IQ; 101 | let op = &mut out[0] as *mut T; 102 | let d0 = &mut d[0] as *mut IQ; 103 | let d1 = &mut d[1] as *mut IQ; 104 | let d2 = &mut d[2] as *mut IQ; 105 | let d0re = &d[0].re as *const T::Reg; 106 | let d1re = &d[1].re as *const T::Reg; 107 | let d2re = &d[2].re as *const T::Reg; 108 | let d0im = &d[0].im as *const T::Reg; 109 | let d1im = &d[1].im as *const T::Reg; 110 | let d2im = &d[2].im as *const T::Reg; 111 | 112 | // FM demodulate. Instantaneous phase is approximated by 113 | // a three-long differentiator doing a multiplication with the 114 | // old conjugate signal, around the point of interest. 115 | // 116 | // Normalised by incoming signal magnitude, but this can go wrong 117 | // for zero valued signals so we also add a "tiny" delta. 118 | for i in 0isize..(n as isize) { 119 | unsafe { 120 | *d2 = *d1; 121 | *d1 = *d0; 122 | T::set_reg(xp.offset(i), d0); 123 | T::set_out(d0re, d0im, d1re, d1im, d2re, d2im, op.offset(i)); 124 | } 125 | } 126 | 127 | // Save differentiator state for next time 128 | self.d = d; 129 | 130 | out 131 | } 132 | } 133 | 134 | #[test] 135 | fn test_fm_demod() { 136 | let mut x: Vec> = Vec::with_capacity(100); 137 | for t in 0..25 { 138 | x.push(IQ::new( 139 | (0.1*(t as f32)).cos(), 140 | (0.1*(t as f32)).sin() 141 | )); 142 | } 143 | for t in 25..50 { 144 | x.push(IQ::new( 145 | (0.3*(t as f32)).cos(), 146 | (0.3*(t as f32)).sin() 147 | )); 148 | } 149 | for t in 50..75 { 150 | x.push(IQ::new( 151 | (0.2*(t as f32)).cos(), 152 | (0.2*(t as f32)).sin() 153 | )); 154 | } 155 | for t in 75..100 { 156 | x.push(IQ::new( 157 | (0.4*(t as f32)).cos(), 158 | (0.4*(t as f32)).sin() 159 | )); 160 | } 161 | let mut demod = FMDemod::new(); 162 | let y = demod.process(&x); 163 | let z: Vec = y.iter().map(|y| (y * 10.0) as i32).collect(); 164 | assert_eq!(z, vec!{ 165 | 0, 0, 166 | 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 167 | -8, -6, 168 | 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 169 | 12, 11, 170 | 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 171 | 6, 8, 172 | 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7, 7}); 173 | } 174 | -------------------------------------------------------------------------------- /src/cic.rs: -------------------------------------------------------------------------------- 1 | use num::{Zero, Complex}; 2 | 3 | /// Implement the underlying operations and types required by allowable CIC 4 | /// sample types. Must be some form of wrapping arithmetic. 5 | pub trait SampleType: Copy { 6 | type RegType: Zero + Copy; 7 | fn check_width(q: usize, r: usize, bw: usize); 8 | fn add(a: Self::RegType, b: Self::RegType) -> Self::RegType; 9 | fn sub(a: Self::RegType, b: Self::RegType) -> Self::RegType; 10 | fn to_reg(x: Self) -> Self::RegType; 11 | fn from_out(out: Self::RegType, gain_shift: usize) -> Self; 12 | } 13 | 14 | /// Implement `SampleType` for scalars like i16. 15 | /// $t is the sample type, $tt the register type, 16 | /// $bw_t the bit width of type t, and $bw_tt the bit width of $tt. 17 | macro_rules! impl_scalar_sampletype { 18 | ($t:ty, $tt:ty) => { 19 | impl SampleType for $t { 20 | type RegType = $tt; 21 | #[inline] fn check_width(q: usize, r: usize, bw: usize) { 22 | let bw = bw as f32; 23 | let bw_t = <$t>::zero().count_zeros() as f32; 24 | let bw_tt = <$tt>::zero().count_zeros() as f32; 25 | assert!(bw <= bw_t); 26 | assert!(bw + ((q as f32) * (r as f32).log2()).ceil() <= bw_tt); 27 | } 28 | #[inline] fn add(a: $tt, b: $tt) -> $tt { 29 | a.wrapping_add(b) 30 | } 31 | #[inline] fn sub(a: $tt, b: $tt) -> $tt { 32 | a.wrapping_sub(b) 33 | } 34 | #[inline] fn to_reg(x: $t) -> $tt { 35 | x as $tt 36 | } 37 | #[inline] fn from_out(out: $tt, gain_shift: usize) -> $t { 38 | (out >> gain_shift) as $t 39 | } 40 | } 41 | } 42 | } 43 | 44 | /// Implement `SampleType` for complex samples like `Complex`. 45 | /// $t is the sample type, $tt the register type, 46 | /// $bw_t the bit width of type t, and $bw_tt the bit width of $tt. 47 | macro_rules! impl_complex_sampletype { 48 | ($t:ty, $tt:ty) => { 49 | impl SampleType for Complex<$t> { 50 | type RegType = Complex<$tt>; 51 | #[inline] fn check_width(q: usize, r: usize, bw: usize) { 52 | let bw = bw as f32; 53 | let bw_t = <$t>::zero().count_zeros() as f32; 54 | let bw_tt = <$tt>::zero().count_zeros() as f32; 55 | assert!(bw <= bw_t); 56 | assert!(bw + ((q as f32) * (r as f32).log2()).ceil() <= bw_tt); 57 | } 58 | #[inline] 59 | fn add(a: Complex<$tt>, b: Complex<$tt>) -> Complex<$tt> { 60 | Complex{ re: a.re.wrapping_add(b.re), 61 | im: a.im.wrapping_add(b.im) } 62 | } 63 | #[inline] 64 | fn sub(a: Complex<$tt>, b: Complex<$tt>) -> Complex<$tt> { 65 | Complex{ re: a.re.wrapping_sub(b.re), 66 | im: a.im.wrapping_sub(b.im) } 67 | } 68 | #[inline] fn to_reg(x: Complex<$t>) -> Complex<$tt> { 69 | Complex{ re: x.re as $tt, im: x.im as $tt } 70 | } 71 | #[inline] fn from_out(out: Complex<$tt>, shift: usize) 72 | -> Complex<$t> 73 | { 74 | Complex{ re: (out.re >> shift) as $t, 75 | im: (out.im >> shift) as $t } 76 | } 77 | } 78 | } 79 | } 80 | 81 | macro_rules! impl_sampletype { 82 | ($t:ty, $tt:ty) => { 83 | impl_scalar_sampletype!($t, $tt); 84 | impl_complex_sampletype!($t, $tt); 85 | } 86 | } 87 | 88 | impl_sampletype!(i8, i16); 89 | impl_sampletype!(i16, i32); 90 | impl_sampletype!(i32, i64); 91 | 92 | /// Qth order CIC filter with decimate-by-R (and D=R). 93 | pub struct CIC { 94 | q: usize, 95 | r: usize, 96 | intg: Vec, 97 | comb: Vec, 98 | gain_shift: usize 99 | } 100 | 101 | impl CIC { 102 | /// Create a new CIC filter of order q and downsampling ratio r. 103 | /// 104 | /// Bitwidth is the number of bits actually used by your input data, 105 | /// so must be equal to or less than the size of the data type. 106 | /// 107 | /// Note that bitwidth + ceil(Q log2 R) must <= 32. 108 | pub fn new(q: usize, r: usize, bw: usize) -> CIC { 109 | // Verify that bw + ceil(Q log2 R) <= reg width. 110 | T::check_width(q, r, bw); 111 | 112 | // Allocate the integrator and comb registers. 113 | let mut intg = Vec::with_capacity(q); 114 | let mut comb = Vec::with_capacity(q+1); 115 | 116 | for _ in 0..q { 117 | intg.push(T::RegType::zero()); 118 | comb.push(T::RegType::zero()); 119 | } 120 | 121 | // One final comb register for holding the output. 122 | comb.push(T::RegType::zero()); 123 | 124 | // Compute the filter gain, Q**R, as an equivalent bit shift 125 | let gain_shift: usize = (q as f32 * (r as f32).log2()).ceil() as usize; 126 | 127 | CIC { q, r, intg, comb, gain_shift } 128 | } 129 | 130 | /// Run the CIC filter over a block x, 131 | /// returning the filtered and decimated output. 132 | pub fn process(&mut self, x: &[T]) -> Vec { 133 | // Check we were initialised correctly 134 | assert!(self.q > 0 && self.r > 0); 135 | assert_eq!(self.intg.len(), self.q); 136 | assert_eq!(self.comb.len(), self.q + 1); 137 | 138 | // To decimate by R we need a multiple of R in the input. 139 | assert_eq!(x.len() % self.r, 0); 140 | 141 | // Output will have 1/R the number of samples of the input. 142 | let mut y: Vec = Vec::with_capacity(x.len() / self.r); 143 | unsafe { y.set_len(x.len() / self.r) }; 144 | 145 | // Hold on to some pointers for more dangerous access later. 146 | let intg_p = &mut self.intg[0] as *mut T::RegType; 147 | let comb_p = &mut self.comb[0] as *mut T::RegType; 148 | let out_p = &mut y[0] as *mut T; 149 | let in_p = &x[0] as *const T; 150 | let r = self.r as isize; 151 | let q = self.q as isize; 152 | let ylen = y.len() as isize; 153 | let gain_shift = self.gain_shift; 154 | 155 | // Push samples through the chain. 156 | // It's a fair bit faster to loop over the outputs and have a tighter 157 | // loop over the inputs. 158 | for k in 0..ylen { 159 | for o in 0..r { 160 | // Add up the integrators. Note that this cascaded approach 161 | // adds additional time delay (but not much!) but is easier to 162 | // compute. 163 | unsafe { 164 | *intg_p = 165 | T::add(*intg_p, T::to_reg(*in_p.offset(k*r + o))); 166 | 167 | for l in 1isize..q { 168 | *intg_p.offset(l) = 169 | T::add(*intg_p.offset(l), *intg_p.offset(l - 1)); 170 | } 171 | 172 | } 173 | } 174 | 175 | // Run the comb section at 1/R the sample rate 176 | // Each comb register is set to the output of the decimator, 177 | // minus all the combs before itself 178 | for l in 0..(q + 1) { 179 | let l = q - l; 180 | unsafe { 181 | *comb_p.offset(l) = *intg_p.offset(q - 1); 182 | 183 | for m in 0isize..l { 184 | *comb_p.offset(l) = 185 | T::sub(*comb_p.offset(l), *comb_p.offset(m)); 186 | } 187 | } 188 | } 189 | 190 | // The output is the final "output" comb register, scaled to 191 | // give unity filter gain. 192 | unsafe { 193 | *out_p.offset(k) = T::from_out(*comb_p.offset(q), gain_shift); 194 | } 195 | } 196 | 197 | y 198 | } 199 | } 200 | 201 | #[cfg(test)] 202 | mod tests { 203 | use super::CIC; 204 | use num::Complex; 205 | 206 | /// Try 1st order R=2 CIC, a simple moving average filter in practice 207 | #[test] 208 | fn test_cic_1_2() { 209 | let mut cic = CIC::new(1, 2, 12); 210 | let x = vec!{1i16, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; 211 | let y = cic.process(&x); 212 | assert_eq!(y, vec!{1i16, 1, 1, 1, 1, 1, 1, 1}); 213 | 214 | let x = vec!{1i16, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3}; 215 | let mut cic = CIC::new(1, 2, 12); 216 | let y = cic.process(&x); 217 | assert_eq!(y, vec!{2i16, 2, 2, 2, 2, 2, 2, 2}); 218 | } 219 | 220 | /// Try a bigger filter 221 | #[test] 222 | fn test_cic_2_4() { 223 | let x = vec!{1i16, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3}; 224 | let mut cic = CIC::new(2, 4, 12); 225 | let y = cic.process(&x); 226 | assert_eq!(y, vec!{1i16, 2, 2, 2}); 227 | } 228 | 229 | /// Now we're cooking with gas! 230 | #[test] 231 | fn test_cic_5_8() { 232 | let mut x: Vec = Vec::with_capacity(8*8); 233 | for _ in 0..16 { 234 | x.push(2); 235 | x.push(4); 236 | x.push(6); 237 | x.push(4); 238 | } 239 | let mut cic = CIC::new(5, 8, 12); 240 | let y = cic.process(&x); 241 | assert_eq!(y, vec!{0i16, 1, 3, 3, 4, 4, 4, 4}); 242 | } 243 | 244 | /// Try a different sample size 245 | #[test] 246 | fn test_cic_i32() { 247 | let x = vec!{1i32, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3, 1, 3}; 248 | let mut cic = CIC::new(2, 4, 5); 249 | let y = cic.process(&x); 250 | assert_eq!(y, vec!{1i32, 2, 2, 2}); 251 | } 252 | 253 | /// Try a complex sample 254 | #[test] 255 | fn test_cic_complex() { 256 | let x = vec!{ 257 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 258 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 259 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 260 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 261 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 262 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 263 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 264 | Complex{ re: 1i16, im: 4i16}, Complex{ re: 3i16, im: 8i16}, 265 | }; 266 | let mut cic = CIC::new(2, 4, 5); 267 | let y = cic.process(&x); 268 | assert_eq!(y, vec!{ 269 | Complex{ re: 1i16, im: 3i16 }, 270 | Complex{ re: 2i16, im: 6i16 }, 271 | Complex{ re: 2i16, im: 6i16 }, 272 | Complex{ re: 2i16, im: 6i16 }, 273 | }); 274 | } 275 | } 276 | -------------------------------------------------------------------------------- /src/fir.rs: -------------------------------------------------------------------------------- 1 | use num::{Num, NumCast, Zero, Complex}; 2 | use std::f64::consts::PI; 3 | use std::f64; 4 | 5 | /// Implement the underlying operations and types required by the FIR. 6 | /// 7 | /// In general the `AccType` should be larger than the `SampleType` but compatible 8 | /// (i.e. you can add a `SampleType` to an `AccType`), while the `TapType` should 9 | /// always be scalar and is usually the same size as the `AccType`. 10 | /// 11 | /// gain() specifies the gain of the filter, i.e. the sum of all the taps, 12 | /// divided by the interpolation level L. 13 | /// input() copies an item from the input into a given delay line element. 14 | /// accumulate() updates an accumulator with a delay line element * a tap. 15 | /// output() writes to the output array from an accumulator. 16 | pub trait SampleType: Copy { 17 | type AccType: Zero + Copy; 18 | type TapType: Num + NumCast + Zero + Copy; 19 | fn gain(interpolate: usize) -> Self::TapType; 20 | unsafe fn input(x: *const Self, delay: *mut Self::AccType); 21 | unsafe fn accumulate(acc: &mut Self::AccType, delay: *const Self::AccType, 22 | tap: *const Self::TapType); 23 | unsafe fn output(acc: Self::AccType, out: *mut Self, gain: Self::TapType); 24 | } 25 | 26 | /// Implement `SampleType` for a scalar type such as i16 or f32. 27 | /// $t is the sample type, $tt the acc/tap type and $tapsum the filter gain. 28 | macro_rules! impl_scalar_sampletype { 29 | ($t:ty, $tt:ty, $tapsum:expr) => { 30 | impl SampleType for $t { 31 | type AccType = $tt; 32 | type TapType = $tt; 33 | 34 | #[inline] 35 | fn gain(interpolate: usize) -> $tt { $tapsum / interpolate as $tt } 36 | 37 | #[inline] 38 | unsafe fn input(x: *const $t, delay: *mut $tt) { 39 | *delay = *x as $tt; 40 | } 41 | 42 | #[inline] 43 | unsafe fn accumulate(acc: &mut $tt, delay: *const $tt, 44 | tap: *const $tt) { 45 | *acc += *delay * *tap; 46 | } 47 | 48 | #[inline] 49 | unsafe fn output(acc: $tt, out: *mut $t, gain: $tt) { 50 | *out = (acc / gain) as $t 51 | } 52 | } 53 | } 54 | } 55 | 56 | /// Implement `SampleType` for a Complex type such as Complex. 57 | /// $t is the sample type, $tt the acc/tap type and $tapsum the filter gain. 58 | macro_rules! impl_complex_sampletype { 59 | ($t:ty, $tt:ty, $tapsum:expr) => { 60 | impl SampleType for Complex<$t> { 61 | type AccType = Complex<$tt>; 62 | type TapType = $tt; 63 | 64 | #[inline] 65 | fn gain(interpolate: usize) -> $tt { $tapsum / interpolate as $tt } 66 | 67 | #[inline] 68 | unsafe fn input(x: *const Complex<$t>, delay: *mut Complex<$tt>) { 69 | (*delay).re = (*x).re as $tt; 70 | (*delay).im = (*x).im as $tt; 71 | } 72 | 73 | #[inline] 74 | unsafe fn accumulate(acc: &mut Complex<$tt>, 75 | delay: *const Complex<$tt>, tap: *const $tt) { 76 | (*acc).re += (*delay).re * *tap; 77 | (*acc).im += (*delay).im * *tap; 78 | } 79 | 80 | #[inline] 81 | unsafe fn output(acc: Complex<$tt>, out: *mut Complex<$t>, 82 | gain: $tt) { 83 | (*out).re = (acc.re / gain) as $t; 84 | (*out).im = (acc.im / gain) as $t; 85 | } 86 | } 87 | } 88 | } 89 | 90 | /// Implement Scalar and Complex `SampleTypes` for the same underlying types. 91 | macro_rules! impl_sampletype { 92 | ($t:ty, $tt:ty, $tapsum:expr) => { 93 | impl_scalar_sampletype!($t, $tt, $tapsum); 94 | impl_complex_sampletype!($t, $tt, $tapsum); 95 | } 96 | } 97 | 98 | impl_sampletype!(i8, i16, 1<<7 ); 99 | impl_sampletype!(i16, i32, 1<<15); 100 | impl_sampletype!(i32, i64, 1<<31); 101 | impl_sampletype!(f32, f64, 1.0); 102 | 103 | /// FIR filter. 104 | pub struct FIR { 105 | taps: Vec, 106 | tap_idx: isize, 107 | delay: Vec, 108 | delay_idx: isize, 109 | leftover: usize, 110 | decimate: usize, 111 | interpolate: usize, 112 | } 113 | 114 | impl FIR { 115 | /// Create a new FIR with the given taps and decimation. 116 | /// 117 | /// Taps should sum to T::gain() or close to it. 118 | /// 119 | /// Set decimate=1 for no decimation, decimate=2 for /2, etc. 120 | /// Set interpolate=1 for no interpolation, interplate=2 for *2, etc. 121 | /// Note that if the number of taps is not a multiple of the interpolation 122 | /// ratio then they will be zero-padded at the end until they are. 123 | /// 124 | /// Implements a polyphase FIR to do efficient decimation and interpolation 125 | /// (identical to a standard FIR when interpolate=1). 126 | pub fn new(taps: &[T::TapType], decimate: usize, interpolate: usize) 127 | -> FIR 128 | { 129 | assert!(!taps.is_empty()); 130 | 131 | // Copy the taps and zero-pad to get a multiple of interpolation ratio 132 | let mut taps: Vec = taps.to_owned(); 133 | if taps.len() % interpolate != 0 { 134 | for _ in 0..(interpolate - (taps.len() % interpolate)) { 135 | taps.push(T::TapType::zero()); 136 | } 137 | } 138 | 139 | // Set up and fill the delay line with zeros 140 | let n_delay = taps.len() / interpolate; 141 | let mut delay: Vec = Vec::with_capacity(n_delay); 142 | for _ in 0..n_delay { 143 | delay.push(T::AccType::zero()); 144 | } 145 | 146 | FIR { taps, tap_idx: interpolate as isize - 1isize, 147 | delay, delay_idx: 0isize, leftover: 0usize, 148 | decimate, interpolate } 149 | } 150 | 151 | /// Create a new FIR from a number of taps, desired frequency response 152 | /// (a 512-long vector from 0 to Nyquist freq) and decimation. 153 | /// 154 | /// Set decimate=1 for no decimation, decimate=2 for /2, etc. 155 | /// Set interpolate=1 for no interpolation, interplate=2 for *2, etc. 156 | pub fn from_gains(n_taps: usize, gains: &[f64], 157 | decimate: usize, interpolate: usize) 158 | -> FIR 159 | { 160 | let taps = firwin2(n_taps, gains); 161 | let taps = quantise_taps::(&taps, T::gain(1)); 162 | FIR::new(&taps, decimate, interpolate) 163 | } 164 | 165 | /// Create a new FIR that compensates for a CIC filter specified by 166 | /// q and r, optionally also cutting off the frequency response after 167 | /// Fs/(2*decimate) and having the FIR decimate by that factor. 168 | /// 169 | /// Set decimate=1 for no decimation, decimate=2 for /2, etc. 170 | /// 171 | /// TODO: Does not quite match results obtained in Python, with slightly 172 | /// worse simulated performance. Investigate. 173 | pub fn cic_compensator(n_taps: usize, q: usize, r: usize, decimate: usize) 174 | -> FIR 175 | { 176 | let q = q as i32; 177 | let r = r as f64; 178 | let f: Vec = (0..512).map(|x| 179 | x as f64 / (16.0_f64 * 512.0_f64)).collect(); 180 | let mut h: Vec = f.iter().map(|f| 181 | ((PI * f * r).sin() / (r * (PI * f).sin())).abs().powi(q).recip()) 182 | .collect(); 183 | h[0] = 1.0_f64; 184 | h[511] = 0.0_f64; 185 | if decimate > 1 { 186 | for n in (512/decimate - 51/decimate)..512 { 187 | h[n] = 0.0_f64; 188 | } 189 | } 190 | FIR::from_gains(n_taps, &h, decimate, 1) 191 | } 192 | 193 | /// Create a new FIR that implements a specified raised cosine filter, 194 | /// with rolloff factor b, reciprocal symbol rate t in normalised time 195 | /// (i.e. samples per symbol), and half-width w (in bit periods, i.e. how 196 | /// many periods of t to extend either side of centre). 197 | pub fn from_rcf(b: f64, t: usize, w: usize) -> FIR { 198 | assert!(b >= 0.0_f64 && b <= 1.0_f64); 199 | let t = t as isize; 200 | let w = w as isize; 201 | let n_taps = 2 * t * w + 1; 202 | let mut taps: Vec = Vec::with_capacity(n_taps as usize); 203 | for i in (-t*w)..(t*w + 1) { 204 | let x = i as f64 / t as f64; 205 | let mut y: f64; 206 | 207 | if i == 0 { 208 | y = 1.0; 209 | } else if i % t == 0 { 210 | y = 0.0; 211 | } else { 212 | y = (PI * x).sin() / (PI * x); 213 | y *= (PI * b * x).cos(); 214 | y /= 1.0 - (4.0 * b*b * x*x); 215 | } 216 | taps.push(y); 217 | } 218 | let taps = quantise_taps::(&taps, T::gain(1)); 219 | FIR::new(&taps, 1, 1) 220 | } 221 | 222 | /// Create a new FIR resampler, with frequency response suitable for the 223 | /// resampling ratio. n_taps should ideally be a multiple of interpolate 224 | /// (but will be zero-padded at the end if not). 225 | pub fn resampler(n_taps: usize, decimate: usize, interpolate: usize) 226 | -> FIR 227 | { 228 | let fc = 512 / ::std::cmp::max(decimate, interpolate); 229 | let mut gains: Vec = Vec::with_capacity(512); 230 | for _ in 0..fc { 231 | gains.push(1.0); 232 | } 233 | for _ in fc..512 { 234 | gains.push(0.0); 235 | } 236 | FIR::from_gains(n_taps, &gains, decimate, interpolate) 237 | } 238 | 239 | /// Create a new FIR low pass filter with specified cutoff frequency (in 240 | /// normalised frequency, e.g. in the range 0.0 to 0.5). 241 | pub fn lowpass(n_taps: usize, cutoff: f64) -> FIR 242 | { 243 | let fc = (512.0 * 2.0 * cutoff) as usize; 244 | let mut gains: Vec = Vec::with_capacity(512); 245 | for _ in 0..fc { 246 | gains.push(1.0); 247 | } 248 | for _ in fc..512 { 249 | gains.push(0.0); 250 | } 251 | FIR::from_gains(n_taps, &gains, 1, 1) 252 | } 253 | 254 | /// Create a new FIR high pass filter with specified cutoff frequency (in 255 | /// normalised frequency, e.g. in the range 0.0 to 0.5). 256 | pub fn highpass(n_taps: usize, cutoff: f64) -> FIR 257 | { 258 | // Must have an odd number of taps for a high pass filter 259 | assert_eq!(n_taps % 2, 1); 260 | 261 | let fc = (512.0 * 2.0 * cutoff) as usize; 262 | let mut gains: Vec = Vec::with_capacity(512); 263 | for _ in 0..fc { 264 | gains.push(0.0); 265 | } 266 | for _ in fc..512 { 267 | gains.push(1.0); 268 | } 269 | FIR::from_gains(n_taps, &gains, 1, 1) 270 | } 271 | 272 | /// Create a new FIR band pass filter with specified passband frequencies 273 | /// (in normalised frequency, e.g. in the range 0.0 to 0.5). 274 | pub fn bandpass(n_taps: usize, f_low: f64, f_high: f64) -> FIR 275 | { 276 | // Band must be well defined 277 | assert!(f_low < f_high); 278 | let fl = (512.0 * 2.0 * f_low) as usize; 279 | let fh = (512.0 * 2.0 * f_high) as usize; 280 | let mut gains: Vec = Vec::with_capacity(512); 281 | for _ in 0..fl { 282 | gains.push(0.0); 283 | } 284 | for _ in fl..fh { 285 | gains.push(1.0); 286 | } 287 | for _ in fh..512 { 288 | gains.push(0.0); 289 | } 290 | FIR::from_gains(n_taps, &gains, 1, 1) 291 | } 292 | 293 | /// Create a new FIR band stop filter with specified stopband frequencies 294 | /// (in normalised frequency, e.g. in the range 0.0 to 0.5). 295 | pub fn bandstop(n_taps: usize, f_low: f64, f_high: f64) -> FIR 296 | { 297 | // Must have an odd number of taps for a high pass filter 298 | assert_eq!(n_taps % 2, 1); 299 | // Band must be well defined 300 | assert!(f_low < f_high); 301 | let fl = (512.0 * 2.0 * f_low) as usize; 302 | let fh = (512.0 * 2.0 * f_high) as usize; 303 | let mut gains: Vec = Vec::with_capacity(512); 304 | for _ in 0..fl { 305 | gains.push(1.0); 306 | } 307 | for _ in fl..fh { 308 | gains.push(0.0); 309 | } 310 | for _ in fh..512 { 311 | gains.push(1.0); 312 | } 313 | FIR::from_gains(n_taps, &gains, 1, 1) 314 | } 315 | 316 | /// Return a reference to the filter's taps. 317 | pub fn taps(&self) -> &Vec { 318 | &self.taps 319 | } 320 | 321 | /// Process a block of data x, outputting the filtered and possibly 322 | /// decimated data. 323 | pub fn process(&mut self, x: &[T]) -> Vec { 324 | // Check we were initialised correctly and 325 | // ensure invariances required for unsafe code. 326 | assert!(self.taps.len() % self.interpolate == 0); 327 | assert!(self.delay.len() == self.taps.len() / self.interpolate); 328 | assert!(self.delay_idx < self.delay.len() as isize); 329 | assert!(self.tap_idx < self.interpolate as isize); 330 | 331 | // Quit early if we have an empty input 332 | if x.is_empty() { 333 | return Vec::new(); 334 | } 335 | 336 | // Allocate output. 337 | let ylen: usize = ((x.len() * self.interpolate) + self.leftover) / 338 | self.decimate; 339 | let mut y: Vec = Vec::with_capacity(ylen); 340 | unsafe { y.set_len(ylen) }; 341 | 342 | // y might be 0-length, so can't just grab &y[0]. 343 | // Instead do this palaver. out_p from the `else` branch won't be used. 344 | let mut out_p: *mut T = 345 | if ylen > 0 { 346 | &mut y[0] as *mut T 347 | } else { 348 | &mut vec!{x[0]}[0] as *mut T 349 | }; 350 | 351 | // Grab pointers to, and local copies of, various things 352 | let decimate = self.decimate as isize; 353 | let interpolate = self.interpolate as isize; 354 | let mut tickstart = self.leftover as isize; 355 | let mut delay_idx = self.delay_idx; 356 | let mut tap_idx = self.tap_idx; 357 | let delay_len = self.delay.len() as isize; 358 | let delay_p = &mut self.delay[0] as *mut T::AccType; 359 | let mut in_p = &x[0] as *const T; 360 | let tap0 = &self.taps[0] as *const T::TapType; 361 | let gain = T::gain(self.interpolate); 362 | 363 | // Store a pointer to just beyond the end of the input 364 | let in_end = unsafe { in_p.offset(x.len() as isize) }; 365 | 366 | // Shift input samples into the delay line and compute output samples 367 | // off the delay line. 368 | 'outputs: loop { 369 | 370 | // For every high-rate clock tick, advance the polyphase 371 | // coefficient commutators by one, and when they wrap around, 372 | // insert a new input into the delay line and advance that by one. 373 | // Repeat until a sample we're not going to skip outputting. 374 | for tick in tickstart..decimate { 375 | // Only start from self.leftover the very first time. 376 | tickstart = 0; 377 | 378 | // Advance coefficient commutators. 379 | // Note that the initialised value for tap_idx is 380 | // interpolate - 1, so that on the first run we'll reset it 381 | // to zero and add the first sample to the delay line. 382 | tap_idx += 1; 383 | if tap_idx == interpolate { 384 | tap_idx = 0; 385 | 386 | if in_p == in_end { 387 | tap_idx = interpolate - 1; 388 | self.leftover = tick as usize; 389 | break 'outputs; 390 | } 391 | 392 | // Insert input sample 393 | unsafe { 394 | T::input(in_p, delay_p.offset(delay_idx)); 395 | in_p = in_p.offset(1); 396 | } 397 | 398 | // Manage the circular buffer 399 | delay_idx -= 1; 400 | if delay_idx == -1 { 401 | delay_idx = delay_len - 1; 402 | } 403 | 404 | } 405 | } 406 | 407 | // Compute the multiply-accumulate 408 | let mut acc: T::AccType = T::AccType::zero(); 409 | let mut tap_p = unsafe { tap0.offset(tap_idx) }; 410 | 411 | // First compute from the index to the end of the buffer 412 | for idx in (delay_idx + 1)..delay_len { 413 | unsafe { 414 | T::accumulate(&mut acc, delay_p.offset(idx), tap_p); 415 | tap_p = tap_p.offset(interpolate); 416 | } 417 | } 418 | 419 | // Then compute from the start of the buffer to the index 420 | for idx in 0..(delay_idx + 1) { 421 | unsafe { 422 | T::accumulate(&mut acc, delay_p.offset(idx), tap_p); 423 | tap_p = tap_p.offset(interpolate); 424 | } 425 | } 426 | 427 | // Save the result, accounting for filter gain 428 | unsafe { 429 | T::output(acc, out_p, gain); 430 | out_p = out_p.offset(1); 431 | } 432 | } 433 | 434 | // Save indices for next time 435 | self.delay_idx = delay_idx; 436 | self.tap_idx = tap_idx; 437 | 438 | y 439 | } 440 | } 441 | 442 | 443 | /// Design an FIR filter using the window method. 444 | /// 445 | /// `n_taps`: the number of taps to return 446 | /// `gains`: desired frequency response evalulated on a 512-point grid between 447 | /// zero and the Nyquist frequency 448 | pub fn firwin2(n_taps: usize, gains: &[f64]) -> Vec { 449 | assert!(n_taps > 0); 450 | assert_eq!(gains.len(), 512); 451 | if n_taps % 2 == 0 { 452 | assert_eq!(gains[511], 0.0f64); 453 | } 454 | 455 | // Gather complex gains 456 | let mut gainsc: Vec> = 457 | gains.iter().map(|g| Complex::new(*g, 0.0)).collect(); 458 | 459 | // Phase shift gains so the resulting impulse response is nicely aligned 460 | // to just grab the first n_taps coefficients. 461 | for (idx, gain) in gainsc.iter_mut().enumerate() { 462 | let (r, mut p) = gain.to_polar(); 463 | p += -(n_taps as f64 - 1.0)/2.0 * PI * (idx as f64)/512.0; 464 | *gain = Complex::from_polar(&r, &p); 465 | } 466 | 467 | // Inverse the frequency response to get the impulse response (aka taps) 468 | let taps: Vec = irdft(&gainsc).into_iter().take(n_taps).collect(); 469 | 470 | // Multiply by window 471 | let w = hamming(n_taps); 472 | taps.iter().zip(w.iter()).map(|(t, w)| t * w).collect() 473 | } 474 | 475 | /// Quantise FIR taps to taps that sum to, and are at most, `total`. 476 | pub fn quantise_taps(taps: &[f64], total: T) -> Vec { 477 | let sum: f64 = taps.iter().fold(0.0_f64, |acc, &x| acc + x); 478 | let total: f64 = ::from(total).unwrap(); 479 | taps.iter().map(|t| ::from(t * (total/sum)) 480 | .unwrap()).collect() 481 | } 482 | 483 | /// Compute the Hamming window over n samples 484 | fn hamming(n: usize) -> Vec { 485 | (0..n).map(|x| 0.54 - 0.46*(2.0 * PI * x as f64 / (n as f64 - 1.0)).cos()) 486 | .collect() 487 | } 488 | 489 | /// Compute the DFT of x. 490 | #[allow(non_snake_case)] 491 | fn dft(x: &[Complex]) -> Vec> { 492 | let N = x.len(); 493 | let mut out: Vec> = Vec::with_capacity(N); 494 | for k in 0..N { 495 | out.push(Complex::new(0.0, 0.0)); 496 | for n in 0..N { 497 | let f = 2.0 * PI * (k as f64) * (n as f64) / (N as f64); 498 | out[k] += x[n] * Complex::new(f.cos(), -f.sin()); 499 | } 500 | } 501 | out 502 | } 503 | 504 | /// Compute the IDFT of x. 505 | /// 506 | /// IDFT(x) = conj(DFT(conj(x))) / N 507 | #[allow(non_snake_case)] 508 | fn idft(x: &[Complex]) -> Vec> { 509 | let N = x.len(); 510 | let x: Vec> = x.iter().map(|x| x.conj()).collect(); 511 | let y = dft(&x); 512 | y.into_iter().map(|y| y.conj().unscale(N as f64)).collect() 513 | } 514 | 515 | /// Compute the IRDFT of x. 516 | #[allow(non_snake_case)] 517 | fn irdft(x: &[Complex]) -> Vec { 518 | let No2p1 = x.len(); 519 | let No2 = No2p1 - 1; 520 | let mut xc = x.to_owned(); 521 | for n in 1..No2 { 522 | xc.push(x[No2 - n].conj()); 523 | } 524 | idft(&xc).iter().map(|x| x.re).collect() 525 | } 526 | 527 | #[cfg(test)] 528 | mod tests { 529 | use num::Complex; 530 | use super::{FIR, firwin2, quantise_taps, hamming, dft, idft, irdft}; 531 | 532 | #[test] 533 | fn test_fir_impulse() { 534 | let taps: Vec = vec!{8192, 16384, 8192}; 535 | let mut fir = FIR::new(&taps, 1, 1); 536 | let x: Vec = vec!{4, 0, 0, 0, 0, 0, 0}; 537 | let y = fir.process(&x); 538 | assert_eq!(y, vec!{1, 2, 1, 0, 0, 0, 0}); 539 | } 540 | 541 | #[test] 542 | fn test_fir_impulse_f() { 543 | let taps: Vec = vec!{0.25, 0.5, 0.25}; 544 | let mut fir = FIR::new(&taps, 1, 1); 545 | let x: Vec = vec!{1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; 546 | let y = fir.process(&x); 547 | assert_eq!(y, vec!{0.25, 0.5, 0.25, 0.0, 0.0, 0.0, 0.0}); 548 | } 549 | 550 | #[test] 551 | fn test_fir_impulse_c() { 552 | let taps: Vec = vec!{8192, 16384, 8192}; 553 | let mut fir = FIR::new(&taps, 1, 1); 554 | let x: Vec> = vec!{ 555 | Complex{re:4, im:-8}, 556 | Complex{re:0, im: 0}, 557 | Complex{re:0, im: 0}, 558 | Complex{re:0, im: 0}, 559 | Complex{re:0, im: 0}, 560 | }; 561 | let y = fir.process(&x); 562 | assert_eq!(y, vec!{ 563 | Complex{re:1, im:-2}, 564 | Complex{re:2, im:-4}, 565 | Complex{re:1, im:-2}, 566 | Complex{re:0, im: 0}, 567 | Complex{re:0, im: 0}}); 568 | } 569 | 570 | #[test] 571 | fn test_fir_impulse_cf() { 572 | let taps: Vec = vec!{0.25, 0.5, 0.25}; 573 | let mut fir = FIR::new(&taps, 1, 1); 574 | let x: Vec> = vec!{ 575 | Complex{re:1.0, im:-2.0}, 576 | Complex{re:0.0, im: 0.0}, 577 | Complex{re:0.0, im: 0.0}, 578 | Complex{re:0.0, im: 0.0}, 579 | Complex{re:0.0, im: 0.0}, 580 | }; 581 | let y = fir.process(&x); 582 | assert_eq!(y, vec!{ 583 | Complex{re:0.25, im:-0.5}, 584 | Complex{re:0.50, im:-1.0}, 585 | Complex{re:0.25, im:-0.5}, 586 | Complex{re:0.00, im: 0.0}, 587 | Complex{re:0.00, im: 0.0}}); 588 | } 589 | 590 | #[test] 591 | fn test_fir_decimate() { 592 | let taps: Vec = vec!{8192, 8192, 8192, 8192}; 593 | let mut fir = FIR::new(&taps, 2, 1); 594 | let x: Vec = vec!{4, 4, 4, 4, 8, 8, 8, 8}; 595 | let y = fir.process(&x); 596 | assert_eq!(y, vec!{2, 4, 6, 8}); 597 | } 598 | 599 | #[test] 600 | fn test_fir_interpolate() { 601 | let taps: Vec = vec!{ 602 | -55, 0, 96, 0, -220, 0, 461, 0, -877, 0, 1608, 0, -3176, 0, 10342, 603 | 16410, 10342, 0, -3176, 0, 1608, 0, -877, 0, 461, 0, -220, 0, 96, 604 | 0, -55}; 605 | let mut fir = FIR::new(&taps, 1, 2); 606 | let x: Vec = vec!{ 607 | 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 110, 120, 130, 140, 150, 608 | 160, 170, 180, 190, 200, 210, 220, 230, 240, 250, 260, 270, 280, 609 | 290, 300}; 610 | let y = fir.process(&x); 611 | assert_eq!(y, vec!{ 612 | 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 3, 10, 15, 20, 24, 30, 613 | 34, 40, 44, 50, 54, 60, 64, 70, 74, 80, 84, 90, 94, 100, 104, 110, 614 | 114, 120, 124, 130, 134, 140, 144, 150, 154, 160, 164, 170, 174, 615 | 180, 184, 190, 194, 200, 204, 210, 214, 220, 224, 230}); 616 | } 617 | 618 | #[test] 619 | fn test_fir_continuity() { 620 | let taps: Vec = vec!{8192, 16384, 8192}; 621 | let mut fir = FIR::new(&taps, 1, 1); 622 | let x: Vec = vec!{4, 0}; 623 | let y = fir.process(&x); 624 | assert_eq!(y, vec!{1, 2}); 625 | let x: Vec = vec!{0, 0, 0, 0, 0}; 626 | let y = fir.process(&x); 627 | assert_eq!(y, vec!{1, 0, 0, 0, 0}); 628 | } 629 | 630 | #[test] 631 | fn test_fir_weird_block_sizes() { 632 | let taps: Vec = vec!{8192, 8192, 8192, 8192}; 633 | let mut fir = FIR::new(&taps, 2, 1); 634 | let y1 = fir.process(&vec!{4i16}); 635 | let y2 = fir.process(&vec!{4i16, 4, 4}); 636 | let y3 = fir.process(&vec!{8i16, 8, 8}); 637 | let y4 = fir.process(&vec!{8i16}); 638 | assert_eq!(y1, vec!{}); 639 | assert_eq!(y2, vec!{2, 4}); 640 | assert_eq!(y3, vec!{6}); 641 | assert_eq!(y4, vec!{8}); 642 | } 643 | 644 | #[test] 645 | fn test_fir_leftovers() { 646 | let taps: Vec = vec!{8192, 8192, 8192, 8192}; 647 | let mut fir = FIR::new(&taps, 5, 2); 648 | 649 | // Called with 1 input, should get 2 leftover 650 | let y = fir.process(&vec!{1i16}); 651 | assert_eq!(y, vec!{}); 652 | 653 | // Called with 1 input, should get 4 leftover 654 | let y = fir.process(&vec!{1i16}); 655 | assert_eq!(y, vec!{}); 656 | 657 | // Called with 1 input, should get 1 output and 1 leftover 658 | let y = fir.process(&vec!{1i16}); 659 | assert_eq!(y, vec!{1}); 660 | 661 | // Called with 1 input, should get 3 leftover 662 | let y = fir.process(&vec!{1i16}); 663 | assert_eq!(y, vec!{}); 664 | 665 | // Called with 4 inputs, should get two outputs and 1 leftover 666 | let y = fir.process(&vec!{1i16, 2, 3, 4}); 667 | assert_eq!(y, vec!{1, 3}); 668 | 669 | // Called with 1 input, should get 3 leftover 670 | let y = fir.process(&vec!{1i16}); 671 | assert_eq!(y, vec!{}); 672 | 673 | // Called with 1 input, should get 1 output and 0 leftover 674 | let y = fir.process(&vec!{1i16}); 675 | assert_eq!(y, vec!{1}); 676 | 677 | // Called with 1 input, should get 2 leftover 678 | let y = fir.process(&vec!{1i16}); 679 | assert_eq!(y, vec!{}); 680 | 681 | // Called with 1 input, should get 4 leftover 682 | let y = fir.process(&vec!{1i16}); 683 | assert_eq!(y, vec!{}); 684 | 685 | // Called with 1 input, should get 1 output and 1 leftover 686 | let y = fir.process(&vec!{1i16}); 687 | assert_eq!(y, vec!{1}); 688 | 689 | // Called with 2 inputs, should get 1 output and 0 leftover 690 | let y = fir.process(&vec!{1i16, 2}); 691 | assert_eq!(y, vec!{1}); 692 | } 693 | 694 | #[test] 695 | fn test_fir_compensate() { 696 | let fir = FIR::::cic_compensator(63, 5, 8, 2); 697 | let taps = fir.taps(); 698 | assert_eq!(*taps, vec!{ 699 | -4, -43, -9, 53, 33, -66, -74, 72, 138, -54, -221, -3, 313, 118, 700 | -389, -303, 419, 560, -359, -880, 161, 1240, 241, -1602, -949, 701 | 1899, 2182, -1975, -4636, 998, 11948, 17090, 10707, -142, -4690, 702 | -1379, 2428, 1591, -1214, -1464, 473, 1206, -20, -906, -230, 615, 703 | 338, -366, -347, 176, 297, -48, -222, -23, 147, 53, -85, -56, 42, 704 | 49, -17, -42, 2}); 705 | } 706 | 707 | #[test] 708 | fn test_fir_rcf() { 709 | let fir = FIR::::from_rcf(0.5, 5, 3); 710 | let taps = fir.taps(); 711 | assert_eq!(*taps, vec!{0i32, 19, 78, 140, 138, 0, -290, -644, -870, 712 | -719, 0, 1319, 3045, 4790, 6090, 6571, 6090, 4790, 3045, 1319, 0, 713 | -719, -870, -644, -290, 0, 138, 140, 78, 19, 0}); 714 | } 715 | 716 | #[test] 717 | fn test_fir_resampler() { 718 | let mut fir = FIR::::resampler(20, 2, 3); 719 | let x = vec!{5, 10, 15, 20, 25, 30, 35, 40, 40, 40, 40, 40}; 720 | let y = fir.process(&x); 721 | assert_eq!(y, vec!{ 722 | 0, 0, 0, 0, 4, 7, 10, 14, 17, 20, 24, 27, 30, 34, 38, 40, 40, 39}); 723 | } 724 | 725 | #[test] 726 | fn test_fir_lowpass() { 727 | // Pass a DC signal 728 | let mut fir = FIR::::lowpass(4, 0.1); 729 | let x = vec!{2, 2, 2, 2, 2, 2, 2, 2}; 730 | let y = fir.process(&x); 731 | assert_eq!(y, vec!{0, 1, 1, 1, 1, 1, 1, 1}); 732 | 733 | // Block a high frequency signal 734 | let mut fir = FIR::::lowpass(4, 0.1); 735 | let x = vec!{2, -2, 2, -2, 2, -2, 2, -2}; 736 | let y = fir.process(&x); 737 | assert_eq!(y, vec!{0, 0, 0, 0, 0, 0, 0, 0}); 738 | } 739 | 740 | #[test] 741 | #[ignore] 742 | fn test_fir_highpass() { 743 | // Ignored at the moment because the firwin high-pass filters have a high pass shape 744 | // but don't really block DC as such. To investigate later. 745 | 746 | // Block a DC signal 747 | let mut fir = FIR::::highpass(7, 0.1); 748 | let x = vec![2; 128]; 749 | let y = fir.process(&x); 750 | assert_eq!(y, vec!{0, 0, 0, 0, 0, 0, 0, 0}); 751 | 752 | // Pass a high frequency signal 753 | let mut fir = FIR::::highpass(3, 0.25); 754 | let x = vec!{2, -2, 2, -2, 2, -2, 2, -2}; 755 | let y = fir.process(&x); 756 | assert_eq!(y, vec!{2, -2, 2, -2, 2, -2, 2, -2}); 757 | } 758 | 759 | #[test] 760 | fn test_firwin2() { 761 | let mut gains: Vec = Vec::with_capacity(512); 762 | for i in 0..512 { 763 | if i < 128 { 764 | gains.push(1.0_f64); 765 | } else { 766 | gains.push(0.0_f64); 767 | } 768 | } 769 | let taps = firwin2(16, &gains); 770 | assert_eq!(taps, vec!{ 771 | -0.0013739594312205646, -0.005478372904749667, 772 | -0.012343994610893342, -0.010323351794476321, 0.021387446454536923, 773 | 0.09167900348875416, 0.17954532405889292, 0.24108715546226311, 774 | 0.24035744268642953, 0.17776648555261024, 0.08976231035333902, 775 | 0.020081701014194094, -0.01085320791569337, -0.012398611604538569, 776 | -0.00540355168901675, -0.0012970506629601231}); 777 | } 778 | 779 | #[test] 780 | fn test_quantise_taps() { 781 | let taps = vec!{0.25, 0.5, 0.25}; 782 | assert_eq!(quantise_taps(&taps, 32768), vec!{8192, 16384, 8192}); 783 | } 784 | 785 | #[test] 786 | fn test_hamming() { 787 | let h3 = hamming(3); 788 | let h5 = hamming(5); 789 | assert!((h3[0] - 0.08).abs() < 1e-8); 790 | assert!((h3[1] - 1.00).abs() < 1e-8); 791 | assert!((h3[2] - 0.08).abs() < 1e-8); 792 | assert!((h5[0] - 0.08).abs() < 1e-8); 793 | assert!((h5[1] - 0.54).abs() < 1e-8); 794 | assert!((h5[2] - 1.00).abs() < 1e-8); 795 | assert!((h5[3] - 0.54).abs() < 1e-8); 796 | assert!((h5[4] - 0.08).abs() < 1e-8); 797 | } 798 | 799 | #[test] 800 | fn test_dft_1() { 801 | let x = vec!{ 802 | Complex::new(1.0, 1.0), 803 | Complex::new(0.0, 0.0), 804 | Complex::new(0.0, 0.0), 805 | Complex::new(0.0, 0.0), 806 | }; 807 | let y = dft(&x); 808 | assert_eq!(y, vec!{ 809 | Complex::new(1.0, 1.0), 810 | Complex::new(1.0, 1.0), 811 | Complex::new(1.0, 1.0), 812 | Complex::new(1.0, 1.0), 813 | }); 814 | } 815 | 816 | #[test] 817 | fn test_dft_2() { 818 | let x = vec!{ 819 | Complex::new(0.0, 1.0), 820 | Complex::new(0.0, -1.0), 821 | Complex::new(0.0, 1.0), 822 | Complex::new(0.0, -1.0), 823 | }; 824 | let y = dft(&x); 825 | assert!((y[0].re - 0.0).abs() < 1e-8); 826 | assert!((y[1].re - 0.0).abs() < 1e-8); 827 | assert!((y[2].re - 0.0).abs() < 1e-8); 828 | assert!((y[3].re - 0.0).abs() < 1e-8); 829 | assert!((y[0].im - 0.0).abs() < 1e-8); 830 | assert!((y[1].im - 0.0).abs() < 1e-8); 831 | assert!((y[2].im - 4.0).abs() < 1e-8); 832 | assert!((y[3].im - 0.0).abs() < 1e-8); 833 | } 834 | 835 | #[test] 836 | fn test_dft_3() { 837 | let x = vec!{ 838 | Complex::new( 2.0, 1.0), 839 | Complex::new( 0.0, 1.0), 840 | Complex::new(-2.0, 1.0), 841 | Complex::new( 0.0, 1.0), 842 | Complex::new( 2.0, 1.0), 843 | Complex::new( 0.0, 1.0), 844 | Complex::new(-2.0, 1.0), 845 | Complex::new( 0.0, 1.0), 846 | }; 847 | let y = dft(&x); 848 | assert!((y[0].re - 0.0).abs() < 1e-7); 849 | assert!((y[0].im - 8.0).abs() < 1e-7); 850 | assert!((y[1].re - 0.0).abs() < 1e-7); 851 | assert!((y[1].im - 0.0).abs() < 1e-7); 852 | assert!((y[2].re - 8.0).abs() < 1e-7); 853 | assert!((y[2].im - 0.0).abs() < 1e-7); 854 | assert!((y[3].re - 0.0).abs() < 1e-7); 855 | assert!((y[3].im - 0.0).abs() < 1e-7); 856 | assert!((y[4].re - 0.0).abs() < 1e-7); 857 | assert!((y[4].im - 0.0).abs() < 1e-7); 858 | assert!((y[5].re - 0.0).abs() < 1e-7); 859 | assert!((y[5].im - 0.0).abs() < 1e-7); 860 | assert!((y[6].re - 8.0).abs() < 1e-7); 861 | assert!((y[6].im - 0.0).abs() < 1e-7); 862 | assert!((y[7].re - 0.0).abs() < 1e-7); 863 | assert!((y[7].im - 0.0).abs() < 1e-7); 864 | } 865 | 866 | #[test] 867 | fn test_dft_4() { 868 | let x = vec!{ 869 | Complex::new( 1.0, 0.0), 870 | Complex::new(-1.0, 0.0), 871 | Complex::new( 2.0, 0.0), 872 | Complex::new(-2.0, 0.0), 873 | }; 874 | let y = dft(&x); 875 | assert!((y[0].re - 0.0).abs() < 1e-8); 876 | assert!((y[0].im - 0.0).abs() < 1e-8); 877 | assert!((y[1].re - -1.0).abs() < 1e-8); 878 | assert!((y[1].im - -1.0).abs() < 1e-8); 879 | assert!((y[2].re - 6.0).abs() < 1e-8); 880 | assert!((y[2].im - 0.0).abs() < 1e-8); 881 | assert!((y[3].re - -1.0).abs() < 1e-8); 882 | assert!((y[3].im - 1.0).abs() < 1e-8); 883 | } 884 | 885 | #[test] 886 | fn test_idft_1() { 887 | let x = vec!{ 888 | Complex::new(1.0, 1.0), 889 | Complex::new(1.0, 1.0), 890 | Complex::new(1.0, 1.0), 891 | Complex::new(1.0, 1.0), 892 | }; 893 | let y = idft(&x); 894 | assert!((y[0].re - 1.0).abs() < 1e-8); 895 | assert!((y[1].re - 0.0).abs() < 1e-8); 896 | assert!((y[2].re - 0.0).abs() < 1e-8); 897 | assert!((y[3].re - 0.0).abs() < 1e-8); 898 | assert!((y[0].im - 1.0).abs() < 1e-8); 899 | assert!((y[1].im - 0.0).abs() < 1e-8); 900 | assert!((y[2].im - 0.0).abs() < 1e-8); 901 | assert!((y[3].im - 0.0).abs() < 1e-8); 902 | } 903 | 904 | #[test] 905 | fn test_idft_2() { 906 | let x = vec!{ 907 | Complex::new(0.0, 0.0), 908 | Complex::new(0.0, 0.0), 909 | Complex::new(0.0, 4.0), 910 | Complex::new(0.0, 0.0), 911 | }; 912 | let y = idft(&x); 913 | assert!((y[0].re - 0.0).abs() < 1e-8); 914 | assert!((y[1].re - 0.0).abs() < 1e-8); 915 | assert!((y[2].re - 0.0).abs() < 1e-8); 916 | assert!((y[3].re - 0.0).abs() < 1e-8); 917 | assert!((y[0].im - 1.0).abs() < 1e-8); 918 | assert!((y[1].im - -1.0).abs() < 1e-8); 919 | assert!((y[2].im - 1.0).abs() < 1e-8); 920 | assert!((y[3].im - -1.0).abs() < 1e-8); 921 | } 922 | 923 | #[test] 924 | fn test_idft_3() { 925 | let x = vec!{ 926 | Complex::new( 0.0, 0.0), 927 | Complex::new(-1.0, -1.0), 928 | Complex::new( 6.0, 0.0), 929 | Complex::new(-1.0, 1.0), 930 | }; 931 | let y = idft(&x); 932 | assert!((y[0].re - 1.0).abs() < 1e-8); 933 | assert!((y[1].re - -1.0).abs() < 1e-8); 934 | assert!((y[2].re - 2.0).abs() < 1e-8); 935 | assert!((y[3].re - -2.0).abs() < 1e-8); 936 | assert!((y[0].im - 0.0).abs() < 1e-8); 937 | assert!((y[1].im - 0.0).abs() < 1e-8); 938 | assert!((y[2].im - 0.0).abs() < 1e-8); 939 | assert!((y[3].im - 0.0).abs() < 1e-8); 940 | } 941 | 942 | #[test] 943 | fn test_irdft_1() { 944 | let x = vec!{ 945 | Complex::new(1.0, 0.0), 946 | Complex::new(1.0, 0.0), 947 | Complex::new(1.0, 0.0), 948 | }; 949 | let y = irdft(&x); 950 | assert!((y[0] - 1.0).abs() < 1e-8); 951 | assert!((y[1] - 0.0).abs() < 1e-8); 952 | assert!((y[2] - 0.0).abs() < 1e-8); 953 | assert!((y[3] - 0.0).abs() < 1e-8); 954 | } 955 | 956 | #[test] 957 | fn test_irdft_2() { 958 | let x = vec!{ 959 | Complex::new( 0.0, 0.0), 960 | Complex::new(-1.0, -1.0), 961 | Complex::new( 6.0, 0.0), 962 | }; 963 | let y = irdft(&x); 964 | assert!((y[0] - 1.0).abs() < 1e-8); 965 | assert!((y[1] - -1.0).abs() < 1e-8); 966 | assert!((y[2] - 2.0).abs() < 1e-8); 967 | assert!((y[3] - -2.0).abs() < 1e-8); 968 | } 969 | } 970 | --------------------------------------------------------------------------------