[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:
2026-03-20 00:05:11 +01:00
parent bb18d90cbe
commit ab8425c85c
14 changed files with 114 additions and 156 deletions
+6 -15
View File
@@ -49,16 +49,7 @@ pub(crate) fn get_cand_offset(wf: &Waterfall, cand: &Candidate) -> usize {
offset.max(0) as usize offset.max(0) as usize
} }
pub(crate) fn wf_mag_at(wf: &Waterfall, base: usize, idx: isize) -> &WfElem { // Default element for out-of-bounds waterfall access
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
pub(crate) static DEFAULT_WF_ELEM: WfElem = WfElem { pub(crate) static DEFAULT_WF_ELEM: WfElem = WfElem {
mag: -120.0, mag: -120.0,
phase: 0.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 mask: u8 = 0x80;
let mut byte_idx = 0; let mut byte_idx = 0;
for i in 0..num_bits { for &bit in bit_array.iter().take(num_bits) {
if bit_array[i] != 0 { if bit != 0 {
packed[byte_idx] |= mask; packed[byte_idx] |= mask;
} }
mask >>= 1; 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 sum_snr = 0.0f32;
let mut n_valid = 0; 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; let block_abs = cand.time_offset as i32 + sym as i32;
if block_abs < 0 || block_abs >= wf.num_blocks as i32 { if block_abs < 0 || block_abs >= wf.num_blocks as i32 {
continue; continue;
} }
let p_offset = base + sym * wf.block_stride; 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 noise_min = 0.0f32;
let mut found_noise = false; let mut found_noise = false;
for t in 0..num_tones { for t in 0..num_tones {
if t == tones[sym] as usize { if t == tone as usize {
continue; continue;
} }
let db = wf_mag_safe(wf, p_offset + t).mag; let db = wf_mag_safe(wf, p_offset + t).mag;
+3 -3
View File
@@ -33,10 +33,10 @@ pub(crate) fn encode174(message: &[u8], codeword: &mut [u8]) {
let mut col_idx: usize = FTX_LDPC_K_BYTES - 1; let mut col_idx: usize = FTX_LDPC_K_BYTES - 1;
// Compute the LDPC checksum bits and store them in codeword // 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; let mut nsum: u8 = 0;
for j in 0..FTX_LDPC_K_BYTES { 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) { 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. /// Each element of the returned array is 0 or 1.
/// Uses the same (174, 91) LDPC generator as `encode174`. /// 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] { pub(crate) fn encode174_to_bits(a91: &[u8; FTX_LDPC_K_BYTES]) -> [u8; super::protocol::FTX_LDPC_N] {
use super::protocol::FTX_LDPC_N; use super::protocol::FTX_LDPC_N;
let mut codeword_packed = [0u8; FTX_LDPC_N_BYTES]; let mut codeword_packed = [0u8; FTX_LDPC_N_BYTES];
+8 -11
View File
@@ -247,8 +247,7 @@ fn parse_cq_modifier(s: &str) -> Option<i32> {
let mut nlet = 0; let mut nlet = 0;
let mut m: i32 = 0; let mut m: i32 = 0;
for i in 3..8.min(bytes.len()) { for &c in &bytes[3..8.min(bytes.len())] {
let c = bytes[i];
if c == b' ' || c == 0 { if c == b' ' || c == 0 {
break; break;
} else if c.is_ascii_digit() { } 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 result: u64 = 0;
let mut c11 = String::with_capacity(12); 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 { if ch == '<' || length >= 11 {
break; break;
} }
c11.push(ch); c11.push(ch);
let j = nchar(ch, CharTable::AlphanumSpaceSlash)?; let j = nchar(ch, CharTable::AlphanumSpaceSlash)?;
result = result * 38 + j as u64; result = result * 38 + j as u64;
length += 1;
} }
save_callsign(hash_table, &c11)?; save_callsign(hash_table, &c11)?;
@@ -1156,9 +1153,9 @@ pub fn ftx_message_decode_free(msg: &FtxMessage) -> String {
for idx in (0..13).rev() { for idx in (0..13).rev() {
// Divide the long integer in b71 by 42 // Divide the long integer in b71 by 42
let mut rem: u16 = 0; let mut rem: u16 = 0;
for i in 0..9 { for b in &mut b71 {
rem = (rem << 8) | b71[i] as u16; rem = (rem << 8) | *b as u16;
b71[i] = (rem / 42) as u8; *b = (rem / 42) as u8;
rem %= 42; rem %= 42;
} }
c14[idx] = charn(rem as i32, CharTable::Full) as u8; 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] { pub fn ftx_message_decode_telemetry(msg: &FtxMessage) -> [u8; 9] {
let mut telemetry = [0u8; 9]; let mut telemetry = [0u8; 9];
let mut carry: u8 = 0; let mut carry: u8 = 0;
for i in 0..9 { for (t, &p) in telemetry.iter_mut().zip(msg.payload.iter()) {
telemetry[i] = (carry << 7) | (msg.payload[i] >> 1); *t = (carry << 7) | (p >> 1);
carry = msg.payload[i] & 0x01; carry = p & 0x01;
} }
telemetry telemetry
} }
-6
View File
@@ -7,17 +7,11 @@
pub mod callsign_hash; pub mod callsign_hash;
pub mod constants; pub mod constants;
pub mod crc; pub mod crc;
#[allow(dead_code, clippy::needless_range_loop)]
pub mod decode; pub mod decode;
#[allow(clippy::needless_range_loop)]
pub mod encode; pub mod encode;
#[allow(clippy::needless_range_loop)]
pub mod ldpc; pub mod ldpc;
#[allow(clippy::explicit_counter_loop, clippy::needless_range_loop)]
pub mod message; pub mod message;
#[allow(dead_code)]
pub mod monitor; pub mod monitor;
#[allow(dead_code, clippy::needless_range_loop, clippy::too_many_arguments)]
pub mod osd; pub mod osd;
pub mod protocol; pub mod protocol;
pub mod text; pub mod text;
@@ -8,7 +8,6 @@
use num_complex::Complex32; use num_complex::Complex32;
use realfft::RealFftPlanner; use realfft::RealFftPlanner;
use rustfft::FftPlanner;
use super::protocol::FtxProtocol; use super::protocol::FtxProtocol;
@@ -94,10 +93,6 @@ pub struct Monitor {
fft_output: Vec<Complex32>, fft_output: Vec<Complex32>,
fft_input: Vec<f32>, fft_input: Vec<f32>,
real_fft: std::sync::Arc<dyn realfft::RealToComplex<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 { fn hann_i(i: usize, n: usize) -> f32 {
@@ -137,11 +132,6 @@ impl Monitor {
let fft_output = real_fft.make_output_vec(); let fft_output = real_fft.make_output_vec();
let fft_input = real_fft.make_input_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 { Self {
symbol_period, symbol_period,
min_bin, min_bin,
@@ -158,9 +148,6 @@ impl Monitor {
fft_output, fft_output,
fft_input, fft_input,
real_fft, real_fft,
nifft,
ifft,
ifft_scratch,
} }
} }
+19 -37
View File
@@ -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. /// Matrix-vector multiply for re-encoding in OSD.
fn mrbencode91(me: &[u8], codeword: &mut [u8], g2: &[u8], n: usize, k: usize) { fn mrbencode91(me: &[u8], codeword: &mut [u8], g2: &[u8], n: usize, k: usize) {
codeword[..n].fill(0); 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 // Build new pattern in-place: zero out after ind, set the swap, pack remaining 1s at end
let ind_u = ind as usize; let ind_u = ind as usize;
for i in (ind_u + 1)..k { mi[(ind_u + 1)..k].fill(0);
mi[i] = 0;
}
mi[ind_u] = 1; mi[ind_u] = 1;
let mut nz = iorder as i32; let mut nz = iorder as i32;
for i in 0..k { for &v in mi.iter().take(k) {
nz -= mi[i] as i32; nz -= v as i32;
} }
if nz > 0 { if nz > 0 {
for i in (k - nz as usize)..k { mi[(k - nz as usize)..k].fill(1);
mi[i] = 1;
}
} }
*iflag = -1; *iflag = -1;
for i in 0..k { for (i, &v) in mi.iter().enumerate().take(k) {
if mi[i] == 1 { if v == 1 {
*iflag = i as i32; *iflag = i as i32;
break; break;
} }
@@ -145,7 +133,6 @@ struct OsdBox {
pairs: Vec<[i32; 2]>, pairs: Vec<[i32; 2]>,
capacity: usize, capacity: usize,
count: usize, count: usize,
size: usize,
last_pattern: i32, last_pattern: i32,
next_index: i32, next_index: i32,
} }
@@ -160,7 +147,6 @@ impl OsdBox {
pairs: vec![[-1, -1]; capacity], pairs: vec![[-1, -1]; capacity],
capacity, capacity,
count: 0, count: 0,
size,
last_pattern: -1, last_pattern: -1,
next_index: -1, next_index: -1,
}) })
@@ -214,8 +200,8 @@ impl OsdBox {
/// Compute hash of a bit pattern for OSD-2 lookup. /// Compute hash of a bit pattern for OSD-2 lookup.
fn pattern_hash(e2: &[u8], ntau: usize) -> usize { fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
let mut ipat = 0usize; let mut ipat = 0usize;
for i in 0..ntau { for (i, &v) in e2.iter().enumerate().take(ntau) {
if e2[i] != 0 { if v != 0 {
ipat |= 1 << (ntau - i - 1); ipat |= 1 << (ntau - i - 1);
} }
} }
@@ -232,6 +218,7 @@ fn pattern_hash(e2: &[u8], ntau: usize) -> usize {
/// `cw`: output 174-bit codeword. /// `cw`: output 174-bit codeword.
/// `nhardmin`: output minimum hard errors. /// `nhardmin`: output minimum hard errors.
/// `dmin`: output minimum distance. /// `dmin`: output minimum distance.
#[allow(clippy::too_many_arguments)]
pub fn osd174_91( pub fn osd174_91(
llr: &mut [f32; FTX_LDPC_N], llr: &mut [f32; FTX_LDPC_N],
k: usize, k: usize,
@@ -372,9 +359,7 @@ pub fn osd174_91(
// OSD-1: exhaustive search over bit patterns of increasing order // OSD-1: exhaustive search over bit patterns of increasing order
for iorder in 1..=nord { for iorder in 1..=nord {
misub.iter_mut().for_each(|v| *v = 0); misub.iter_mut().for_each(|v| *v = 0);
for i in (k - iorder)..k { misub[(k - iorder)..k].fill(1);
misub[i] = 1;
}
let mut iflag = (k - iorder) as i32; let mut iflag = (k - iorder) as i32;
while iflag >= 0 { while iflag >= 0 {
@@ -408,8 +393,8 @@ pub fn osd174_91(
e2[i] = e2sub[i]; e2[i] = e2sub[i];
} }
let mut nd1kpt = 1; let mut nd1kpt = 1;
for i in 0..nt.min(n - k) { for &v in e2sub.iter().take(nt.min(n - k)) {
nd1kpt += e2sub[i] as i32; nd1kpt += v as i32;
} }
d1 = 0.0; d1 = 0.0;
for i in 0..k { for i in 0..k {
@@ -438,8 +423,8 @@ pub fn osd174_91(
e2[i] = e2sub[i] ^ g2[(k + i) * k + n1 as usize]; e2[i] = e2sub[i] ^ g2[(k + i) * k + n1 as usize];
} }
let mut nd1kpt = 2; let mut nd1kpt = 2;
for i in 0..nt.min(n - k) { for &v in e2.iter().take(nt.min(n - k)) {
nd1kpt += e2[i] as i32; nd1kpt += v as i32;
} }
if nd1kpt <= ntheta { if nd1kpt <= ntheta {
mrbencode91(&me, &mut ce, &g2, n, k); mrbencode91(&me, &mut ce, &g2, n, k);
@@ -486,9 +471,7 @@ pub fn osd174_91(
// Search using base patterns // Search using base patterns
misub.iter_mut().for_each(|v| *v = 0); misub.iter_mut().for_each(|v| *v = 0);
for i in (k - nord)..k { misub[(k - nord)..k].fill(1);
misub[i] = 1;
}
let mut iflag = (k - nord) as i32; let mut iflag = (k - nord) as i32;
while iflag >= 0 { 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]; let mut msg = [0u8; FTX_LDPC_K];
msg[i] = 1; msg[i] = 1;
if i < 77 { if i < 77 {
for j in 77..FTX_LDPC_K { msg[77..FTX_LDPC_K].fill(0);
msg[j] = 0;
}
} }
encode174_91_nocrc_bits(&msg, &mut gen[i]); 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). /// `ntype`: output decode type (0=fail, 1=BP, 2=OSD).
/// `nharderror`: output number of hard errors. /// `nharderror`: output number of hard errors.
/// `dmin`: output minimum distance. /// `dmin`: output minimum distance.
#[allow(clippy::too_many_arguments)]
pub fn ft2_decode174_91_osd( pub fn ft2_decode174_91_osd(
llr: &mut [f32; FTX_LDPC_N], llr: &mut [f32; FTX_LDPC_N],
keff: usize, keff: usize,
@@ -732,8 +714,8 @@ pub fn ft2_decode174_91_osd(
for i in 0..num_rows.min(7) { for i in 0..num_rows.min(7) {
tanhtoc[i] = (-toc[m][i] / 2.0).tanh(); tanhtoc[i] = (-toc[m][i] / 2.0).tanh();
} }
for j in 0..num_rows { for &nm_val in FTX_LDPC_NM[m].iter().take(num_rows) {
let n = FTX_LDPC_NM[m][j] as usize - 1; let n = nm_val as usize - 1;
if n >= FTX_LDPC_N { if n >= FTX_LDPC_N {
continue; continue;
} }
+3 -3
View File
@@ -78,9 +78,9 @@ impl BitMetricsWorkspace {
// Sync quality check: verify Costas patterns are detectable // Sync quality check: verify Costas patterns are detectable
let mut sync_ok = 0; let mut sync_ok = 0;
for group in 0..4 { for (group, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate() {
let base = group * 33; let base = group * 33;
for i in 0..4 { for (i, &costas_tone) in costas_group.iter().enumerate() {
if base + i >= FT2_FRAME_SYMBOLS { if base + i >= FT2_FRAME_SYMBOLS {
continue; continue;
} }
@@ -90,7 +90,7 @@ impl BitMetricsWorkspace {
best = tone; best = tone;
} }
} }
if best == FT4_COSTAS_PATTERN[group][i] as usize { if best == costas_tone as usize {
sync_ok += 1; sync_ok += 1;
} }
} }
+5 -5
View File
@@ -17,10 +17,10 @@ pub(crate) fn ft2_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
let mut score_f: f32 = 0.0; let mut score_f: f32 = 0.0;
let mut groups = 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 sum = Complex32::new(0.0, 0.0);
let mut complete = true; 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 = 1 + FT2_SYNC_OFFSET * m + k;
let block_abs = cand.time_offset as i32 + block as i32; let block_abs = cand.time_offset as i32 + block as i32;
if block_abs < 0 || block_abs >= wf.num_blocks 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; break;
} }
let sym_offset = base + block * wf.block_stride; 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); let elem = *wf_mag_safe(wf, sym_offset + tone);
sum += wf_elem_to_complex(elem); sum += wf_elem_to_complex(elem);
} }
@@ -63,9 +63,9 @@ pub(crate) fn ft2_extract_likelihood(
continue; continue;
} }
let sym_offset = base + sym_idx * wf.block_stride; 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); 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);
} }
} }
+17 -9
View File
@@ -190,8 +190,8 @@ impl DownsampleContext {
} }
// Apply spectral window // Apply spectral window
for i in 0..self.nfft2 { for (b, &w) in band.iter_mut().zip(self.window.iter()) {
band[i] *= self.window[i]; *b *= w;
} }
// Inverse FFT (in-place) // Inverse FFT (in-place)
@@ -221,19 +221,27 @@ fn build_spectral_window(nfft2: usize, df: f32) -> Vec<f32> {
} }
// Raised-cosine leading edge // Raised-cosine leading edge
for i in 0..iwt.min(nfft2) { for (i, w) in window.iter_mut().enumerate().take(iwt.min(nfft2)) {
window[i] = 0.5 * (1.0 + (std::f32::consts::PI * (iwt - 1 - i) as f32 / iwt as f32).cos()); *w = 0.5 * (1.0 + (std::f32::consts::PI * (iwt - 1 - i) as f32 / iwt as f32).cos());
} }
// Flat passband // Flat passband
for i in iwt..(iwt + iwf).min(nfft2) { for w in window
window[i] = 1.0; .iter_mut()
.skip(iwt)
.take((iwt + iwf).min(nfft2) - iwt)
{
*w = 1.0;
} }
// Raised-cosine trailing edge // Raised-cosine trailing edge
for i in (iwt + iwf)..(2 * iwt + iwf).min(nfft2) { for (i, w) in window
window[i] = .iter_mut()
0.5 * (1.0 + (std::f32::consts::PI * (i - (iwt + iwf)) as f32 / iwt as f32).cos()); .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 // Circular shift by iws bins
+39 -18
View File
@@ -8,12 +8,9 @@
//! peaks in the averaged spectrum, downsample each candidate, compute 2D sync //! peaks in the averaged spectrum, downsample each candidate, compute 2D sync
//! scores, extract bit metrics, and run multi-pass LDPC + OSD decode. //! scores, extract bit metrics, and run multi-pass LDPC + OSD decode.
#[allow(clippy::needless_range_loop)]
pub mod bitmetrics; pub mod bitmetrics;
pub(crate) mod decode; pub(crate) mod decode;
#[allow(clippy::needless_range_loop)]
pub mod downsample; pub mod downsample;
#[allow(clippy::needless_range_loop)]
pub mod sync; pub mod sync;
pub(crate) use self::decode::{ft2_extract_likelihood, ft2_sync_score}; 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 -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. /// Raw frequency peak candidate from the averaged power spectrum.
#[derive(Clone, Copy, Default)] #[derive(Clone, Copy, Default)]
pub struct RawCandidate { pub struct RawCandidate {
@@ -315,8 +319,8 @@ impl Ft2Pipeline {
} }
let inv_n_frames = 1.0 / n_frames as f32; let inv_n_frames = 1.0 / n_frames as f32;
for bin in 1..FT2_NH1 { for v in avg.iter_mut().take(FT2_NH1).skip(1) {
avg[bin] *= inv_n_frames; *v *= inv_n_frames;
} }
// Smooth with 15-point moving average // Smooth with 15-point moving average
@@ -620,17 +624,24 @@ impl Ft2Pipeline {
} }
// Scale and derive combined passes // Scale and derive combined passes
for i in 0..FTX_LDPC_N { let [ref mut pass0, ref mut pass1, ref mut pass2, ref mut pass3, ref mut pass4] =
llr_passes[0][i] *= 2.83; llr_passes;
llr_passes[1][i] *= 2.83; for v in pass0.iter_mut() {
llr_passes[2][i] *= 2.83; *v *= 2.83;
}
let a = llr_passes[0][i]; for v in pass1.iter_mut() {
let b = llr_passes[1][i]; *v *= 2.83;
let c = llr_passes[2][i]; }
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 // 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 a
} else if b.abs() >= c.abs() { } else if b.abs() >= c.abs() {
b b
@@ -639,7 +650,7 @@ impl Ft2Pipeline {
}; };
// Pass 4: average // 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 // Multi-pass LDPC decode using full BP+OSD decoder
@@ -647,11 +658,11 @@ impl Ft2Pipeline {
let mut message = FtxMessage::default(); let mut message = FtxMessage::default();
let mut apmask = [0u8; FTX_LDPC_N]; let mut apmask = [0u8; FTX_LDPC_N];
for pass in 0..5 { for llr_pass in &llr_passes {
if ok { if ok {
break; break;
} }
let mut log174 = llr_passes[pass]; let mut log174 = *llr_pass;
let mut message91 = [0u8; FTX_LDPC_K]; let mut message91 = [0u8; FTX_LDPC_K];
let mut cw = [0u8; FTX_LDPC_N]; let mut cw = [0u8; FTX_LDPC_N];
@@ -822,4 +833,14 @@ mod tests {
assert_eq!(b, 0); 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);
}
} }
+9 -11
View File
@@ -50,8 +50,8 @@ pub fn prepare_sync_waveforms() -> SyncWaveforms {
for group in 0..4 { for group in 0..4 {
let mut idx = 0usize; let mut idx = 0usize;
let mut phase = 0.0f32; let mut phase = 0.0f32;
for tone_idx in 0..4 { for &costas_tone in FT4_COSTAS_PATTERN[group].iter() {
let tone = FT4_COSTAS_PATTERN[group][tone_idx] as f32; let tone = costas_tone as f32;
let dphase = 4.0 * std::f32::consts::PI * tone / nss; let dphase = 4.0 * std::f32::consts::PI * tone / nss;
let half_nss = (nss / 2.0) as usize; let half_nss = (nss / 2.0) as usize;
for _step in 0..half_nss { for _step in 0..half_nss {
@@ -68,9 +68,9 @@ pub fn prepare_sync_waveforms() -> SyncWaveforms {
// Build frequency tweak phasors // Build frequency tweak phasors
for idf in FT2_SYNC_TWEAK_MIN..=FT2_SYNC_TWEAK_MAX { for idf in FT2_SYNC_TWEAK_MIN..=FT2_SYNC_TWEAK_MAX {
let tw_idx = (idf - FT2_SYNC_TWEAK_MIN) as usize; 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; 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 waveforms = prepare_sync_waveforms();
let mut refs = [[[Complex32::new(0.0, 0.0); SYNC_SAMPLES]; SYNC_GROUP_COUNT]; NUM_TWEAKS]; let mut refs = [[[Complex32::new(0.0, 0.0); SYNC_SAMPLES]; SYNC_GROUP_COUNT]; NUM_TWEAKS];
for tw_idx in 0..NUM_TWEAKS { for (tw_idx, refs_tw) in refs.iter_mut().enumerate() {
for group in 0..SYNC_GROUP_COUNT { for (group, refs_group) in refs_tw.iter_mut().enumerate() {
for i in 0..SYNC_SAMPLES { for (i, r) in refs_group.iter_mut().enumerate() {
refs[tw_idx][group][i] = *r = (waveforms.sync_wave[group][i] * waveforms.tweak_wave[tw_idx][i]).conj();
(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 mut usable = 0usize;
let n_samples = samples.len() as i32; 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; let sample_idx = pos + i as i32 * SAMPLE_STRIDE as i32;
if sample_idx < 0 || sample_idx >= n_samples { if sample_idx < 0 || sample_idx >= n_samples {
continue; continue;
} }
let sample = samples[sample_idx as usize]; let sample = samples[sample_idx as usize];
let reference = refs[i];
sum_re += sample.re * reference.re - sample.im * reference.im; sum_re += sample.re * reference.re - sample.im * reference.im;
sum_im += sample.re * reference.im + sample.im * reference.re; sum_im += sample.re * reference.im + sample.im * reference.re;
usable += 1; usable += 1;
+3 -20
View File
@@ -17,8 +17,8 @@ pub(crate) fn ft4_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
let mut score: i32 = 0; let mut score: i32 = 0;
let mut num_average: i32 = 0; let mut num_average: i32 = 0;
for m in 0..FT4_NUM_SYNC { for (m, costas_group) in FT4_COSTAS_PATTERN.iter().enumerate().take(FT4_NUM_SYNC) {
for k in 0..FT4_LENGTH_SYNC { for (k, &sm_val) in costas_group.iter().enumerate().take(FT4_LENGTH_SYNC) {
let block = 1 + FT4_SYNC_OFFSET * m + k; let block = 1 + FT4_SYNC_OFFSET * m + k;
let block_abs = cand.time_offset as i32 + block as i32; let block_abs = cand.time_offset as i32 + block as i32;
if block_abs < 0 { 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 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 { if sm > 0 {
let a = wf_mag_safe(wf, p_offset + sm).mag_int(); 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -244,14 +237,4 @@ mod tests {
ft4_encode(&payload, &mut tones2); ft4_encode(&payload, &mut tones2);
assert_eq!(tones1, 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);
}
} }
+2 -2
View File
@@ -18,7 +18,7 @@ pub(crate) fn ft8_sync_score(wf: &Waterfall, cand: &Candidate) -> i32 {
let mut num_average: i32 = 0; let mut num_average: i32 = 0;
for m in 0..FT8_NUM_SYNC { 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 = FT8_SYNC_OFFSET * m + k;
let block_abs = cand.time_offset as i32 + block as i32; let block_abs = cand.time_offset as i32 + block as i32;
if block_abs < 0 { 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 p_offset = base + block * wf.block_stride;
let sm = FT8_COSTAS_PATTERN[k] as usize; let sm = sm_val as usize;
if sm > 0 { if sm > 0 {
let a = wf_mag_safe(wf, p_offset + sm).mag_int(); let a = wf_mag_safe(wf, p_offset + sm).mag_int();
-3
View File
@@ -4,11 +4,8 @@
pub mod common; pub mod common;
mod decoder; mod decoder;
#[allow(clippy::needless_range_loop)]
pub mod ft2; pub mod ft2;
#[allow(clippy::needless_range_loop)]
pub mod ft4; pub mod ft4;
#[allow(clippy::needless_range_loop)]
pub mod ft8; pub mod ft8;
pub use decoder::{Ft8DecodeResult, Ft8Decoder}; pub use decoder::{Ft8DecodeResult, Ft8Decoder};