use num_complex::Complex32 as C32;
use crate::core::{Block, WorkReport};
use crate::dsp::{FirLowpass, Nco, mix_with_nco};
pub struct SsbPhasingMod {
fs: f32,
audio_if_hz: f32,
pre_lp: FirLowpass,
rf_nco: Nco,
sideband: f32,
gain: f32,
i_buf: Vec<f32>,
q_buf: Vec<f32>,
tmp: Vec<f32>,
cos_nco: Nco,
sin_nco: Nco,
}
impl SsbPhasingMod {
pub fn new(sample_rate: f32, audio_bw_hz: f32, audio_if_hz: f32, rf_hz: f32, usb: bool) -> Self {
let trans_hz = (audio_bw_hz * 0.15).clamp(200.0, 600.0);
let pre_lp = FirLowpass::design(sample_rate, audio_bw_hz, trans_hz);
let side = if usb { 1.0 } else { -1.0 };
Self {
fs: sample_rate,
audio_if_hz,
pre_lp,
rf_nco: Nco::new(rf_hz, sample_rate),
sideband: side,
gain: 1.0,
i_buf: Vec::new(),
q_buf: Vec::new(),
tmp: Vec::new(),
cos_nco: Nco::new(audio_if_hz, sample_rate),
sin_nco: Nco::new(audio_if_hz, sample_rate),
}
}
pub fn set_gain(&mut self, g: f32) { self.gain = g; }
pub fn set_audio_if(&mut self, f: f32) {
self.audio_if_hz = f;
self.cos_nco = Nco::new(f, self.fs);
self.sin_nco = Nco::new(f, self.fs);
}
pub fn set_usb(&mut self, usb: bool) { self.sideband = if usb { 1.0 } else { -1.0 }; }
}
impl Block for SsbPhasingMod {
type In = f32; type Out = C32;
fn process(&mut self, input: &[f32], output: &mut [C32]) -> WorkReport {
let n = input.len().min(output.len());
if self.i_buf.len() < n { self.i_buf.resize(n, 0.0); }
if self.q_buf.len() < n { self.q_buf.resize(n, 0.0); }
let (i_buf, q_buf) = (&mut self.i_buf[..n], &mut self.q_buf[..n]);
for k in 0..n {
let (c, s) = self.cos_nco.next_cs(); let x = input[k];
i_buf[k] = x * c;
q_buf[k] = x * s;
}
if self.tmp.len() != n { self.tmp.resize(n, 0.0); }
self.tmp.copy_from_slice(&i_buf[..n]);
self.pre_lp.process_block(&self.tmp[..], &mut i_buf[..n]);
self.tmp.copy_from_slice(&q_buf[..n]);
self.pre_lp.process_block(&self.tmp[..], &mut q_buf[..n]);
for k in 0..n {
let z = C32::new(i_buf[k], self.sideband * q_buf[k]) * self.gain;
output[k] = mix_with_nco(z, &mut self.rf_nco);
}
WorkReport { in_read: n, out_written: n }
}
}