use std::f32::consts::PI;
use num_complex::Complex32;
use crate::core::FecCodec;
use crate::fec::Ldpc240_101;
use super::framing::{FrameHeader, HEADER_BYTES, INFO_BYTES_PER_BLOCK, PackError, pack_header};
use super::interleaver::interleave;
use super::puncture::{Mode, puncture};
use super::sync_pattern::{PREAMBLE_LEN, preamble_for};
const N_LDPC: usize = 240;
const K_LDPC: usize = 101;
const PAYLOAD_BITS_PER_BLOCK: usize = INFO_BYTES_PER_BLOCK * 8;
pub(super) const SAMPLE_RATE_HZ: f32 = 12_000.0;
pub(super) const RRC_SPAN_SYMS: usize = 6;
pub(super) const RRC_ALPHA: f32 = 0.5;
const PI4_DQPSK_DELTA: [f32; 4] = [PI / 4.0, 3.0 * PI / 4.0, -PI / 4.0, -3.0 * PI / 4.0];
pub fn expected_total_symbols(mode: Mode, n_payload_blocks: u8) -> usize {
let header_sym = N_LDPC / 2;
let payload_sym = (n_payload_blocks as usize) * mode.ch_bits_per_block() / 2;
PREAMBLE_LEN + header_sym + payload_sym
}
#[inline]
pub fn encode_output_len(mode: Mode, n_payload_blocks: u8) -> usize {
let nsps = mode.nsps();
let rrc_len = RRC_SPAN_SYMS * nsps + 1;
expected_total_symbols(mode, n_payload_blocks) * nsps + rrc_len
}
pub fn encode(
header: &FrameHeader,
payload: &[u8],
audio_centre_hz: f32,
) -> Result<Vec<f32>, PackError> {
let mut audio = vec![0.0f32; encode_output_len(header.mode, header.block_count)];
encode_into(&mut audio, header, payload, audio_centre_hz)?;
Ok(audio)
}
pub fn encode_into(
out: &mut [f32],
header: &FrameHeader,
payload: &[u8],
audio_centre_hz: f32,
) -> Result<(), PackError> {
let mode = header.mode;
let n_blocks = header.block_count as usize;
let payload_capacity = n_blocks * INFO_BYTES_PER_BLOCK;
if payload.len() > payload_capacity {
return Err(PackError::PayloadTooLarge(payload.len()));
}
assert_eq!(
out.len(),
encode_output_len(mode, header.block_count),
"encode_into: out.len() must equal encode_output_len()"
);
let mut padded_payload = vec![0u8; payload_capacity];
padded_payload[..payload.len()].copy_from_slice(payload);
let header_bytes = pack_header(header, &padded_payload)?;
let mut header_info_bytes = [0u8; INFO_BYTES_PER_BLOCK];
header_info_bytes[..HEADER_BYTES].copy_from_slice(&header_bytes);
let fec = Ldpc240_101;
let mut info_buf = vec![0u8; K_LDPC];
let mut codeword_buf = vec![0u8; N_LDPC];
bytes_to_bits_msb(&header_info_bytes, &mut info_buf[..PAYLOAD_BITS_PER_BLOCK]);
fec.encode(&info_buf, &mut codeword_buf);
let header_codeword: Vec<u8> = codeword_buf.clone();
let block_ch_bits = mode.ch_bits_per_block();
let mut interleaver_in: Vec<u8> = Vec::with_capacity(n_blocks * block_ch_bits);
for block_idx in 0..n_blocks {
let chunk = &padded_payload
[block_idx * INFO_BYTES_PER_BLOCK..(block_idx + 1) * INFO_BYTES_PER_BLOCK];
bytes_to_bits_msb(chunk, &mut info_buf[..PAYLOAD_BITS_PER_BLOCK]);
for b in &mut info_buf[PAYLOAD_BITS_PER_BLOCK..] {
*b = 0;
}
fec.encode(&info_buf, &mut codeword_buf);
interleaver_in.extend_from_slice(&puncture(&codeword_buf, mode));
}
let interleaved = interleave(&interleaver_in, n_blocks);
let mut data_bits: Vec<u8> = Vec::with_capacity(N_LDPC + interleaved.len());
data_bits.extend_from_slice(&header_codeword);
data_bits.extend_from_slice(&interleaved);
debug_assert!(data_bits.len().is_multiple_of(2));
let n_data_syms = data_bits.len() / 2;
let mut deltas: Vec<f32> = Vec::with_capacity(n_data_syms);
for sym_idx in 0..n_data_syms {
let pair = ((data_bits[sym_idx * 2] << 1) | data_bits[sym_idx * 2 + 1]) as usize;
deltas.push(PI4_DQPSK_DELTA[pair]);
}
let preamble_bits = preamble_for(mode);
let mut symbols: Vec<Complex32> = Vec::with_capacity(PREAMBLE_LEN + n_data_syms);
for &b in preamble_bits.iter() {
symbols.push(Complex32::new(if b { -1.0 } else { 1.0 }, 0.0));
}
let mut prev = symbols[symbols.len() - 1];
for &delta in &deltas {
let next = prev * Complex32::from_polar(1.0, delta);
symbols.push(next);
prev = next;
}
let nsps = mode.nsps();
let rrc = rrc_pulse(RRC_ALPHA, RRC_SPAN_SYMS, nsps);
let total_samples = symbols.len() * nsps + rrc.len();
let mut baseband = vec![Complex32::new(0.0, 0.0); total_samples];
for (i, &sym) in symbols.iter().enumerate() {
let start = i * nsps;
for (j, &tap) in rrc.iter().enumerate() {
let pos = start + j;
if pos < baseband.len() {
baseband[pos] += sym * tap;
}
}
}
let two_pi_fc_dt = 2.0 * PI * audio_centre_hz / SAMPLE_RATE_HZ;
for n in 0..total_samples {
let phase = two_pi_fc_dt * n as f32;
let (s, c) = phase.sin_cos();
out[n] = baseband[n].re * c - baseband[n].im * s;
}
let peak = out.iter().map(|s| s.abs()).fold(0.0_f32, f32::max);
if peak > 1.0 {
let scale = 1.0 / peak;
for s in out.iter_mut() {
*s *= scale;
}
}
Ok(())
}
fn bytes_to_bits_msb(bytes: &[u8], bits: &mut [u8]) {
debug_assert!(bits.len() >= bytes.len() * 8);
for (byte_idx, &byte) in bytes.iter().enumerate() {
for bit_idx in 0..8 {
bits[byte_idx * 8 + bit_idx] = (byte >> (7 - bit_idx)) & 1;
}
}
}
pub(super) fn rrc_pulse(alpha: f32, span_syms: usize, samples_per_sym: usize) -> Vec<f32> {
let n = span_syms * samples_per_sym;
let mut h = vec![0.0_f32; n + 1];
let center = n as f32 / 2.0;
for (i, h_i) in h.iter_mut().enumerate() {
let t = (i as f32 - center) / samples_per_sym as f32;
*h_i = if t.abs() < 1e-6 {
1.0 - alpha + 4.0 * alpha / PI
} else if (t.abs() - 1.0 / (4.0 * alpha)).abs() < 1e-6 {
(alpha / 2.0_f32.sqrt())
* ((1.0 + 2.0 / PI) * (PI / (4.0 * alpha)).sin()
+ (1.0 - 2.0 / PI) * (PI / (4.0 * alpha)).cos())
} else {
let pi_t = PI * t;
let four_at = 4.0 * alpha * t;
((pi_t * (1.0 - alpha)).sin() + four_at * (pi_t * (1.0 + alpha)).cos())
/ (pi_t * (1.0 - four_at * four_at))
};
}
let norm: f32 = h.iter().map(|x| x * x).sum::<f32>().sqrt();
if norm > 0.0 {
for x in h.iter_mut() {
*x /= norm;
}
}
h
}
#[cfg(test)]
mod tests {
use super::*;
use crate::uvpacket::AUDIO_CENTRE_HZ;
fn header_for(mode: Mode, n_blocks: u8) -> FrameHeader {
FrameHeader {
mode,
block_count: n_blocks,
app_type: 1,
sequence: 0,
}
}
#[test]
fn encode_succeeds_all_modes() {
for mode in [
Mode::Robust,
Mode::Standard,
Mode::UltraRobust,
Mode::Express,
] {
for n_blocks in [1u8, 4, 18, 32] {
let header = header_for(mode, n_blocks);
let cap = (n_blocks as usize) * INFO_BYTES_PER_BLOCK;
let payload = vec![0xA5_u8; cap];
let audio = encode(&header, &payload, AUDIO_CENTRE_HZ).unwrap();
assert!(!audio.is_empty(), "{mode:?} n={n_blocks}: empty audio");
}
}
}
#[test]
fn encode_peak_amplitude_bounded() {
let header = header_for(Mode::Robust, 4);
let audio = encode(&header, b"hello", AUDIO_CENTRE_HZ).unwrap();
let peak = audio.iter().map(|s| s.abs()).fold(0.0_f32, f32::max);
assert!(peak <= 1.0001, "peak {peak} > 1");
}
#[test]
fn encode_sample_count_matches_formula() {
for mode in [
Mode::Robust,
Mode::Standard,
Mode::UltraRobust,
Mode::Express,
] {
let n_blocks = 4u8;
let header = header_for(mode, n_blocks);
let cap = (n_blocks as usize) * INFO_BYTES_PER_BLOCK;
let payload = vec![0xCC_u8; cap];
let audio = encode(&header, &payload, AUDIO_CENTRE_HZ).unwrap();
let n_syms = expected_total_symbols(mode, n_blocks);
let nsps = mode.nsps();
let rrc_len = RRC_SPAN_SYMS * nsps + 1;
let expected = n_syms * nsps + rrc_len;
assert_eq!(
audio.len(),
expected,
"{mode:?}: got {} samples, expected {}",
audio.len(),
expected,
);
}
}
#[test]
fn distinct_payloads_diverge() {
let header = header_for(Mode::Robust, 4);
let a = encode(&header, b"alpha", AUDIO_CENTRE_HZ).unwrap();
let b = encode(&header, b"bravo", AUDIO_CENTRE_HZ).unwrap();
assert_eq!(a.len(), b.len());
let differences = a
.iter()
.zip(b.iter())
.filter(|(x, y)| (**x - **y).abs() > 1e-4)
.count();
assert!(
differences > a.len() / 4,
"expected substantial divergence, got {differences} / {}",
a.len(),
);
}
#[test]
fn distinct_modes_use_distinct_preambles() {
let payload = vec![0u8; 12];
let h_r = header_for(Mode::Robust, 1);
let h_s = header_for(Mode::Standard, 1);
let a_r = encode(&h_r, &payload, AUDIO_CENTRE_HZ).unwrap();
let a_s = encode(&h_s, &payload, AUDIO_CENTRE_HZ).unwrap();
let nsps = Mode::Robust.nsps();
let diffs = a_r[..PREAMBLE_LEN * nsps]
.iter()
.zip(a_s[..PREAMBLE_LEN * nsps].iter())
.filter(|(x, y)| (**x - **y).abs() > 1e-3)
.count();
assert!(
diffs > PREAMBLE_LEN * nsps / 4,
"Robust and Standard preambles should differ substantially in preamble window: {diffs}",
);
}
#[test]
fn oversize_payload_rejected() {
let header = header_for(Mode::Robust, 1);
let too_big = vec![0u8; INFO_BYTES_PER_BLOCK + 1];
assert!(matches!(
encode(&header, &too_big, AUDIO_CENTRE_HZ).unwrap_err(),
PackError::PayloadTooLarge(_),
));
}
}