[refactor](trx-ftx): move ft2_encode to ft2 module, remove all allow clauses
Move ft2_encode from ft4/ to ft2/ where it belongs. Remove all module-level #[allow] suppressions and fix the underlying issues: - Remove dead code: wf_mag_at, xor_rows, unused Monitor IFFT fields, OsdBox.size - Gate encode174_to_bits with #[cfg(test)] (only used in tests) - Convert 40+ C-style index loops to idiomatic iterators - Add targeted #[allow(clippy::too_many_arguments)] on two OSD functions Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <sjg@haxx.space>
This commit is contained in:
@@ -49,16 +49,7 @@ pub(crate) fn get_cand_offset(wf: &Waterfall, cand: &Candidate) -> usize {
|
||||
offset.max(0) as usize
|
||||
}
|
||||
|
||||
pub(crate) fn wf_mag_at(wf: &Waterfall, base: usize, idx: isize) -> &WfElem {
|
||||
let i = (base as isize + idx).max(0) as usize;
|
||||
if i < wf.mag.len() {
|
||||
&wf.mag[i]
|
||||
} else {
|
||||
&DEFAULT_WF_ELEM
|
||||
}
|
||||
}
|
||||
|
||||
// Leaked reference for out-of-bounds default
|
||||
// Default element for out-of-bounds waterfall access
|
||||
pub(crate) static DEFAULT_WF_ELEM: WfElem = WfElem {
|
||||
mag: -120.0,
|
||||
phase: 0.0,
|
||||
@@ -256,8 +247,8 @@ pub fn pack_bits(bit_array: &[u8], num_bits: usize, packed: &mut [u8]) {
|
||||
}
|
||||
let mut mask: u8 = 0x80;
|
||||
let mut byte_idx = 0;
|
||||
for i in 0..num_bits {
|
||||
if bit_array[i] != 0 {
|
||||
for &bit in bit_array.iter().take(num_bits) {
|
||||
if bit != 0 {
|
||||
packed[byte_idx] |= mask;
|
||||
}
|
||||
mask >>= 1;
|
||||
@@ -312,19 +303,19 @@ pub fn ftx_post_decode_snr(wf: &Waterfall, cand: &Candidate, message: &FtxMessag
|
||||
let mut sum_snr = 0.0f32;
|
||||
let mut n_valid = 0;
|
||||
|
||||
for sym in 0..nn {
|
||||
for (sym, &tone) in tones.iter().enumerate().take(nn) {
|
||||
let block_abs = cand.time_offset as i32 + sym as i32;
|
||||
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
||||
continue;
|
||||
}
|
||||
|
||||
let p_offset = base + sym * wf.block_stride;
|
||||
let sig_db = wf_mag_safe(wf, p_offset + tones[sym] as usize).mag;
|
||||
let sig_db = wf_mag_safe(wf, p_offset + tone as usize).mag;
|
||||
|
||||
let mut noise_min = 0.0f32;
|
||||
let mut found_noise = false;
|
||||
for t in 0..num_tones {
|
||||
if t == tones[sym] as usize {
|
||||
if t == tone as usize {
|
||||
continue;
|
||||
}
|
||||
let db = wf_mag_safe(wf, p_offset + t).mag;
|
||||
|
||||
@@ -33,10 +33,10 @@ pub(crate) fn encode174(message: &[u8], codeword: &mut [u8]) {
|
||||
let mut col_idx: usize = FTX_LDPC_K_BYTES - 1;
|
||||
|
||||
// Compute the LDPC checksum bits and store them in codeword
|
||||
for i in 0..FTX_LDPC_M {
|
||||
for gen_row in FTX_LDPC_GENERATOR.iter().take(FTX_LDPC_M) {
|
||||
let mut nsum: u8 = 0;
|
||||
for j in 0..FTX_LDPC_K_BYTES {
|
||||
nsum ^= parity8(message[j] & FTX_LDPC_GENERATOR[i][j]);
|
||||
nsum ^= parity8(message[j] & gen_row[j]);
|
||||
}
|
||||
|
||||
if !nsum.is_multiple_of(2) {
|
||||
@@ -55,7 +55,7 @@ pub(crate) fn encode174(message: &[u8], codeword: &mut [u8]) {
|
||||
///
|
||||
/// Each element of the returned array is 0 or 1.
|
||||
/// Uses the same (174, 91) LDPC generator as `encode174`.
|
||||
#[allow(dead_code)]
|
||||
#[cfg(test)]
|
||||
pub(crate) fn encode174_to_bits(a91: &[u8; FTX_LDPC_K_BYTES]) -> [u8; super::protocol::FTX_LDPC_N] {
|
||||
use super::protocol::FTX_LDPC_N;
|
||||
let mut codeword_packed = [0u8; FTX_LDPC_N_BYTES];
|
||||
|
||||
@@ -247,8 +247,7 @@ fn parse_cq_modifier(s: &str) -> Option<i32> {
|
||||
let mut nlet = 0;
|
||||
let mut m: i32 = 0;
|
||||
|
||||
for i in 3..8.min(bytes.len()) {
|
||||
let c = bytes[i];
|
||||
for &c in &bytes[3..8.min(bytes.len())] {
|
||||
if c == b' ' || c == 0 {
|
||||
break;
|
||||
} else if c.is_ascii_digit() {
|
||||
@@ -511,16 +510,14 @@ fn pack58(hash_table: Option<&mut CallsignHashTable>, callsign: &str) -> Option<
|
||||
|
||||
let mut result: u64 = 0;
|
||||
let mut c11 = String::with_capacity(12);
|
||||
let mut length = 0;
|
||||
|
||||
for ch in src.chars() {
|
||||
for (length, ch) in src.chars().enumerate() {
|
||||
if ch == '<' || length >= 11 {
|
||||
break;
|
||||
}
|
||||
c11.push(ch);
|
||||
let j = nchar(ch, CharTable::AlphanumSpaceSlash)?;
|
||||
result = result * 38 + j as u64;
|
||||
length += 1;
|
||||
}
|
||||
|
||||
save_callsign(hash_table, &c11)?;
|
||||
@@ -1156,9 +1153,9 @@ pub fn ftx_message_decode_free(msg: &FtxMessage) -> String {
|
||||
for idx in (0..13).rev() {
|
||||
// Divide the long integer in b71 by 42
|
||||
let mut rem: u16 = 0;
|
||||
for i in 0..9 {
|
||||
rem = (rem << 8) | b71[i] as u16;
|
||||
b71[i] = (rem / 42) as u8;
|
||||
for b in &mut b71 {
|
||||
rem = (rem << 8) | *b as u16;
|
||||
*b = (rem / 42) as u8;
|
||||
rem %= 42;
|
||||
}
|
||||
c14[idx] = charn(rem as i32, CharTable::Full) as u8;
|
||||
@@ -1183,9 +1180,9 @@ pub fn ftx_message_decode_telemetry_hex(msg: &FtxMessage) -> String {
|
||||
pub fn ftx_message_decode_telemetry(msg: &FtxMessage) -> [u8; 9] {
|
||||
let mut telemetry = [0u8; 9];
|
||||
let mut carry: u8 = 0;
|
||||
for i in 0..9 {
|
||||
telemetry[i] = (carry << 7) | (msg.payload[i] >> 1);
|
||||
carry = msg.payload[i] & 0x01;
|
||||
for (t, &p) in telemetry.iter_mut().zip(msg.payload.iter()) {
|
||||
*t = (carry << 7) | (p >> 1);
|
||||
carry = p & 0x01;
|
||||
}
|
||||
telemetry
|
||||
}
|
||||
|
||||
@@ -7,17 +7,11 @@
|
||||
pub mod callsign_hash;
|
||||
pub mod constants;
|
||||
pub mod crc;
|
||||
#[allow(dead_code, clippy::needless_range_loop)]
|
||||
pub mod decode;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod encode;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod ldpc;
|
||||
#[allow(clippy::explicit_counter_loop, clippy::needless_range_loop)]
|
||||
pub mod message;
|
||||
#[allow(dead_code)]
|
||||
pub mod monitor;
|
||||
#[allow(dead_code, clippy::needless_range_loop, clippy::too_many_arguments)]
|
||||
pub mod osd;
|
||||
pub mod protocol;
|
||||
pub mod text;
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
use num_complex::Complex32;
|
||||
use realfft::RealFftPlanner;
|
||||
use rustfft::FftPlanner;
|
||||
|
||||
use super::protocol::FtxProtocol;
|
||||
|
||||
@@ -94,10 +93,6 @@ pub struct Monitor {
|
||||
fft_output: Vec<Complex32>,
|
||||
fft_input: Vec<f32>,
|
||||
real_fft: std::sync::Arc<dyn realfft::RealToComplex<f32>>,
|
||||
// iFFT for resynthesis
|
||||
nifft: usize,
|
||||
ifft: std::sync::Arc<dyn rustfft::Fft<f32>>,
|
||||
ifft_scratch: Vec<Complex32>,
|
||||
}
|
||||
|
||||
fn hann_i(i: usize, n: usize) -> f32 {
|
||||
@@ -137,11 +132,6 @@ impl Monitor {
|
||||
let fft_output = real_fft.make_output_vec();
|
||||
let fft_input = real_fft.make_input_vec();
|
||||
|
||||
let nifft = 64;
|
||||
let mut fft_planner = FftPlanner::<f32>::new();
|
||||
let ifft = fft_planner.plan_fft_inverse(nifft);
|
||||
let ifft_scratch = vec![Complex32::new(0.0, 0.0); ifft.get_inplace_scratch_len()];
|
||||
|
||||
Self {
|
||||
symbol_period,
|
||||
min_bin,
|
||||
@@ -158,9 +148,6 @@ impl Monitor {
|
||||
fft_output,
|
||||
fft_input,
|
||||
real_fft,
|
||||
nifft,
|
||||
ifft,
|
||||
ifft_scratch,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -76,14 +76,6 @@ fn encode174_91_nocrc_bits(message91: &[u8], codeword: &mut [u8; FTX_LDPC_N]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// XOR two byte slices.
|
||||
fn xor_rows(dst: &mut [u8], src: &[u8], len: usize) {
|
||||
dst[..len]
|
||||
.iter_mut()
|
||||
.zip(&src[..len])
|
||||
.for_each(|(d, s)| *d ^= s);
|
||||
}
|
||||
|
||||
/// Matrix-vector multiply for re-encoding in OSD.
|
||||
fn mrbencode91(me: &[u8], codeword: &mut [u8], g2: &[u8], n: usize, k: usize) {
|
||||
codeword[..n].fill(0);
|
||||
@@ -114,24 +106,20 @@ fn nextpat91(mi: &mut [u8], k: usize, iorder: usize, iflag: &mut i32) {
|
||||
|
||||
// Build new pattern in-place: zero out after ind, set the swap, pack remaining 1s at end
|
||||
let ind_u = ind as usize;
|
||||
for i in (ind_u + 1)..k {
|
||||
mi[i] = 0;
|
||||
}
|
||||
mi[(ind_u + 1)..k].fill(0);
|
||||
mi[ind_u] = 1;
|
||||
|
||||
let mut nz = iorder as i32;
|
||||
for i in 0..k {
|
||||
nz -= mi[i] as i32;
|
||||
for &v in mi.iter().take(k) {
|
||||
nz -= v as i32;
|
||||
}
|
||||
if nz > 0 {
|
||||
for i in (k - nz as usize)..k {
|
||||
mi[i] = 1;
|
||||
}
|
||||
mi[(k - nz as usize)..k].fill(1);
|
||||
}
|
||||
|
||||
*iflag = -1;
|
||||
for i in 0..k {
|
||||
if mi[i] == 1 {
|
||||
for (i, &v) in mi.iter().enumerate().take(k) {
|
||||
if v == 1 {
|
||||
*iflag = i as i32;
|
||||
break;
|
||||
}
|
||||
@@ -145,7 +133,6 @@ struct OsdBox {
|
||||
pairs: Vec<[i32; 2]>,
|
||||
capacity: usize,
|
||||
count: usize,
|
||||
size: usize,
|
||||
last_pattern: i32,
|
||||
next_index: i32,
|
||||
}
|
||||
@@ -160,7 +147,6 @@ impl OsdBox {
|
||||
pairs: vec![[-1, -1]; capacity],
|
||||
capacity,
|
||||
count: 0,
|
||||
size,
|
||||
last_pattern: -1,
|
||||
next_index: -1,
|
||||
})
|
||||
@@ -214,8 +200,8 @@ impl OsdBox {
|
||||
/// Compute hash of a bit pattern for OSD-2 lookup.
|
||||
fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
|
||||
let mut ipat = 0usize;
|
||||
for i in 0..ntau {
|
||||
if e2[i] != 0 {
|
||||
for (i, &v) in e2.iter().enumerate().take(ntau) {
|
||||
if v != 0 {
|
||||
ipat |= 1 << (ntau - i - 1);
|
||||
}
|
||||
}
|
||||
@@ -232,6 +218,7 @@ fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
|
||||
/// `cw`: output 174-bit codeword.
|
||||
/// `nhardmin`: output minimum hard errors.
|
||||
/// `dmin`: output minimum distance.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn osd174_91(
|
||||
llr: &mut [f32; FTX_LDPC_N],
|
||||
k: usize,
|
||||
@@ -372,9 +359,7 @@ pub fn osd174_91(
|
||||
// OSD-1: exhaustive search over bit patterns of increasing order
|
||||
for iorder in 1..=nord {
|
||||
misub.iter_mut().for_each(|v| *v = 0);
|
||||
for i in (k - iorder)..k {
|
||||
misub[i] = 1;
|
||||
}
|
||||
misub[(k - iorder)..k].fill(1);
|
||||
let mut iflag = (k - iorder) as i32;
|
||||
|
||||
while iflag >= 0 {
|
||||
@@ -408,8 +393,8 @@ pub fn osd174_91(
|
||||
e2[i] = e2sub[i];
|
||||
}
|
||||
let mut nd1kpt = 1;
|
||||
for i in 0..nt.min(n - k) {
|
||||
nd1kpt += e2sub[i] as i32;
|
||||
for &v in e2sub.iter().take(nt.min(n - k)) {
|
||||
nd1kpt += v as i32;
|
||||
}
|
||||
d1 = 0.0;
|
||||
for i in 0..k {
|
||||
@@ -438,8 +423,8 @@ pub fn osd174_91(
|
||||
e2[i] = e2sub[i] ^ g2[(k + i) * k + n1 as usize];
|
||||
}
|
||||
let mut nd1kpt = 2;
|
||||
for i in 0..nt.min(n - k) {
|
||||
nd1kpt += e2[i] as i32;
|
||||
for &v in e2.iter().take(nt.min(n - k)) {
|
||||
nd1kpt += v as i32;
|
||||
}
|
||||
if nd1kpt <= ntheta {
|
||||
mrbencode91(&me, &mut ce, &g2, n, k);
|
||||
@@ -486,9 +471,7 @@ pub fn osd174_91(
|
||||
|
||||
// Search using base patterns
|
||||
misub.iter_mut().for_each(|v| *v = 0);
|
||||
for i in (k - nord)..k {
|
||||
misub[i] = 1;
|
||||
}
|
||||
misub[(k - nord)..k].fill(1);
|
||||
let mut iflag = (k - nord) as i32;
|
||||
|
||||
while iflag >= 0 {
|
||||
@@ -594,9 +577,7 @@ fn generator_matrix() -> &'static [[u8; FTX_LDPC_N]; FTX_LDPC_K] {
|
||||
let mut msg = [0u8; FTX_LDPC_K];
|
||||
msg[i] = 1;
|
||||
if i < 77 {
|
||||
for j in 77..FTX_LDPC_K {
|
||||
msg[j] = 0;
|
||||
}
|
||||
msg[77..FTX_LDPC_K].fill(0);
|
||||
}
|
||||
encode174_91_nocrc_bits(&msg, &mut gen[i]);
|
||||
}
|
||||
@@ -620,6 +601,7 @@ fn generator_matrix() -> &'static [[u8; FTX_LDPC_N]; FTX_LDPC_K] {
|
||||
/// `ntype`: output decode type (0=fail, 1=BP, 2=OSD).
|
||||
/// `nharderror`: output number of hard errors.
|
||||
/// `dmin`: output minimum distance.
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn ft2_decode174_91_osd(
|
||||
llr: &mut [f32; FTX_LDPC_N],
|
||||
keff: usize,
|
||||
@@ -732,8 +714,8 @@ pub fn ft2_decode174_91_osd(
|
||||
for i in 0..num_rows.min(7) {
|
||||
tanhtoc[i] = (-toc[m][i] / 2.0).tanh();
|
||||
}
|
||||
for j in 0..num_rows {
|
||||
let n = FTX_LDPC_NM[m][j] as usize - 1;
|
||||
for &nm_val in FTX_LDPC_NM[m].iter().take(num_rows) {
|
||||
let n = nm_val as usize - 1;
|
||||
if n >= FTX_LDPC_N {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -78,9 +78,9 @@ impl BitMetricsWorkspace {
|
||||
|
||||
// Sync quality check: verify Costas patterns are detectable
|
||||
let mut sync_ok = 0;
|
||||
for group in 0..4 {
|
||||
for (group, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate() {
|
||||
let base = group * 33;
|
||||
for i in 0..4 {
|
||||
for (i, &costas_tone) in costas_group.iter().enumerate() {
|
||||
if base + i >= FT2_FRAME_SYMBOLS {
|
||||
continue;
|
||||
}
|
||||
@@ -90,7 +90,7 @@ impl BitMetricsWorkspace {
|
||||
best = tone;
|
||||
}
|
||||
}
|
||||
if best == FT4_COSTAS_PATTERN[group][i] as usize {
|
||||
if best == costas_tone as usize {
|
||||
sync_ok += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,10 @@ pub(crate) fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
let mut score_f: f32 = 0.0;
|
||||
let mut groups = 0;
|
||||
|
||||
for m in 0..FT2_NUM_SYNC {
|
||||
for (m, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate().take(FT2_NUM_SYNC) {
|
||||
let mut sum = Complex32::new(0.0, 0.0);
|
||||
let mut complete = true;
|
||||
for k in 0..FT2_LENGTH_SYNC {
|
||||
for (k, &costas_tone) in costas_group.iter().enumerate().take(FT2_LENGTH_SYNC) {
|
||||
let block = 1 + FT2_SYNC_OFFSET * m + k;
|
||||
let block_abs = cand.time_offset as i32 + block as i32;
|
||||
if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
|
||||
@@ -28,7 +28,7 @@ pub(crate) fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
break;
|
||||
}
|
||||
let sym_offset = base + block * wf.block_stride;
|
||||
let tone = FT4_COSTAS_PATTERN[m][k] as usize;
|
||||
let tone = costas_tone as usize;
|
||||
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
||||
sum += wf_elem_to_complex(elem);
|
||||
}
|
||||
@@ -63,9 +63,9 @@ pub(crate) fn ft2_extract_likelihood(
|
||||
continue;
|
||||
}
|
||||
let sym_offset = base + sym_idx * wf.block_stride;
|
||||
for tone in 0..4 {
|
||||
for (tone, symbol_row) in symbols.iter_mut().enumerate().take(4) {
|
||||
let elem = *wf_mag_safe(wf, sym_offset + tone);
|
||||
symbols[tone][frame_sym] = wf_elem_to_complex(elem);
|
||||
symbol_row[frame_sym] = wf_elem_to_complex(elem);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -190,8 +190,8 @@ impl DownsampleContext {
|
||||
}
|
||||
|
||||
// Apply spectral window
|
||||
for i in 0..self.nfft2 {
|
||||
band[i] *= self.window[i];
|
||||
for (b, &w) in band.iter_mut().zip(self.window.iter()) {
|
||||
*b *= w;
|
||||
}
|
||||
|
||||
// Inverse FFT (in-place)
|
||||
@@ -221,19 +221,27 @@ fn build_spectral_window(nfft2: usize, df: f32) -> Vec<f32> {
|
||||
}
|
||||
|
||||
// Raised-cosine leading edge
|
||||
for i in 0..iwt.min(nfft2) {
|
||||
window[i] = 0.5 * (1.0 + (std::f32::consts::PI * (iwt - 1 - i) as f32 / iwt as f32).cos());
|
||||
for (i, w) in window.iter_mut().enumerate().take(iwt.min(nfft2)) {
|
||||
*w = 0.5 * (1.0 + (std::f32::consts::PI * (iwt - 1 - i) as f32 / iwt as f32).cos());
|
||||
}
|
||||
|
||||
// Flat passband
|
||||
for i in iwt..(iwt + iwf).min(nfft2) {
|
||||
window[i] = 1.0;
|
||||
for w in window
|
||||
.iter_mut()
|
||||
.skip(iwt)
|
||||
.take((iwt + iwf).min(nfft2) - iwt)
|
||||
{
|
||||
*w = 1.0;
|
||||
}
|
||||
|
||||
// Raised-cosine trailing edge
|
||||
for i in (iwt + iwf)..(2 * iwt + iwf).min(nfft2) {
|
||||
window[i] =
|
||||
0.5 * (1.0 + (std::f32::consts::PI * (i - (iwt + iwf)) as f32 / iwt as f32).cos());
|
||||
for (i, w) in window
|
||||
.iter_mut()
|
||||
.enumerate()
|
||||
.take((2 * iwt + iwf).min(nfft2))
|
||||
.skip(iwt + iwf)
|
||||
{
|
||||
*w = 0.5 * (1.0 + (std::f32::consts::PI * (i - (iwt + iwf)) as f32 / iwt as f32).cos());
|
||||
}
|
||||
|
||||
// Circular shift by iws bins
|
||||
|
||||
@@ -8,12 +8,9 @@
|
||||
//! peaks in the averaged spectrum, downsample each candidate, compute 2D sync
|
||||
//! scores, extract bit metrics, and run multi-pass LDPC + OSD decode.
|
||||
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod bitmetrics;
|
||||
pub(crate) mod decode;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod downsample;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod sync;
|
||||
|
||||
pub(crate) use self::decode::{ft2_extract_likelihood, ft2_sync_score};
|
||||
@@ -50,6 +47,13 @@ pub fn ft2_frequency_offset_hz() -> f32 {
|
||||
-1.5 / FT2_SYMBOL_PERIOD_F
|
||||
}
|
||||
|
||||
/// Generate FT2 tone sequence from payload data.
|
||||
///
|
||||
/// FT2 uses the FT4 framing with a doubled symbol rate.
|
||||
pub fn ft2_encode(payload: &[u8], tones: &mut [u8]) {
|
||||
crate::ft4::ft4_encode(payload, tones);
|
||||
}
|
||||
|
||||
/// Raw frequency peak candidate from the averaged power spectrum.
|
||||
#[derive(Clone, Copy, Default)]
|
||||
pub struct RawCandidate {
|
||||
@@ -315,8 +319,8 @@ impl Ft2Pipeline {
|
||||
}
|
||||
|
||||
let inv_n_frames = 1.0 / n_frames as f32;
|
||||
for bin in 1..FT2_NH1 {
|
||||
avg[bin] *= inv_n_frames;
|
||||
for v in avg.iter_mut().take(FT2_NH1).skip(1) {
|
||||
*v *= inv_n_frames;
|
||||
}
|
||||
|
||||
// Smooth with 15-point moving average
|
||||
@@ -620,17 +624,24 @@ impl Ft2Pipeline {
|
||||
}
|
||||
|
||||
// Scale and derive combined passes
|
||||
for i in 0..FTX_LDPC_N {
|
||||
llr_passes[0][i] *= 2.83;
|
||||
llr_passes[1][i] *= 2.83;
|
||||
llr_passes[2][i] *= 2.83;
|
||||
|
||||
let a = llr_passes[0][i];
|
||||
let b = llr_passes[1][i];
|
||||
let c = llr_passes[2][i];
|
||||
|
||||
let [ref mut pass0, ref mut pass1, ref mut pass2, ref mut pass3, ref mut pass4] =
|
||||
llr_passes;
|
||||
for v in pass0.iter_mut() {
|
||||
*v *= 2.83;
|
||||
}
|
||||
for v in pass1.iter_mut() {
|
||||
*v *= 2.83;
|
||||
}
|
||||
for v in pass2.iter_mut() {
|
||||
*v *= 2.83;
|
||||
}
|
||||
for ((&a, &b), (&c, (p3, p4))) in pass0
|
||||
.iter()
|
||||
.zip(pass1.iter())
|
||||
.zip(pass2.iter().zip(pass3.iter_mut().zip(pass4.iter_mut())))
|
||||
{
|
||||
// Pass 3: max-abs metric
|
||||
llr_passes[3][i] = if a.abs() >= b.abs() && a.abs() >= c.abs() {
|
||||
*p3 = if a.abs() >= b.abs() && a.abs() >= c.abs() {
|
||||
a
|
||||
} else if b.abs() >= c.abs() {
|
||||
b
|
||||
@@ -639,7 +650,7 @@ impl Ft2Pipeline {
|
||||
};
|
||||
|
||||
// Pass 4: average
|
||||
llr_passes[4][i] = (a + b + c) / 3.0;
|
||||
*p4 = (a + b + c) / 3.0;
|
||||
}
|
||||
|
||||
// Multi-pass LDPC decode using full BP+OSD decoder
|
||||
@@ -647,11 +658,11 @@ impl Ft2Pipeline {
|
||||
let mut message = FtxMessage::default();
|
||||
let mut apmask = [0u8; FTX_LDPC_N];
|
||||
|
||||
for pass in 0..5 {
|
||||
for llr_pass in &llr_passes {
|
||||
if ok {
|
||||
break;
|
||||
}
|
||||
let mut log174 = llr_passes[pass];
|
||||
let mut log174 = *llr_pass;
|
||||
|
||||
let mut message91 = [0u8; FTX_LDPC_K];
|
||||
let mut cw = [0u8; FTX_LDPC_N];
|
||||
@@ -822,4 +833,14 @@ mod tests {
|
||||
assert_eq!(b, 0);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ft2_encode_matches_ft4() {
|
||||
let payload = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x20];
|
||||
let mut tones_ft4 = [0u8; FT4_NN];
|
||||
let mut tones_ft2 = [0u8; FT4_NN];
|
||||
crate::ft4::ft4_encode(&payload, &mut tones_ft4);
|
||||
ft2_encode(&payload, &mut tones_ft2);
|
||||
assert_eq!(tones_ft4, tones_ft2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -50,8 +50,8 @@ pub fn prepare_sync_waveforms() -> SyncWaveforms {
|
||||
for group in 0..4 {
|
||||
let mut idx = 0usize;
|
||||
let mut phase = 0.0f32;
|
||||
for tone_idx in 0..4 {
|
||||
let tone = FT4_COSTAS_PATTERN[group][tone_idx] as f32;
|
||||
for &costas_tone in FT4_COSTAS_PATTERN[group].iter() {
|
||||
let tone = costas_tone as f32;
|
||||
let dphase = 4.0 * std::f32::consts::PI * tone / nss;
|
||||
let half_nss = (nss / 2.0) as usize;
|
||||
for _step in 0..half_nss {
|
||||
@@ -68,9 +68,9 @@ pub fn prepare_sync_waveforms() -> SyncWaveforms {
|
||||
// Build frequency tweak phasors
|
||||
for idf in FT2_SYNC_TWEAK_MIN..=FT2_SYNC_TWEAK_MAX {
|
||||
let tw_idx = (idf - FT2_SYNC_TWEAK_MIN) as usize;
|
||||
for n in 0..64 {
|
||||
for (n, tw) in tweak_wave[tw_idx].iter_mut().enumerate() {
|
||||
let phase = 4.0 * std::f32::consts::PI * idf as f32 * n as f32 / fs_down;
|
||||
tweak_wave[tw_idx][n] = Complex32::new(phase.cos(), phase.sin());
|
||||
*tw = Complex32::new(phase.cos(), phase.sin());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,11 +89,10 @@ fn sync_reference_bank() -> &'static SyncReferenceBank {
|
||||
let waveforms = prepare_sync_waveforms();
|
||||
let mut refs = [[[Complex32::new(0.0, 0.0); SYNC_SAMPLES]; SYNC_GROUP_COUNT]; NUM_TWEAKS];
|
||||
|
||||
for tw_idx in 0..NUM_TWEAKS {
|
||||
for group in 0..SYNC_GROUP_COUNT {
|
||||
for i in 0..SYNC_SAMPLES {
|
||||
refs[tw_idx][group][i] =
|
||||
(waveforms.sync_wave[group][i] * waveforms.tweak_wave[tw_idx][i]).conj();
|
||||
for (tw_idx, refs_tw) in refs.iter_mut().enumerate() {
|
||||
for (group, refs_group) in refs_tw.iter_mut().enumerate() {
|
||||
for (i, r) in refs_group.iter_mut().enumerate() {
|
||||
*r = (waveforms.sync_wave[group][i] * waveforms.tweak_wave[tw_idx][i]).conj();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -132,14 +131,13 @@ fn correlate_group_clipped(
|
||||
let mut usable = 0usize;
|
||||
let n_samples = samples.len() as i32;
|
||||
|
||||
for i in 0..SYNC_SAMPLES {
|
||||
for (i, &reference) in refs.iter().enumerate() {
|
||||
let sample_idx = pos + i as i32 * SAMPLE_STRIDE as i32;
|
||||
if sample_idx < 0 || sample_idx >= n_samples {
|
||||
continue;
|
||||
}
|
||||
|
||||
let sample = samples[sample_idx as usize];
|
||||
let reference = refs[i];
|
||||
sum_re += sample.re * reference.re - sample.im * reference.im;
|
||||
sum_im += sample.re * reference.im + sample.im * reference.re;
|
||||
usable += 1;
|
||||
|
||||
@@ -17,8 +17,8 @@ pub(crate) fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
let mut score: i32 = 0;
|
||||
let mut num_average: i32 = 0;
|
||||
|
||||
for m in 0..FT4_NUM_SYNC {
|
||||
for k in 0..FT4_LENGTH_SYNC {
|
||||
for (m, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate().take(FT4_NUM_SYNC) {
|
||||
for (k, &sm_val) in costas_group.iter().enumerate().take(FT4_LENGTH_SYNC) {
|
||||
let block = 1 + FT4_SYNC_OFFSET * m + k;
|
||||
let block_abs = cand.time_offset as i32 + block as i32;
|
||||
if block_abs < 0 {
|
||||
@@ -29,7 +29,7 @@ pub(crate) fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
}
|
||||
|
||||
let p_offset = base + block * wf.block_stride;
|
||||
let sm = FT4_COSTAS_PATTERN[m][k] as usize;
|
||||
let sm = sm_val as usize;
|
||||
|
||||
if sm > 0 {
|
||||
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
||||
@@ -174,13 +174,6 @@ pub fn ft4_encode(payload: &[u8], tones: &mut [u8]) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate FT2 tone sequence from payload data.
|
||||
///
|
||||
/// FT2 uses the FT4 framing with a doubled symbol rate.
|
||||
pub fn ft2_encode(payload: &[u8], tones: &mut [u8]) {
|
||||
ft4_encode(payload, tones);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -244,14 +237,4 @@ mod tests {
|
||||
ft4_encode(&payload, &mut tones2);
|
||||
assert_eq!(tones1, tones2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ft2_encode_matches_ft4() {
|
||||
let payload = [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0, 0x11, 0x20];
|
||||
let mut tones_ft4 = [0u8; FT4_NN];
|
||||
let mut tones_ft2 = [0u8; FT4_NN];
|
||||
ft4_encode(&payload, &mut tones_ft4);
|
||||
ft2_encode(&payload, &mut tones_ft2);
|
||||
assert_eq!(tones_ft4, tones_ft2);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ pub(crate) fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
let mut num_average: i32 = 0;
|
||||
|
||||
for m in 0..FT8_NUM_SYNC {
|
||||
for k in 0..FT8_LENGTH_SYNC {
|
||||
for (k, &sm_val) in FT8_COSTAS_PATTERN.iter().enumerate().take(FT8_LENGTH_SYNC) {
|
||||
let block = FT8_SYNC_OFFSET * m + k;
|
||||
let block_abs = cand.time_offset as i32 + block as i32;
|
||||
if block_abs < 0 {
|
||||
@@ -29,7 +29,7 @@ pub(crate) fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
|
||||
}
|
||||
|
||||
let p_offset = base + block * wf.block_stride;
|
||||
let sm = FT8_COSTAS_PATTERN[k] as usize;
|
||||
let sm = sm_val as usize;
|
||||
|
||||
if sm > 0 {
|
||||
let a = wf_mag_safe(wf, p_offset + sm).mag_int();
|
||||
|
||||
@@ -4,11 +4,8 @@
|
||||
|
||||
pub mod common;
|
||||
mod decoder;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod ft2;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod ft4;
|
||||
#[allow(clippy::needless_range_loop)]
|
||||
pub mod ft8;
|
||||
|
||||
pub use decoder::{Ft8DecodeResult, Ft8Decoder};
|
||||
|
||||
Reference in New Issue
Block a user