1
2use num_complex::Complex;
3use std::f32::consts::PI;
4
5use crate::error::{Error, Result};
6
7use super::nco::Nco;
8use super::vco::Vco;
9
10const PLL_BANDWIDTH_DEFAULT: f32 = 0.1;
11
12
13#[derive(Debug, Clone, Copy, PartialEq)]
14pub enum OscScheme {
15 Nco,
16 Vco,
17}
18
19#[derive(Debug, Clone)]
20enum OscData {
21 Nco(Nco),
22 Vco(Vco),
23}
24
25#[derive(Debug, Clone)]
27pub struct Osc {
28 theta: u32,
29 d_theta: u32,
30 alpha: f32,
31 beta: f32,
32 osc: OscData,
33}
34
35impl Osc {
36 pub fn new(osc_scheme: OscScheme) -> Self {
38 let data = match osc_scheme {
39 OscScheme::Nco => OscData::Nco(Nco::new()),
40 OscScheme::Vco => OscData::Vco(Vco::new()),
41 };
42 let mut nco = Osc {
43 theta: 0,
44 d_theta: 0,
45 alpha: 0.0,
46 beta: 0.0,
47 osc: data,
48 };
49
50 nco.pll_set_bandwidth(PLL_BANDWIDTH_DEFAULT);
52
53 nco.reset();
55
56 nco
57 }
58
59 pub fn reset(&mut self) {
61 self.theta = 0;
62 self.d_theta = 0;
63 }
64
65 pub fn set_frequency(&mut self, dtheta: f32) {
67 self.d_theta = Self::constrain(dtheta);
68 }
69
70 pub fn adjust_frequency(&mut self, df: f32) {
72 self.d_theta = self.d_theta.wrapping_add(Self::constrain(df));
73 }
74
75 pub fn set_phase(&mut self, phi: f32) {
77 self.theta = Self::constrain(phi);
78 }
79
80 pub fn adjust_phase(&mut self, dphi: f32) {
82 self.theta = self.theta.wrapping_add(Self::constrain(dphi));
83 }
84
85 pub fn step(&mut self) {
87 self.theta = self.theta.wrapping_add(self.d_theta);
88 }
89
90 pub fn get_phase(&self) -> f32 {
92 2.0 * PI * self.theta as f32 / ((1u64 << 32) as f32)
93 }
94
95 pub fn get_frequency(&self) -> f32 {
97 let d_theta = 2.0 * PI * self.d_theta as f32 / (1u64 << 32) as f32;
98 if d_theta > PI {
99 d_theta - 2.0 * PI
100 } else {
101 d_theta
102 }
103 }
104
105 pub fn sin(&self) -> f32 {
107 match self.osc {
108 OscData::Nco(ref nco) => nco.sin(self.theta),
109 OscData::Vco(ref vco) => vco.sin(self.theta),
110 }
111 }
112
113 pub fn cos(&self) -> f32 {
115 match self.osc {
116 OscData::Nco(ref nco) => nco.cos(self.theta),
117 OscData::Vco(ref vco) => vco.cos(self.theta),
118 }
119 }
120
121 pub fn sin_cos(&self) -> (f32, f32) {
123 match self.osc {
124 OscData::Nco(ref nco) => nco.sin_cos(self.theta),
125 OscData::Vco(ref vco) => vco.sin_cos(self.theta),
126 }
127 }
128
129 pub fn cexp(&self) -> Complex<f32> {
131 let (sin, cos) = self.sin_cos();
132 Complex::new(cos, sin)
133 }
134
135 pub fn pll_set_bandwidth(&mut self, bw: f32) {
139 if bw < 0.0 {
140 panic!("Bandwidth must be positive");
141 }
142 self.alpha = bw;
143 self.beta = bw.sqrt();
144 }
145
146 pub fn pll_step(&mut self, dphi: f32) {
148 self.adjust_frequency(dphi * self.alpha);
149 self.adjust_phase(dphi * self.beta);
150 }
151
152 pub fn mix_up(&self, input: Complex<f32>) -> Complex<f32> {
156 let (sin, cos) = self.sin_cos();
157 input * Complex::new(cos, sin)
158 }
159
160 pub fn mix_block_up(&mut self, input: &[Complex<f32>], output: &mut [Complex<f32>]) -> Result<()> {
162 if input.len() != output.len() {
163 return Err(Error::Range("Input and output slices must have the same length".to_owned()));
164 }
165 for (x, y) in input.iter().zip(output.iter_mut()) {
166 *y = self.mix_up(*x);
167 self.step();
168 }
169 Ok(())
170 }
171
172 pub fn mix_down(&self, input: Complex<f32>) -> Complex<f32> {
174 let (sin, cos) = self.sin_cos();
175 input * Complex::new(cos, sin).conj()
176 }
177
178 pub fn mix_block_down(&mut self, input: &[Complex<f32>], output: &mut [Complex<f32>]) -> Result<()> {
180 if input.len() != output.len() {
181 return Err(Error::Range("Input and output slices must have the same length".to_owned()));
182 }
183 for (x, y) in input.iter().zip(output.iter_mut()) {
184 *y = self.mix_down(*x);
185 self.step();
186 }
187 Ok(())
188 }
189
190 fn constrain(theta: f32) -> u32 {
192 let mut theta = theta;
193 while theta >= 2.0 * PI {
194 theta -= 2.0 * PI;
195 }
196 while theta < 0.0 {
197 theta += 2.0 * PI;
198 }
199 ((theta / (2.0 * PI)) * (u32::MAX as f32)) as u32
200 }
201}
202
203#[cfg(test)]
206mod tests {
207 use crate::nco::{Osc, OscScheme};
208 use lazy_static::lazy_static;
209 use num_complex::Complex;
210 use std::f32::consts::PI;
211 use test_macro::autotest_annotate;
212 use crate::utility::test_helpers::{PsdRegion, validate_psd_spgramcf};
213 use crate::fft::spgram::Spgram;
214 use crate::math::windows::{hann, WindowType};
215
216 fn pll_error(a: f32, b: f32) -> f32 {
218 let mut error = a - b;
219 while error >= 2.0 * PI {
220 error -= 2.0 * PI;
221 }
222 while error <= -2.0 * PI {
223 error += 2.0 * PI;
224 }
225 error
226 }
227
228 fn nco_crcf_pll_test(scheme: OscScheme, phase_offset: f32, freq_offset: f32, pll_bandwidth: f32, num_iterations: usize, tol: f32) {
230 let mut nco_tx = Osc::new(scheme);
232 let mut nco_rx = Osc::new(scheme);
233
234 nco_tx.set_phase(phase_offset);
236 nco_tx.set_frequency(freq_offset);
237 nco_rx.pll_set_bandwidth(pll_bandwidth);
238
239 for _ in 0..num_iterations {
241 let r = nco_tx.cexp();
243 let v = nco_rx.cexp();
244
245 let phase_error = (r * v.conj()).arg();
247
248 nco_rx.pll_step(phase_error);
250
251 nco_tx.step();
253 nco_rx.step();
254 }
255
256 let phase_error = pll_error(nco_tx.get_phase(), nco_rx.get_phase());
258 assert!((phase_error).abs() < tol, "Phase error: {}", phase_error);
259
260 let freq_error = pll_error(nco_tx.get_frequency(), nco_rx.get_frequency());
262 assert!((freq_error).abs() < tol, "Frequency error: {}", freq_error);
263
264 println!(
265 "nco[bw:{:.4},n={}], phase:{:.6},e={:.4e}, freq:{:.6},e={:.4e}",
266 pll_bandwidth, num_iterations, phase_offset, phase_error, freq_offset, freq_error
267 );
268 }
269
270 #[test]
271 #[autotest_annotate(autotest_nco_crcf_pll_phase)]
272 fn test_nco_crcf_pll_phase() {
273 let bandwidths = [0.1, 0.01, 0.001, 0.0001];
274 let tol = 1e-2;
275
276 for &bw in &bandwidths {
277 let num_steps = (32.0 / bw) as usize;
279
280 nco_crcf_pll_test(OscScheme::Nco, -PI / 1.1, 0.0, bw, num_steps, tol);
282 nco_crcf_pll_test(OscScheme::Nco, -PI / 2.0, 0.0, bw, num_steps, tol);
283 nco_crcf_pll_test(OscScheme::Nco, -PI / 4.0, 0.0, bw, num_steps, tol);
284 nco_crcf_pll_test(OscScheme::Nco, -PI / 8.0, 0.0, bw, num_steps, tol);
285 nco_crcf_pll_test(OscScheme::Nco, PI / 8.0, 0.0, bw, num_steps, tol);
286 nco_crcf_pll_test(OscScheme::Nco, PI / 4.0, 0.0, bw, num_steps, tol);
287 nco_crcf_pll_test(OscScheme::Nco, PI / 2.0, 0.0, bw, num_steps, tol);
288 nco_crcf_pll_test(OscScheme::Nco, PI / 1.1, 0.0, bw, num_steps, tol);
289 }
290 }
291
292 #[test]
293 #[autotest_annotate(autotest_nco_crcf_pll_freq)]
294 fn test_nco_crcf_pll_freq() {
295 let bandwidths = [0.1, 0.05, 0.02, 0.01];
296 let tol = 1e-2;
297
298 for &bw in &bandwidths {
299 let num_steps = (32.0 / bw) as usize;
301
302 nco_crcf_pll_test(OscScheme::Nco, 0.0, -0.8, bw, num_steps, tol);
304 nco_crcf_pll_test(OscScheme::Nco, 0.0, -0.4, bw, num_steps, tol);
305 nco_crcf_pll_test(OscScheme::Nco, 0.0, -0.2, bw, num_steps, tol);
306 nco_crcf_pll_test(OscScheme::Nco, 0.0, -0.1, bw, num_steps, tol);
307 nco_crcf_pll_test(OscScheme::Nco, 0.0, 0.1, bw, num_steps, tol);
308 nco_crcf_pll_test(OscScheme::Nco, 0.0, 0.2, bw, num_steps, tol);
309 nco_crcf_pll_test(OscScheme::Nco, 0.0, 0.4, bw, num_steps, tol);
310 nco_crcf_pll_test(OscScheme::Nco, 0.0, 0.8, bw, num_steps, tol);
311 }
312 }
313
314 fn nco_crcf_phase_test(scheme: OscScheme, theta: f32, expected_cos: f32, expected_sin: f32, tol: f32) {
316 let mut nco = Osc::new(scheme);
318
319 nco.set_phase(theta);
321
322 let c = nco.cos();
324 let s = nco.sin();
325
326 println!(
327 "cos({:8.5}) = {:8.5} ({:8.5}) e:{:8.5}, sin({:8.5}) = {:8.5} ({:8.5}) e:{:8.5}",
328 theta,
329 expected_cos,
330 c,
331 expected_cos - c,
332 theta,
333 expected_sin,
334 s,
335 expected_sin - s
336 );
337
338 assert!((c - expected_cos).abs() < tol, "Cosine error: expected {}, got {}", expected_cos, c);
340 assert!((s - expected_sin).abs() < tol, "Sine error: expected {}, got {}", expected_sin, s);
341 }
342
343 #[test]
344 #[autotest_annotate(autotest_nco_crcf_phase)]
345 fn test_nco_crcf_phase() {
346 let tol = 0.02;
348
349 nco_crcf_phase_test(OscScheme::Nco, -6.283185307, 1.000000000, 0.000000000, tol);
350 nco_crcf_phase_test(OscScheme::Nco, -6.195739393, 0.996179042, 0.087334510, tol);
351 nco_crcf_phase_test(OscScheme::Nco, -5.951041106, 0.945345356, 0.326070787, tol);
352 nco_crcf_phase_test(OscScheme::Nco, -5.131745978, 0.407173250, 0.913350943, tol);
353 nco_crcf_phase_test(OscScheme::Nco, -4.748043551, 0.035647016, 0.999364443, tol);
354 nco_crcf_phase_test(OscScheme::Nco, -3.041191113, -0.994963998, -0.100232943, tol);
355 nco_crcf_phase_test(OscScheme::Nco, -1.947799864, -0.368136099, -0.929771914, tol);
356 nco_crcf_phase_test(OscScheme::Nco, -1.143752030, 0.414182352, -0.910193924, tol);
357 nco_crcf_phase_test(OscScheme::Nco, -1.029377689, 0.515352252, -0.856978446, tol);
358 nco_crcf_phase_test(OscScheme::Nco, -0.174356887, 0.984838307, -0.173474811, tol);
359 nco_crcf_phase_test(OscScheme::Nco, -0.114520496, 0.993449692, -0.114270338, tol);
360 nco_crcf_phase_test(OscScheme::Nco, 0.000000000, 1.000000000, 0.000000000, tol);
361 nco_crcf_phase_test(OscScheme::Nco, 1.436080000, 0.134309213, 0.990939471, tol);
362 nco_crcf_phase_test(OscScheme::Nco, 2.016119855, -0.430749878, 0.902471353, tol);
363 nco_crcf_phase_test(OscScheme::Nco, 2.996498473, -0.989492293, 0.144585621, tol);
364 nco_crcf_phase_test(OscScheme::Nco, 3.403689755, -0.965848729, -0.259106603, tol);
365 nco_crcf_phase_test(OscScheme::Nco, 3.591162483, -0.900634128, -0.434578148, tol);
366 nco_crcf_phase_test(OscScheme::Nco, 5.111428476, 0.388533479, -0.921434607, tol);
367 nco_crcf_phase_test(OscScheme::Nco, 5.727585681, 0.849584319, -0.527452828, tol);
368 nco_crcf_phase_test(OscScheme::Nco, 6.283185307, 1.000000000, -0.000000000, tol);
369 }
370
371 #[test]
372 #[autotest_annotate(autotest_nco_basic)]
373 fn test_nco_basic() {
374 let mut nco = Osc::new(OscScheme::Nco);
375
376 let tol = 1e-4; let f = 2.0 * PI / 64.0; nco.set_phase(0.0);
380 assert!((nco.cos() - 1.0).abs() < tol, "Cosine at phase 0 error");
381 assert!(nco.sin().abs() < tol, "Sine at phase 0 error");
382
383 let (s, c) = nco.sin_cos();
384 assert!(s.abs() < tol, "Sine at phase 0 error (sin_cos)");
385 assert!((c - 1.0).abs() < tol, "Cosine at phase 0 error (sin_cos)");
386
387 nco.set_phase(PI / 2.0);
388 assert!(nco.cos().abs() < tol, "Cosine at phase PI/2 error");
389 assert!((nco.sin() - 1.0).abs() < tol, "Sine at phase PI/2 error");
390
391 let (s, c) = nco.sin_cos();
392 assert!((s - 1.0).abs() < tol, "Sine at phase PI/2 error (sin_cos)");
393 assert!(c.abs() < tol, "Cosine at phase PI/2 error (sin_cos)");
394
395 nco.set_phase(0.0);
397 nco.set_frequency(f);
398 for i in 0..128 {
399 let (s, c) = nco.sin_cos();
400 assert!((s - (i as f32 * f).sin()).abs() < tol, "Sine error at step {}", i);
401 assert!((c - (i as f32 * f).cos()).abs() < tol, "Cosine error at step {}", i);
402 nco.step();
403 }
404
405 nco.set_phase(0.0);
407 nco.set_frequency(2.0 * f);
408 for i in 0..128 {
409 let (s, c) = nco.sin_cos();
410 assert!((s - (i as f32 * 2.0 * f).sin()).abs() < tol, "Sine error at step {} (double frequency)", i);
411 assert!((c - (i as f32 * 2.0 * f).cos()).abs() < tol, "Cosine error at step {} (double frequency)", i);
412 nco.step();
413 }
414 }
415
416 #[test]
417 #[autotest_annotate(autotest_nco_mixing)]
418 fn test_nco_mixing() {
419 let f = 0.1;
421 let phi = PI;
422
423 let tol = 0.05;
425
426 let mut nco = Osc::new(OscScheme::Nco);
428 nco.set_frequency(f);
429 nco.set_phase(phi);
430
431 for _ in 0..64 {
432 let (nco_q, nco_i) = nco.sin_cos();
434
435 let nco_cplx_in = Complex::new(nco_i, nco_q);
437 let nco_cplx_out = nco.mix_down(nco_cplx_in);
438
439 assert!((nco_cplx_out.re - 1.0).abs() < tol, "Real part mixing error");
441 assert!(nco_cplx_out.im.abs() < tol, "Imaginary part mixing error");
442
443 nco.step();
445 }
446 }
447
448 #[test]
449 #[autotest_annotate(autotest_nco_block_mixing)]
450 fn test_nco_block_mixing() {
451 let f = 0.1;
453 let phi = PI;
454
455 let tol = 0.05;
457
458 const NUM_SAMPLES: usize = 1024;
460
461 let mut x = [Complex::new(0.0, 0.0); NUM_SAMPLES];
463 let mut y = [Complex::new(0.0, 0.0); NUM_SAMPLES];
464
465 for i in 0..NUM_SAMPLES {
467 x[i] = Complex::new(0.0, f * i as f32 + phi).exp();
468 }
469
470 let mut nco = Osc::new(OscScheme::Nco);
472 nco.set_frequency(f);
473 nco.set_phase(phi);
474
475 let mut i = 0;
477 while i < NUM_SAMPLES {
478 let n = std::cmp::min(7, NUM_SAMPLES - i);
479 nco.mix_block_down(&x[i..i + n], &mut y[i..i + n]).unwrap();
480 i += n;
481 }
482
483 for i in 0..NUM_SAMPLES {
485 assert!((y[i].re - 1.0).abs() < tol, "Real part mixing error at index {}", i);
486 assert!(y[i].im.abs() < tol, "Imaginary part mixing error at index {}", i);
487 }
488 }
489
490 fn testbench_nco_crcf_mix(scheme: OscScheme, phase: f32, frequency: f32) {
491 use rand::Rng;
492 let buf_len = 1200;
494 let tol = 1e-2;
495
496 let mut nco = Osc::new(scheme);
498 nco.set_phase(phase);
499 nco.set_frequency(frequency);
500
501 let mut rng = rand::thread_rng();
503 let buf_0: Vec<Complex<f32>> = (0..buf_len).map(|_| Complex::new(0.0, 2.0 * PI * rng.gen::<f32>()).exp()).collect();
504
505 let mut buf_1 = vec![Complex::new(0.0, 0.0); buf_len];
507 nco.mix_block_up(&buf_0, &mut buf_1).unwrap();
508
509 let mut theta = phase;
511 for i in 0..buf_len {
512 let v = buf_0[i] * Complex::new(0.0, theta).exp();
513 assert!((buf_1[i].re - v.re).abs() < tol, "Real part mixing error at index {}", i);
514 assert!((buf_1[i].im - v.im).abs() < tol, "Imaginary part mixing error at index {}", i);
515
516 theta += frequency;
518 while theta > PI {
519 theta -= 2.0 * PI;
520 }
521 while theta < -PI {
522 theta += 2.0 * PI;
523 }
524 }
525 }
526
527 #[test]
529 #[autotest_annotate(autotest_nco_crcf_mix_nco_0)]
530 fn test_nco_crcf_mix_nco_0() {
531 testbench_nco_crcf_mix(OscScheme::Nco, 0.000, 0.000);
532 }
533
534 #[test]
535 #[autotest_annotate(autotest_nco_crcf_mix_nco_1)]
536 fn test_nco_crcf_mix_nco_1() {
537 testbench_nco_crcf_mix(OscScheme::Nco, 1.234, 0.000);
538 }
539
540 #[test]
541 #[autotest_annotate(autotest_nco_crcf_mix_nco_2)]
542 fn test_nco_crcf_mix_nco_2() {
543 testbench_nco_crcf_mix(OscScheme::Nco, -1.234, 0.000);
544 }
545
546 #[test]
547 #[autotest_annotate(autotest_nco_crcf_mix_nco_3)]
548 fn test_nco_crcf_mix_nco_3() {
549 testbench_nco_crcf_mix(OscScheme::Nco, 99.000, 0.000);
550 }
551
552 #[test]
553 #[autotest_annotate(autotest_nco_crcf_mix_nco_4)]
554 fn test_nco_crcf_mix_nco_4() {
555 testbench_nco_crcf_mix(OscScheme::Nco, PI, 0.000);
556 }
557
558 #[test]
559 #[autotest_annotate(autotest_nco_crcf_mix_nco_5)]
560 fn test_nco_crcf_mix_nco_5() {
561 testbench_nco_crcf_mix(OscScheme::Nco, 0.000, PI);
562 }
563
564 #[test]
565 #[autotest_annotate(autotest_nco_crcf_mix_nco_6)]
566 fn test_nco_crcf_mix_nco_6() {
567 testbench_nco_crcf_mix(OscScheme::Nco, 0.000, -PI);
568 }
569
570 #[test]
571 #[autotest_annotate(autotest_nco_crcf_mix_nco_7)]
572 fn test_nco_crcf_mix_nco_7() {
573 testbench_nco_crcf_mix(OscScheme::Nco, 0.000, 0.123);
574 }
575
576 #[test]
577 #[autotest_annotate(autotest_nco_crcf_mix_nco_8)]
578 fn test_nco_crcf_mix_nco_8() {
579 testbench_nco_crcf_mix(OscScheme::Nco, 0.000, -0.123);
580 }
581
582 #[test]
583 #[autotest_annotate(autotest_nco_crcf_mix_nco_9)]
584 fn test_nco_crcf_mix_nco_9() {
585 testbench_nco_crcf_mix(OscScheme::Nco, 0.000, 1e-5);
586 }
587
588 #[test]
589 #[autotest_annotate(autotest_nco_crcf_mix_vco_0)]
590 fn test_nco_crcf_mix_vco_0() {
591 testbench_nco_crcf_mix(OscScheme::Vco, 0.000, 0.000);
592 }
593
594 #[test]
595 #[autotest_annotate(autotest_nco_crcf_mix_vco_1)]
596 fn test_nco_crcf_mix_vco_1() {
597 testbench_nco_crcf_mix(OscScheme::Vco, 1.234, 0.000);
598 }
599
600 #[test]
601 #[autotest_annotate(autotest_nco_crcf_mix_vco_2)]
602 fn test_nco_crcf_mix_vco_2() {
603 testbench_nco_crcf_mix(OscScheme::Vco, -1.234, 0.000);
604 }
605
606 #[test]
607 #[autotest_annotate(autotest_nco_crcf_mix_vco_3)]
608 fn test_nco_crcf_mix_vco_3() {
609 testbench_nco_crcf_mix(OscScheme::Vco, 99.000, 0.000);
610 }
611
612 #[test]
613 #[autotest_annotate(autotest_nco_crcf_mix_vco_4)]
614 fn test_nco_crcf_mix_vco_4() {
615 testbench_nco_crcf_mix(OscScheme::Vco, PI, 0.000);
616 }
617
618 #[test]
619 #[autotest_annotate(autotest_nco_crcf_mix_vco_5)]
620 fn test_nco_crcf_mix_vco_5() {
621 testbench_nco_crcf_mix(OscScheme::Vco, 0.000, PI);
622 }
623
624 #[test]
625 #[autotest_annotate(autotest_nco_crcf_mix_vco_6)]
626 fn test_nco_crcf_mix_vco_6() {
627 testbench_nco_crcf_mix(OscScheme::Vco, 0.000, -PI);
628 }
629
630 #[test]
631 #[autotest_annotate(autotest_nco_crcf_mix_vco_7)]
632 fn test_nco_crcf_mix_vco_7() {
633 testbench_nco_crcf_mix(OscScheme::Vco, 0.000, 0.123);
634 }
635
636 #[test]
637 #[autotest_annotate(autotest_nco_crcf_mix_vco_8)]
638 fn test_nco_crcf_mix_vco_8() {
639 testbench_nco_crcf_mix(OscScheme::Vco, 0.000, -0.123);
640 }
641
642 #[test]
643 #[autotest_annotate(autotest_nco_crcf_mix_vco_9)]
644 fn test_nco_crcf_mix_vco_9() {
645 testbench_nco_crcf_mix(OscScheme::Vco, 0.000, 1e-5);
646 }
647
648 fn nco_crcf_spectrum_test(scheme: OscScheme, freq: f32) {
649 let num_samples = 1 << 16;
650 let nfft: usize = 9600;
651
652 let mut nco = Osc::new(scheme);
653 nco.set_frequency(2.0 * PI * freq);
654
655 let buf_len = 3 * nfft;
656 let mut buf_0 = vec![Complex::new(0.0, 0.0); buf_len];
657 let mut buf_1 = vec![Complex::new(0.0, 0.0); buf_len];
658 for i in 0..buf_len {
659 buf_0[i] = Complex::new(1.0 / (nfft as f32).sqrt(), 0.0);
660 }
661
662 let mut psd = Spgram::new(nfft, WindowType::BlackmanHarris, nfft, nfft / 2).unwrap();
663
664 while psd.get_num_samples_total() < num_samples {
665 nco.mix_block_up(&buf_0, &mut buf_1).unwrap();
666 if psd.get_num_samples_total() == 0 {
667 for i in 0..buf_len {
668 buf_1[i] *= hann(i, 2 * buf_len).unwrap();
669 }
670 }
671 psd.write(&buf_1);
672 }
673
674 #[rustfmt::skip]
675 let regions = [
676 PsdRegion { fmin: -0.5, fmax: freq - 0.002, pmin: 0.0, pmax: -60.0, test_lo: false, test_hi: true },
677 PsdRegion { fmin: freq - 0.002, fmax: freq + 0.002, pmin: 0.0, pmax: 0.0, test_lo: false, test_hi: true },
678 PsdRegion { fmin: freq + 0.002, fmax: 0.5, pmin: 0.0, pmax: -60.0, test_lo: false, test_hi: true },
679 ];
680
681 let result = validate_psd_spgramcf(&psd, ®ions).unwrap();
682 assert!(result, "PSD test failed");
683 }
684
685 #[test]
686 #[autotest_annotate(autotest_nco_crcf_spectrum_nco_f00)]
687 fn test_nco_crcf_spectrum_nco_0() {
688 nco_crcf_spectrum_test(OscScheme::Nco, 0.000);
689 }
690
691 #[test]
692 #[autotest_annotate(autotest_nco_crcf_spectrum_nco_f01)]
693 fn test_nco_crcf_spectrum_nco_1() {
694 nco_crcf_spectrum_test(OscScheme::Nco, 0.1234);
695 }
696
697 #[test]
698 #[autotest_annotate(autotest_nco_crcf_spectrum_nco_f02)]
699 fn test_nco_crcf_spectrum_nco_2() {
700 nco_crcf_spectrum_test(OscScheme::Nco, -0.1234);
701 }
702
703 #[test]
704 #[autotest_annotate(autotest_nco_crcf_spectrum_nco_f03)]
705 fn test_nco_crcf_spectrum_nco_3() {
706 nco_crcf_spectrum_test(OscScheme::Nco, 0.25);
707 }
708
709 #[test]
710 #[autotest_annotate(autotest_nco_crcf_spectrum_nco_f04)]
711 fn test_nco_crcf_spectrum_nco_4() {
712 nco_crcf_spectrum_test(OscScheme::Nco, 0.1);
713 }
714 #[test]
715 #[autotest_annotate(autotest_nco_crcf_spectrum_vco_f00)]
716 fn test_nco_crcf_spectrum_vco_0() {
717 nco_crcf_spectrum_test(OscScheme::Vco, 0.0);
718 }
719
720 #[test]
721 #[autotest_annotate(autotest_nco_crcf_spectrum_vco_f01)]
722 fn test_nco_crcf_spectrum_vco_1() {
723 nco_crcf_spectrum_test(OscScheme::Vco, 0.1234);
724 }
725
726 #[test]
727 #[autotest_annotate(autotest_nco_crcf_spectrum_vco_f02)]
728 fn test_nco_crcf_spectrum_vco_2() {
729 nco_crcf_spectrum_test(OscScheme::Vco, -0.1234);
730 }
731
732 #[test]
733 #[autotest_annotate(autotest_nco_crcf_spectrum_vco_f03)]
734 fn test_nco_crcf_spectrum_vco_3() {
735 nco_crcf_spectrum_test(OscScheme::Vco, 0.25);
736 }
737
738 #[test]
739 #[autotest_annotate(autotest_nco_crcf_spectrum_vco_f04)]
740 fn test_nco_crcf_spectrum_vco_4() {
741 nco_crcf_spectrum_test(OscScheme::Vco, 0.1);
742 }
743
744 fn nco_crcf_frequency_test(scheme: OscScheme, phase: f32, frequency: f32, sincos: &[Complex<f32>], num_samples: usize, tol: f32) {
746 let mut nco = Osc::new(scheme);
748
749 nco.set_phase(phase);
751 nco.set_frequency(frequency);
752
753 for i in 0..num_samples {
755 let y_test = nco.cexp();
757
758 let y = sincos[i];
760
761 assert!((y_test.re - y.re).abs() < tol, "Real part error at index {}: expected {}, got {}", i, y.re, y_test.re);
763 assert!((y_test.im - y.im).abs() < tol, "Imaginary part error at index {}: expected {}, got {}", i, y.im, y_test.im);
764
765 nco.step();
767 }
768 }
769
770 #[test]
771 #[autotest_annotate(autotest_nco_crcf_frequency)]
772 fn test_nco_crcf_frequency() {
773 let tol = 0.04;
775
776 nco_crcf_frequency_test(OscScheme::Nco, 0.0, 1.0 / 2.0_f32.sqrt(), &NCO_SINCOS_FSQRT1_2, 256, tol); nco_crcf_frequency_test(OscScheme::Nco, 0.0, 1.0 / 3.0_f32.sqrt(), &NCO_SINCOS_FSQRT1_3, 256, tol); nco_crcf_frequency_test(OscScheme::Nco, 0.0, 1.0 / 5.0_f32.sqrt(), &NCO_SINCOS_FSQRT1_5, 256, tol); nco_crcf_frequency_test(OscScheme::Nco, 0.0, 1.0 / 7.0_f32.sqrt(), &NCO_SINCOS_FSQRT1_7, 256, tol); }
782
783 pub fn generate_sincos(frequency: f32, num_samples: usize) -> Vec<Complex<f32>> {
784 (0..num_samples)
785 .map(|i| {
786 let phase = i as f32 * frequency;
787 Complex::new(phase.cos(), phase.sin())
788 })
789 .collect()
790 }
791
792 lazy_static! {
794 pub static ref NCO_SINCOS_FSQRT1_2: Vec<Complex<f32>> = generate_sincos(1.0 / 2.0_f32.sqrt(), 256);
795 pub static ref NCO_SINCOS_FSQRT1_3: Vec<Complex<f32>> = generate_sincos(1.0 / 3.0_f32.sqrt(), 256);
796 pub static ref NCO_SINCOS_FSQRT1_5: Vec<Complex<f32>> = generate_sincos(1.0 / 5.0_f32.sqrt(), 256);
797 pub static ref NCO_SINCOS_FSQRT1_7: Vec<Complex<f32>> = generate_sincos(1.0 / 7.0_f32.sqrt(), 256);
798 }
799}