use num_complex::Complex;
use crate::dsp::DspBlock;
pub struct Rotate {
rot: Complex<f32>,
mult: Complex<f32>,
}
impl Rotate {
pub fn new(angle: f32) -> Self {
Self {
rot: Complex::new(1.0, 0.0),
mult: Complex::new(angle.cos(), angle.sin()),
}
}
pub fn reset(&mut self) {
self.rot = Complex::new(1.0, 0.0);
}
}
impl DspBlock for Rotate {
fn process(&mut self, data: &[Complex<f32>]) -> Vec<Complex<f32>> {
let mut output = Vec::with_capacity(data.len());
for &sample in data {
let rr = sample.re * self.rot.re;
let ii = sample.im * self.rot.im;
let ri = sample.re * self.rot.im;
let ir = sample.im * self.rot.re;
output.push(Complex::new(rr - ii, ir + ri));
self.rot *= self.mult;
}
let norm = self.rot.norm();
if norm > 0.0 {
self.rot /= norm;
}
output
}
}
impl Default for Rotate {
fn default() -> Self {
Self::new(0.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
use approx::assert_relative_eq;
#[test]
fn test_rotate_new() {
let rotator = Rotate::new(0.1);
assert_eq!(rotator.rot, Complex::new(1.0, 0.0));
assert_relative_eq!(rotator.mult.re, 0.1_f32.cos(), epsilon = 1e-6);
assert_relative_eq!(rotator.mult.im, 0.1_f32.sin(), epsilon = 1e-6);
}
#[test]
fn test_rotate_zero_angle() {
let mut rotator = Rotate::new(0.0);
let input = vec![
Complex::new(1.0, 0.0),
Complex::new(0.5, 0.5),
Complex::new(0.0, 1.0),
];
let output = rotator.process(&input);
for (inp, out) in input.iter().zip(output.iter()) {
assert_relative_eq!(inp.re, out.re, epsilon = 1e-6);
assert_relative_eq!(inp.im, out.im, epsilon = 1e-6);
}
}
#[test]
fn test_rotate_90_degrees() {
let mut rotator = Rotate::new(std::f32::consts::FRAC_PI_2);
let input = vec![Complex::new(1.0, 0.0)];
let output = rotator.process(&input);
assert_relative_eq!(output[0].re, 1.0, epsilon = 1e-6);
assert_relative_eq!(output[0].im, 0.0, epsilon = 1e-6);
}
#[test]
fn test_rotate_180_degrees() {
let mut rotator = Rotate::new(std::f32::consts::PI);
let input = vec![Complex::new(1.0, 0.0), Complex::new(1.0, 0.0)];
let output = rotator.process(&input);
assert_relative_eq!(output[0].re, 1.0, epsilon = 1e-6);
assert_relative_eq!(output[0].im, 0.0, epsilon = 1e-6);
assert_relative_eq!(output[1].re, -1.0, epsilon = 1e-6);
assert_relative_eq!(output[1].im, 0.0, epsilon = 1e-6);
}
#[test]
fn test_rotate_accumulation() {
let angle = 0.1;
let mut rotator = Rotate::new(angle);
let input = vec![Complex::new(1.0, 0.0); 10];
let output = rotator.process(&input);
for (i, &sample) in output.iter().enumerate() {
let expected_angle = angle * i as f32;
let expected = Complex::new(expected_angle.cos(), expected_angle.sin());
assert_relative_eq!(sample.re, expected.re, epsilon = 1e-4);
assert_relative_eq!(sample.im, expected.im, epsilon = 1e-4);
}
}
#[test]
fn test_rotate_normalization() {
let mut rotator = Rotate::new(0.01);
let input = vec![Complex::new(1.0, 0.0); 10000];
let _ = rotator.process(&input);
let norm = rotator.rot.norm();
assert_relative_eq!(norm, 1.0, epsilon = 1e-4);
}
#[test]
fn test_rotate_reset() {
let mut rotator = Rotate::new(0.5);
let input = vec![Complex::new(1.0, 0.0); 10];
let _ = rotator.process(&input);
rotator.reset();
assert_eq!(rotator.rot, Complex::new(1.0, 0.0));
}
#[test]
fn test_rotate_empty_input() {
let mut rotator = Rotate::new(0.1);
let input: Vec<Complex<f32>> = vec![];
let output = rotator.process(&input);
assert_eq!(output.len(), 0);
}
#[test]
fn test_rotate_output_length() {
let mut rotator = Rotate::new(0.1);
for len in [1, 10, 100, 1000] {
let input = vec![Complex::new(1.0, 0.0); len];
let output = rotator.process(&input);
assert_eq!(output.len(), len);
}
}
#[test]
fn test_rotate_frequency_shift() {
let sample_rate = 1_000_000.0;
let freq_offset = 100_000.0; let angle = -2.0 * std::f32::consts::PI * freq_offset / sample_rate;
let mut rotator = Rotate::new(angle);
let n_samples = 100;
let mut input = Vec::with_capacity(n_samples);
for i in 0..n_samples {
let phase = 2.0 * std::f32::consts::PI * freq_offset * i as f32 / sample_rate;
input.push(Complex::new(phase.cos(), phase.sin()));
}
let output = rotator.process(&input);
let mean_re = output.iter().map(|c| c.re).sum::<f32>() / output.len() as f32;
let mean_im = output.iter().map(|c| c.im).sum::<f32>() / output.len() as f32;
for sample in &output[10..] {
assert_relative_eq!(sample.re, mean_re, epsilon = 0.1);
assert_relative_eq!(sample.im, mean_im, epsilon = 0.1);
}
}
}