use core::f32::consts::TAU;
use crate::core::ModulationParams;
use super::Wspr;
pub fn synthesize_audio(
symbols: &[u8; 162],
sample_rate: u32,
base_freq_hz: f32,
amplitude: f32,
) -> Vec<f32> {
let nsps = (sample_rate as f32 * <Wspr as ModulationParams>::SYMBOL_DT).round() as usize;
let tone_spacing = <Wspr as ModulationParams>::TONE_SPACING_HZ;
let mut out = Vec::with_capacity(nsps * 162);
let mut phase = 0.0f32;
for &sym in symbols {
assert!(sym < 4, "WSPR channel symbol must be in 0..=3");
let freq = base_freq_hz + sym as f32 * tone_spacing;
let dphi = TAU * freq / sample_rate as f32;
for _ in 0..nsps {
out.push(amplitude * phase.cos());
phase += dphi;
if phase > TAU {
phase -= TAU;
} else if phase < -TAU {
phase += TAU;
}
}
}
out
}
pub fn synthesize_type1(
callsign: &str,
grid: &str,
power_dbm: i32,
sample_rate: u32,
base_freq_hz: f32,
amplitude: f32,
) -> Option<Vec<f32>> {
let info = crate::msg::wspr::pack_type1(callsign, grid, power_dbm)?;
let symbols = super::encode_channel_symbols(&info);
Some(synthesize_audio(
&symbols,
sample_rate,
base_freq_hz,
amplitude,
))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn synthesizes_162_symbol_buffer_at_12k() {
let symbols = [0u8; 162];
let audio = synthesize_audio(&symbols, 12_000, 1500.0, 0.5);
assert_eq!(audio.len(), 8192 * 162);
}
#[test]
fn synthesizes_valid_message() {
let audio =
synthesize_type1("K1ABC", "FN42", 37, 12_000, 1500.0, 0.3).expect("valid message");
assert_eq!(audio.len(), 8192 * 162);
let peak = audio.iter().cloned().fold(0.0f32, f32::max);
assert!(
peak > 0.28 && peak < 0.32,
"peak amplitude out of range: {}",
peak
);
}
}