Skip to main content

orion_sdr/modulate/
pm.rs

1// Copyright (c) 2025-2026 G & R Associates LLC
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4use crate::core::{Block, WorkReport};
5use crate::dsp::{Nco, mix_with_nco};
6use num_complex::Complex32 as C32;
7
8/// PM (direct) – instantaneous phase φ = kp * x[n], optional RF upconversion.
9#[derive(Debug, Clone)]
10pub struct PmDirectPhaseMod {
11    kp_rad_per_unit: f32,
12    rf_nco: Nco,
13    gain: f32,
14}
15
16impl PmDirectPhaseMod {
17    pub fn new(sample_rate: f32, kp_rad_per_unit: f32, rf_hz: f32) -> Self {
18        Self {
19            kp_rad_per_unit,
20            rf_nco: Nco::new(rf_hz, sample_rate),
21            gain: 1.0,
22        }
23    }
24    pub fn set_gain(&mut self, g: f32) {
25        self.gain = g;
26    }
27    pub fn set_sensitivity(&mut self, kp_rad_per_unit: f32) {
28        self.kp_rad_per_unit = kp_rad_per_unit;
29    }
30}
31
32impl Block for PmDirectPhaseMod {
33    type In = f32;
34    type Out = C32;
35
36    fn process(&mut self, input: &[f32], output: &mut [C32]) -> WorkReport {
37        let n = input.len().min(output.len());
38        for i in 0..n {
39            let phi = self.kp_rad_per_unit * input[i];
40            let base = C32::new(phi.cos(), phi.sin()) * self.gain;
41            output[i] = mix_with_nco(base, &mut self.rf_nco);
42        }
43        WorkReport {
44            in_read: n,
45            out_written: n,
46        }
47    }
48}