[feat](trx-rs): add FT2 decoder protocol support

Implement a distinct FT2 protocol path in the decoder stack and align\nits timing with the confirmed FT2 framing used by Decodium.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>

Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
2026-03-14 19:49:05 +01:00
parent d547c45a9c
commit ad6aa6aab4
9 changed files with 116 additions and 46 deletions
+9
View File
@@ -31,12 +31,21 @@ fn main() {
println!("cargo:rerun-if-changed=src/ft8_wrapper.c");
println!("cargo:rerun-if-changed={base}/common/monitor.c");
println!("cargo:rerun-if-changed={base}/common/monitor.h");
println!("cargo:rerun-if-changed={base}/fft/kiss_fft.c");
println!("cargo:rerun-if-changed={base}/fft/kiss_fft.h");
println!("cargo:rerun-if-changed={base}/fft/kiss_fftr.c");
println!("cargo:rerun-if-changed={base}/fft/kiss_fftr.h");
println!("cargo:rerun-if-changed={base}/ft8/constants.c");
println!("cargo:rerun-if-changed={base}/ft8/constants.h");
println!("cargo:rerun-if-changed={base}/ft8/crc.c");
println!("cargo:rerun-if-changed={base}/ft8/crc.h");
println!("cargo:rerun-if-changed={base}/ft8/decode.c");
println!("cargo:rerun-if-changed={base}/ft8/decode.h");
println!("cargo:rerun-if-changed={base}/ft8/ldpc.c");
println!("cargo:rerun-if-changed={base}/ft8/ldpc.h");
println!("cargo:rerun-if-changed={base}/ft8/message.c");
println!("cargo:rerun-if-changed={base}/ft8/message.h");
println!("cargo:rerun-if-changed={base}/ft8/text.c");
println!("cargo:rerun-if-changed={base}/ft8/text.h");
}
+20 -1
View File
@@ -11,6 +11,13 @@
#include <string.h>
#include <stdio.h>
enum
{
TRX_FTX_PROTOCOL_FT4 = 0,
TRX_FTX_PROTOCOL_FT8 = 1,
TRX_FTX_PROTOCOL_FT2 = 2,
};
// Callsign hash table (from demo/decode_ft8.c)
#define CALLSIGN_HASHTABLE_SIZE 256
@@ -120,7 +127,19 @@ ft8_decoder_t* ft8_decoder_create(int sample_rate, float f_min, float f_max, int
dec->cfg.sample_rate = sample_rate;
dec->cfg.time_osr = time_osr;
dec->cfg.freq_osr = freq_osr;
dec->cfg.protocol = (protocol == 0) ? FTX_PROTOCOL_FT4 : FTX_PROTOCOL_FT8;
switch (protocol)
{
case TRX_FTX_PROTOCOL_FT4:
dec->cfg.protocol = FTX_PROTOCOL_FT4;
break;
case TRX_FTX_PROTOCOL_FT2:
dec->cfg.protocol = FTX_PROTOCOL_FT2;
break;
case TRX_FTX_PROTOCOL_FT8:
default:
dec->cfg.protocol = FTX_PROTOCOL_FT8;
break;
}
hashtable_init();
monitor_init(&dec->mon, &dec->cfg);
+29 -28
View File
@@ -12,6 +12,9 @@ const TIME_OSR: i32 = 2;
const FREQ_OSR: i32 = 2;
const FTX_MAX_MESSAGE_LENGTH: usize = 35;
const PROTOCOL_FT4: c_int = 0;
const PROTOCOL_FT8: c_int = 1;
const PROTOCOL_FT2: c_int = 2;
#[repr(C)]
#[derive(Clone, Copy)]
@@ -63,30 +66,18 @@ unsafe impl Send for Ft8Decoder {}
impl Ft8Decoder {
pub fn new(sample_rate: u32) -> Result<Self, String> {
unsafe {
let ptr = ft8_decoder_create(
sample_rate as c_int,
F_MIN_HZ,
F_MAX_HZ,
TIME_OSR as c_int,
FREQ_OSR as c_int,
1, // FTX_PROTOCOL_FT8
);
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
if block_size == 0 {
ft8_decoder_free(inner.as_ptr());
return Err("invalid FT8 block size".to_string());
}
Ok(Self {
inner,
block_size,
sample_rate,
})
}
Self::new_with_protocol(sample_rate, PROTOCOL_FT8, "FT8")
}
pub fn new_ft4(sample_rate: u32) -> Result<Self, String> {
Self::new_with_protocol(sample_rate, PROTOCOL_FT4, "FT4")
}
pub fn new_ft2(sample_rate: u32) -> Result<Self, String> {
Self::new_with_protocol(sample_rate, PROTOCOL_FT2, "FT2")
}
fn new_with_protocol(sample_rate: u32, protocol: c_int, label: &str) -> Result<Self, String> {
unsafe {
let ptr = ft8_decoder_create(
sample_rate as c_int,
@@ -94,13 +85,13 @@ impl Ft8Decoder {
F_MAX_HZ,
TIME_OSR as c_int,
FREQ_OSR as c_int,
0, // FTX_PROTOCOL_FT4
protocol,
);
let inner = NonNull::new(ptr).ok_or_else(|| "ft8_decoder_create failed".to_string())?;
let block_size = ft8_decoder_block_size(inner.as_ptr()) as usize;
if block_size == 0 {
ft8_decoder_free(inner.as_ptr());
return Err("invalid FT4 block size".to_string());
return Err(format!("invalid {label} block size"));
}
Ok(Self {
inner,
@@ -110,11 +101,6 @@ impl Ft8Decoder {
}
}
pub fn new_ft2(sample_rate: u32) -> Result<Self, String> {
// Wired to FT4 protocol pending a dedicated FT2 implementation.
Self::new_ft4(sample_rate)
}
pub fn block_size(&self) -> usize {
self.block_size
}
@@ -179,3 +165,18 @@ impl Drop for Ft8Decoder {
}
}
}
#[cfg(test)]
mod tests {
use super::Ft8Decoder;
#[test]
fn ft2_uses_distinct_block_size() {
let ft4 = Ft8Decoder::new_ft4(12_000).expect("ft4 decoder");
let ft2 = Ft8Decoder::new_ft2(12_000).expect("ft2 decoder");
assert!(ft2.block_size() < ft4.block_size());
assert_eq!(ft4.block_size(), 576);
assert_eq!(ft2.block_size(), 288);
}
}