include!(concat!(env!("OUT_DIR"), "/cossin_table.rs"));
pub fn cossin(mut phase: i32) -> (i32, i32) {
let mut octant = phase as u32;
if octant & (1 << 29) != 0 {
phase = !phase;
}
const ALIGN_MSB: usize = 32 - 16 - 1;
phase = (((phase as u32) << 3) >> (32 - COSSIN_DEPTH - ALIGN_MSB)) as _;
let lookup = COSSIN[(phase >> ALIGN_MSB) as usize];
phase &= (1 << ALIGN_MSB) - 1;
phase -= 1 << (ALIGN_MSB - 1);
const PI4: i32 = (core::f64::consts::FRAC_PI_4 * (1 << 16) as f64) as _;
let dphi = (phase * PI4) >> 16;
let mut cos = lookup as u16 as i32 + (1 << 16);
let mut sin = (lookup >> 16) as i32;
let dcos = (sin * dphi) >> COSSIN_DEPTH;
let dsin = (cos * dphi) >> (COSSIN_DEPTH + 1);
cos = (cos << (ALIGN_MSB - 1)) - dcos;
sin = (sin << ALIGN_MSB) + dsin;
octant ^= octant >> 1;
if octant & (1 << 29) != 0 {
(cos, sin) = (sin, cos);
}
if octant & (1 << 30) != 0 {
cos = -cos;
}
if octant & (1 << 31) != 0 {
sin = -sin;
}
(cos, sin)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::testing::{db, dds_metrics};
use core::f64::consts::PI;
use rustfft::num_complex::Complex64;
const DDS_LOG2: u32 = 16;
const DDS_LEN: usize = 1 << DDS_LOG2;
const AMPLITUDE: f64 = (1i64 << 31) as f64 - 0.85 * (1i64 << 15) as f64;
fn dds_step(k: usize) -> i32 {
(k as i32) << (32 - DDS_LOG2)
}
fn dds_complex<D>(k: usize, mut dither: D) -> Vec<Complex64>
where
D: Iterator<Item = i32>,
{
let step = dds_step(k);
let mut phase = 0i32;
(0..DDS_LEN)
.map(|_| {
phase = phase.wrapping_add(step);
let (re, im) = cossin(phase.wrapping_add(dither.next().unwrap()));
Complex64::new(re as f64 / AMPLITUDE, im as f64 / AMPLITUDE)
})
.collect()
}
fn dds_real<D>(k: usize, dither: D) -> Vec<f64>
where
D: Iterator<Item = i32>,
{
dds_complex(k, dither).into_iter().map(|z| z.re).collect()
}
fn complex_fft_power(x: &[Complex64]) -> Vec<f64> {
use rustfft::FftPlanner;
let mut planner = FftPlanner::new();
let fft = planner.plan_fft_forward(x.len());
let mut x = x.to_vec();
fft.process(&mut x);
x.into_iter().map(|x| x.norm_sqr()).collect()
}
fn complex_spur_bins(k: usize) -> (usize, usize) {
let m = 8 * (1 << COSSIN_DEPTH);
(DDS_LEN - ((m - 1) * k) % DDS_LEN, ((m + 1) * k) % DDS_LEN)
}
fn real_spur_bins(k: usize) -> (usize, usize) {
let (lo, hi) = complex_spur_bins(k);
(alias_bin(lo), alias_bin(hi))
}
fn alias_bin(bin: usize) -> usize {
bin.min(DDS_LEN - bin)
}
#[test]
fn cossin_error_max_rms_all_phase() {
const MAX_PHASE: f64 = (1i64 << 32) as _;
let mut rms_err = (0f64, 0f64);
let mut sum_err = (0f64, 0f64);
let mut max_err = (0f64, 0f64);
let mut sum = (0f64, 0f64);
let mut demod = (0f64, 0f64);
const PHASE_DEPTH: usize = 20;
for phase in 0..(1 << PHASE_DEPTH) {
let phase = phase << (32 - PHASE_DEPTH);
let have = cossin(phase);
let have = (have.0 as f64 / AMPLITUDE, have.1 as f64 / AMPLITUDE);
let radian_phase = 2. * PI * phase as f64 / MAX_PHASE;
let want = (radian_phase.cos(), radian_phase.sin());
sum.0 += have.0;
sum.1 += have.1;
demod.0 += have.0 * want.0 - have.1 * want.1;
demod.1 += have.1 * want.0 + have.0 * want.1;
let err = (have.0 - want.0, have.1 - want.1);
sum_err.0 += err.0;
sum_err.1 += err.1;
rms_err.0 += err.0 * err.0;
rms_err.1 += err.1 * err.1;
max_err.0 = max_err.0.max(err.0.abs());
max_err.1 = max_err.1.max(err.1.abs());
}
rms_err.0 /= (1 << PHASE_DEPTH) as f64;
rms_err.1 /= (1 << PHASE_DEPTH) as f64;
println!("sum: {:.2e} {:.2e}", sum.0, sum.1);
println!("demod: {:.2e} {:.2e}", demod.0, demod.1);
println!("sum_err: {:.2e} {:.2e}", sum_err.0, sum_err.1);
println!("rms: {:.2e} {:.2e}", rms_err.0.sqrt(), rms_err.1.sqrt());
println!("max: {:.2e} {:.2e}", max_err.0, max_err.1);
assert!(sum.0.abs() < 4e-10);
assert!(sum.1.abs() < 3e-8);
assert!(demod.0.abs() < 4e-10);
assert!(demod.1.abs() < 1e-8);
assert!(sum_err.0.abs() < 4e-10);
assert!(sum_err.1.abs() < 4e-10);
assert!(rms_err.0.sqrt() < 4e-6);
assert!(rms_err.1.sqrt() < 4e-6);
assert!(max_err.0 < 1e-5);
assert!(max_err.1 < 1e-5);
}
#[test]
fn cossin_dds_spur_prediction_complex() {
let k = 7;
let x = dds_complex(k, core::iter::repeat(0));
let power = complex_fft_power(&x);
let carrier = power[k];
let (lo, hi) = complex_spur_bins(k);
let lo_db = db(power[lo] / carrier);
let hi_db = db(power[hi] / carrier);
let strongest = power
.iter()
.enumerate()
.filter(|(i, _)| *i != k)
.max_by(|(_, a), (_, b)| a.total_cmp(b))
.unwrap();
assert!((lo_db + 120.4).abs() < 1.5, "{lo_db}");
assert!((hi_db + 120.4).abs() < 1.5, "{hi_db}");
assert!(strongest.0 == lo || strongest.0 == hi);
}
#[test]
fn cossin_dds_metrics_real() {
let k = 7;
let metrics = dds_metrics(&dds_real(k, core::iter::repeat(0)), k, 16);
let (lo, hi) = real_spur_bins(k);
let spur_bins = [alias_bin(lo), alias_bin(hi)];
assert!(spur_bins.contains(&metrics.strongest_spur_bin));
assert!(metrics.sfdr_db > 118.0, "{metrics:?}");
assert!(metrics.snr_db > 106.0, "{metrics:?}");
assert!(metrics.thdn_db > 105.9, "{metrics:?}");
assert!(metrics.thd_db > 123.0, "{metrics:?}");
}
}