[refactor](trx-rs): remove trx-ft8 C FFI crate, use trx-ftx directly

Delete trx-ft8 (C wrapper around ft8_lib + ft2_ldpc) and update
trx-server to depend on trx-ftx (pure Rust) directly. Removes
~2,900 lines of C code and all unsafe FFI.

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-18 22:22:20 +01:00
parent de79e8a1e6
commit 5dabe32106
10 changed files with 4 additions and 2929 deletions
Generated
+1 -9
View File
@@ -2642,14 +2642,6 @@ dependencies = [
"trx-protocol", "trx-protocol",
] ]
[[package]]
name = "trx-ft8"
version = "0.1.0"
dependencies = [
"cc",
"libc",
]
[[package]] [[package]]
name = "trx-ftx" name = "trx-ftx"
version = "0.1.0" version = "0.1.0"
@@ -2712,7 +2704,7 @@ dependencies = [
"trx-core", "trx-core",
"trx-cw", "trx-cw",
"trx-decode-log", "trx-decode-log",
"trx-ft8", "trx-ftx",
"trx-protocol", "trx-protocol",
"trx-reporting", "trx-reporting",
"trx-vdes", "trx-vdes",
-1
View File
@@ -8,7 +8,6 @@ members = [
"src/decoders/trx-aprs", "src/decoders/trx-aprs",
"src/decoders/trx-cw", "src/decoders/trx-cw",
"src/decoders/trx-decode-log", "src/decoders/trx-decode-log",
"src/decoders/trx-ft8",
"src/decoders/trx-ftx", "src/decoders/trx-ftx",
"src/decoders/trx-rds", "src/decoders/trx-rds",
"src/decoders/trx-vdes", "src/decoders/trx-vdes",
-14
View File
@@ -1,14 +0,0 @@
# SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
#
# SPDX-License-Identifier: BSD-2-Clause
[package]
name = "trx-ft8"
version.workspace = true
edition = "2021"
[dependencies]
libc = "0.2"
[build-dependencies]
cc = "1"
-54
View File
@@ -1,54 +0,0 @@
// SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
fn main() {
let base = "../../../external/ft8_lib";
let mut build = cc::Build::new();
build
.include(base)
.include(format!("{base}/common"))
.include(format!("{base}/fft"))
.include(format!("{base}/ft8"))
.define("_GNU_SOURCE", None)
.define("_POSIX_C_SOURCE", "200809L")
.file("src/ft8_wrapper.c")
.file(format!("{base}/common/monitor.c"))
.file(format!("{base}/fft/kiss_fft.c"))
.file(format!("{base}/fft/kiss_fftr.c"))
.file(format!("{base}/ft8/constants.c"))
.file(format!("{base}/ft8/crc.c"))
.file(format!("{base}/ft8/decode.c"))
.file(format!("{base}/ft8/encode.c"))
.file(format!("{base}/ft8/ldpc.c"))
.file(format!("{base}/ft8/message.c"))
.file(format!("{base}/ft8/text.c"))
.flag_if_supported("-std=c99")
.flag_if_supported("-Wno-unused-const-variable")
.flag_if_supported("-Wno-unused-function")
.compile("trx_ft8");
println!("cargo:rustc-link-lib=m");
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/encode.c");
println!("cargo:rerun-if-changed={base}/ft8/encode.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");
}
-821
View File
@@ -1,821 +0,0 @@
// SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
#include "ft2_ldpc.h"
#include <ft8/constants.h>
#include <ft8/crc.h>
#include <math.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
typedef struct
{
int index;
float abs_llr;
} reliability_entry_t;
typedef struct
{
int* head;
int* next;
int (*pairs)[2];
int capacity;
int count;
int size;
int last_pattern;
int next_index;
} osd_box_t;
static int ft2_ldpc_check(const uint8_t codeword[])
{
int errors = 0;
for (int m = 0; m < FTX_LDPC_M; ++m)
{
uint8_t x = 0;
for (int i = 0; i < kFTX_LDPC_Num_rows[m]; ++i)
{
x ^= codeword[kFTX_LDPC_Nm[m][i] - 1];
}
if (x != 0)
++errors;
}
return errors;
}
static float ft2_fast_atanh(float x)
{
float x2 = x * x;
float a = x * (945.0f + x2 * (-735.0f + x2 * 64.0f));
float b = 945.0f + x2 * (-1050.0f + x2 * 225.0f);
return a / b;
}
static float ft2_platanh(float x)
{
int isign = 1;
float z = x;
if (x < 0.0f)
{
isign = -1;
z = -x;
}
if (z <= 0.664f)
return x / 0.83f;
if (z <= 0.9217f)
return isign * ((z - 0.4064f) / 0.322f);
if (z <= 0.9951f)
return isign * ((z - 0.8378f) / 0.0524f);
if (z <= 0.9998f)
return isign * ((z - 0.9914f) / 0.0012f);
return isign * 7.0f;
}
static void pack_bits91(const uint8_t bit_array[], int num_bits, uint8_t packed[])
{
int num_bytes = (num_bits + 7) / 8;
memset(packed, 0, (size_t)num_bytes);
uint8_t mask = 0x80u;
int byte_idx = 0;
for (int i = 0; i < num_bits; ++i)
{
if (bit_array[i] != 0)
packed[byte_idx] |= mask;
mask >>= 1;
if (mask == 0)
{
mask = 0x80u;
++byte_idx;
}
}
}
static bool check_crc91(const uint8_t plain91[])
{
uint8_t a91[FTX_LDPC_K_BYTES];
pack_bits91(plain91, FTX_LDPC_K, a91);
uint16_t crc_extracted = ftx_extract_crc(a91);
a91[9] &= 0xF8;
a91[10] &= 0x00;
uint16_t crc_calculated = ftx_compute_crc(a91, 96 - 14);
return crc_extracted == crc_calculated;
}
static uint8_t parity8(uint8_t x)
{
x ^= x >> 4;
x ^= x >> 2;
x ^= x >> 1;
return x & 1u;
}
static void encode174_91_nocrc_bits(const uint8_t message91[], uint8_t codeword[])
{
uint8_t packed[FTX_LDPC_K_BYTES];
pack_bits91(message91, FTX_LDPC_K, packed);
for (int i = 0; i < FTX_LDPC_K; ++i)
{
codeword[i] = message91[i] & 0x1u;
}
for (int i = 0; i < FTX_LDPC_M; ++i)
{
uint8_t nsum = 0;
for (int j = 0; j < FTX_LDPC_K_BYTES; ++j)
{
nsum ^= parity8(packed[j] & kFTX_LDPC_generator[i][j]);
}
codeword[FTX_LDPC_K + i] = nsum & 0x1u;
}
}
static int cmp_reliability_desc(const void* lhs, const void* rhs)
{
const reliability_entry_t* a = (const reliability_entry_t*)lhs;
const reliability_entry_t* b = (const reliability_entry_t*)rhs;
if (a->abs_llr < b->abs_llr)
return 1;
if (a->abs_llr > b->abs_llr)
return -1;
return 0;
}
static void xor_rows(uint8_t* dst, const uint8_t* src, int len)
{
for (int i = 0; i < len; ++i)
dst[i] ^= src[i];
}
static void mrbencode91(const uint8_t* me, uint8_t* codeword, const uint8_t* g2, int n, int k)
{
memset(codeword, 0, (size_t)n);
for (int i = 0; i < k; ++i)
{
if (me[i] == 0)
continue;
for (int j = 0; j < n; ++j)
codeword[j] ^= g2[j * k + i];
}
}
static void nextpat91(uint8_t* mi, int k, int iorder, int* iflag)
{
int ind = -1;
for (int i = 0; i < k - 1; ++i)
{
if (mi[i] == 0 && mi[i + 1] == 1)
ind = i;
}
if (ind < 0)
{
*iflag = -1;
return;
}
uint8_t* ms = (uint8_t*)calloc((size_t)k, sizeof(uint8_t));
if (ms == NULL)
{
*iflag = -1;
return;
}
for (int i = 0; i < ind; ++i)
ms[i] = mi[i];
ms[ind] = 1;
ms[ind + 1] = 0;
if (ind + 1 < k)
{
int nz = iorder;
for (int i = 0; i < k; ++i)
nz -= ms[i];
for (int i = k - nz; i < k; ++i)
ms[i] = 1;
}
memcpy(mi, ms, (size_t)k);
free(ms);
*iflag = -1;
for (int i = 0; i < k; ++i)
{
if (mi[i] == 1)
{
*iflag = i;
break;
}
}
}
static bool osd_box_init(osd_box_t* box, int ntau)
{
box->size = 1 << ntau;
box->capacity = 5000;
box->count = 0;
box->last_pattern = -1;
box->next_index = -1;
box->head = (int*)malloc(sizeof(int) * box->size);
box->next = (int*)malloc(sizeof(int) * box->capacity);
box->pairs = malloc(sizeof(int[2]) * box->capacity);
if (box->head == NULL || box->next == NULL || box->pairs == NULL)
{
free(box->head);
free(box->next);
free(box->pairs);
return false;
}
for (int i = 0; i < box->size; ++i)
box->head[i] = -1;
for (int i = 0; i < box->capacity; ++i)
{
box->next[i] = -1;
box->pairs[i][0] = -1;
box->pairs[i][1] = -1;
}
return true;
}
static void osd_box_free(osd_box_t* box)
{
free(box->head);
free(box->next);
free(box->pairs);
}
static int pattern_hash(const uint8_t* e2, int ntau)
{
int ipat = 0;
for (int i = 0; i < ntau; ++i)
{
if (e2[i] != 0)
ipat |= 1 << (ntau - i - 1);
}
return ipat;
}
static void boxit91(osd_box_t* box, const uint8_t* e2, int ntau, int i1, int i2)
{
if (box->count >= box->capacity)
return;
int idx = box->count++;
box->pairs[idx][0] = i1;
box->pairs[idx][1] = i2;
int ipat = pattern_hash(e2, ntau);
int ip = box->head[ipat];
if (ip == -1)
{
box->head[ipat] = idx;
}
else
{
while (box->next[ip] != -1)
ip = box->next[ip];
box->next[ip] = idx;
}
}
static void fetchit91(osd_box_t* box, const uint8_t* e2, int ntau, int* i1, int* i2)
{
int ipat = pattern_hash(e2, ntau);
int index = box->head[ipat];
if (box->last_pattern != ipat && index >= 0)
{
*i1 = box->pairs[index][0];
*i2 = box->pairs[index][1];
box->next_index = box->next[index];
}
else if (box->last_pattern == ipat && box->next_index >= 0)
{
*i1 = box->pairs[box->next_index][0];
*i2 = box->pairs[box->next_index][1];
box->next_index = box->next[box->next_index];
}
else
{
*i1 = -1;
*i2 = -1;
box->next_index = -1;
}
box->last_pattern = ipat;
}
static void osd174_91(float llr[], int k, uint8_t apmask[], int ndeep, uint8_t message91[], uint8_t cw[], int* nhardmin, float* dmin)
{
const int n = FTX_LDPC_N;
static bool gen_ready = false;
static uint8_t gen[FTX_LDPC_K][FTX_LDPC_N];
if (!gen_ready)
{
for (int i = 0; i < FTX_LDPC_K; ++i)
{
uint8_t msg[FTX_LDPC_K] = { 0 };
msg[i] = 1;
if (i < 77)
{
for (int j = 77; j < FTX_LDPC_K; ++j)
msg[j] = 0;
}
encode174_91_nocrc_bits(msg, gen[i]);
}
gen_ready = true;
}
uint8_t* genmrb = (uint8_t*)malloc((size_t)k * n);
uint8_t* g2 = (uint8_t*)malloc((size_t)n * k);
uint8_t* m0 = (uint8_t*)malloc((size_t)k);
uint8_t* me = (uint8_t*)malloc((size_t)k);
uint8_t* mi = (uint8_t*)malloc((size_t)k);
uint8_t* misub = (uint8_t*)malloc((size_t)k);
uint8_t* e2sub = (uint8_t*)malloc((size_t)(n - k));
uint8_t* e2 = (uint8_t*)malloc((size_t)(n - k));
uint8_t* ui = (uint8_t*)malloc((size_t)(n - k));
uint8_t* r2pat = (uint8_t*)malloc((size_t)(n - k));
uint8_t* hdec = (uint8_t*)malloc((size_t)n);
uint8_t* c0 = (uint8_t*)malloc((size_t)n);
uint8_t* ce = (uint8_t*)malloc((size_t)n);
uint8_t* nxor = (uint8_t*)malloc((size_t)n);
uint8_t* apmaskr = (uint8_t*)malloc((size_t)n);
float* rx = (float*)malloc(sizeof(float) * n);
float* absrx = (float*)malloc(sizeof(float) * n);
reliability_entry_t* rel = (reliability_entry_t*)malloc(sizeof(reliability_entry_t) * n);
int* indices = (int*)malloc(sizeof(int) * n);
if (genmrb == NULL || g2 == NULL || m0 == NULL || me == NULL || mi == NULL || misub == NULL || e2sub == NULL || e2 == NULL || ui == NULL || r2pat == NULL || hdec == NULL || c0 == NULL || ce == NULL || nxor == NULL || apmaskr == NULL || rx == NULL || absrx == NULL || rel == NULL || indices == NULL)
{
goto cleanup;
}
for (int i = 0; i < n; ++i)
{
rx[i] = llr[i];
apmaskr[i] = apmask[i];
hdec[i] = (rx[i] >= 0.0f) ? 1u : 0u;
absrx[i] = fabsf(rx[i]);
rel[i].index = i;
rel[i].abs_llr = absrx[i];
}
qsort(rel, n, sizeof(rel[0]), cmp_reliability_desc);
for (int i = 0; i < n; ++i)
{
indices[i] = rel[i].index;
for (int row = 0; row < k; ++row)
genmrb[row * n + i] = gen[row][indices[i]];
}
for (int id = 0; id < k; ++id)
{
int max_col = k + 20;
if (max_col > n)
max_col = n;
for (int col = id; col < max_col; ++col)
{
if (genmrb[id * n + col] == 0)
continue;
if (col != id)
{
for (int row = 0; row < k; ++row)
{
uint8_t swap = genmrb[row * n + id];
genmrb[row * n + id] = genmrb[row * n + col];
genmrb[row * n + col] = swap;
}
int itmp = indices[id];
indices[id] = indices[col];
indices[col] = itmp;
}
for (int row = 0; row < k; ++row)
{
if (row != id && genmrb[row * n + id] == 1)
xor_rows(&genmrb[row * n], &genmrb[id * n], n);
}
break;
}
}
for (int row = 0; row < k; ++row)
{
for (int col = 0; col < n; ++col)
g2[col * k + row] = genmrb[row * n + col];
}
for (int i = 0; i < n; ++i)
{
hdec[i] = (rx[indices[i]] >= 0.0f) ? 1u : 0u;
absrx[i] = fabsf(rx[indices[i]]);
rx[i] = llr[indices[i]];
apmaskr[i] = apmask[indices[i]];
}
for (int i = 0; i < k; ++i)
m0[i] = hdec[i];
mrbencode91(m0, c0, g2, n, k);
for (int i = 0; i < n; ++i)
nxor[i] = c0[i] ^ hdec[i];
*nhardmin = 0;
*dmin = 0.0f;
for (int i = 0; i < n; ++i)
{
*nhardmin += nxor[i];
*dmin += nxor[i] != 0 ? absrx[i] : 0.0f;
}
memcpy(cw, c0, (size_t)n);
if (ndeep > 6)
ndeep = 6;
int nord = 0;
int npre1 = 0;
int npre2 = 0;
int nt = 0;
int ntheta = 0;
int ntau = 0;
if (ndeep == 0)
{
goto reorder;
}
if (ndeep == 1)
{
nord = 1;
nt = 40;
ntheta = 12;
}
else if (ndeep == 2)
{
nord = 1;
npre1 = 1;
nt = 40;
ntheta = 10;
}
else if (ndeep == 3)
{
nord = 1;
npre1 = 1;
npre2 = 1;
nt = 40;
ntheta = 12;
ntau = 14;
}
else if (ndeep == 4)
{
nord = 2;
npre1 = 1;
npre2 = 1;
nt = 40;
ntheta = 12;
ntau = 17;
}
else if (ndeep == 5)
{
nord = 3;
npre1 = 1;
npre2 = 1;
nt = 40;
ntheta = 12;
ntau = 15;
}
else
{
nord = 4;
npre1 = 1;
npre2 = 1;
nt = 95;
ntheta = 12;
ntau = 15;
}
for (int iorder = 1; iorder <= nord; ++iorder)
{
memset(misub, 0, (size_t)k);
for (int i = k - iorder; i < k; ++i)
misub[i] = 1;
int iflag = k - iorder;
while (iflag >= 0)
{
int iend = (iorder == nord && npre1 == 0) ? iflag : 0;
float d1 = 0.0f;
for (int n1 = iflag; n1 >= iend; --n1)
{
memcpy(mi, misub, (size_t)k);
mi[n1] = 1;
bool masked = false;
for (int i = 0; i < k; ++i)
{
if (apmaskr[i] != 0 && mi[i] != 0)
{
masked = true;
break;
}
}
if (masked)
continue;
for (int i = 0; i < k; ++i)
me[i] = m0[i] ^ mi[i];
if (n1 == iflag)
{
mrbencode91(me, ce, g2, n, k);
for (int i = 0; i < n - k; ++i)
{
e2sub[i] = ce[k + i] ^ hdec[k + i];
e2[i] = e2sub[i];
}
int nd1kpt = 1;
for (int i = 0; i < nt; ++i)
nd1kpt += e2sub[i];
d1 = 0.0f;
for (int i = 0; i < k; ++i)
d1 += (me[i] ^ hdec[i]) != 0 ? absrx[i] : 0.0f;
if (nd1kpt <= ntheta)
{
float dd = d1;
for (int i = 0; i < n - k; ++i)
dd += e2sub[i] != 0 ? absrx[k + i] : 0.0f;
if (dd < *dmin)
{
*dmin = dd;
memcpy(cw, ce, (size_t)n);
*nhardmin = 0;
for (int i = 0; i < n; ++i)
*nhardmin += ce[i] ^ hdec[i];
}
}
}
else
{
for (int i = 0; i < n - k; ++i)
e2[i] = e2sub[i] ^ g2[(k + i) * k + n1];
int nd1kpt = 2;
for (int i = 0; i < nt; ++i)
nd1kpt += e2[i];
if (nd1kpt <= ntheta)
{
mrbencode91(me, ce, g2, n, k);
float dd = d1 + ((ce[n1] ^ hdec[n1]) != 0 ? absrx[n1] : 0.0f);
for (int i = 0; i < n - k; ++i)
dd += e2[i] != 0 ? absrx[k + i] : 0.0f;
if (dd < *dmin)
{
*dmin = dd;
memcpy(cw, ce, (size_t)n);
*nhardmin = 0;
for (int i = 0; i < n; ++i)
*nhardmin += ce[i] ^ hdec[i];
}
}
}
}
nextpat91(misub, k, iorder, &iflag);
}
}
if (npre2 == 1)
{
osd_box_t box;
if (osd_box_init(&box, ntau))
{
for (int i1 = k - 1; i1 >= 0; --i1)
{
for (int i2 = i1 - 1; i2 >= 0; --i2)
{
for (int i = 0; i < ntau; ++i)
mi[i] = g2[(k + i) * k + i1] ^ g2[(k + i) * k + i2];
boxit91(&box, mi, ntau, i1, i2);
}
}
memset(misub, 0, (size_t)k);
for (int i = k - nord; i < k; ++i)
misub[i] = 1;
int iflag = k - nord;
while (iflag >= 0)
{
for (int i = 0; i < k; ++i)
me[i] = m0[i] ^ misub[i];
mrbencode91(me, ce, g2, n, k);
for (int i = 0; i < n - k; ++i)
e2sub[i] = ce[k + i] ^ hdec[k + i];
for (int i2 = 0; i2 <= ntau; ++i2)
{
memset(ui, 0, (size_t)(n - k));
if (i2 > 0)
ui[i2 - 1] = 1;
for (int i = 0; i < ntau; ++i)
r2pat[i] = e2sub[i] ^ ui[i];
box.last_pattern = -1;
box.next_index = -1;
while (true)
{
int in1 = -1;
int in2 = -1;
fetchit91(&box, r2pat, ntau, &in1, &in2);
if (in1 < 0 || in2 < 0)
break;
memcpy(mi, misub, (size_t)k);
mi[in1] = 1;
mi[in2] = 1;
int w = 0;
bool masked = false;
for (int i = 0; i < k; ++i)
{
w += mi[i];
if (apmaskr[i] != 0 && mi[i] != 0)
masked = true;
}
if (w < nord + npre1 + npre2 || masked)
continue;
for (int i = 0; i < k; ++i)
me[i] = m0[i] ^ mi[i];
mrbencode91(me, ce, g2, n, k);
float dd = 0.0f;
int nh = 0;
for (int i = 0; i < n; ++i)
{
uint8_t diff = ce[i] ^ hdec[i];
nh += diff;
if (diff != 0)
dd += absrx[i];
}
if (dd < *dmin)
{
*dmin = dd;
memcpy(cw, ce, (size_t)n);
*nhardmin = nh;
}
}
}
nextpat91(misub, k, nord, &iflag);
}
osd_box_free(&box);
}
}
reorder:
{
uint8_t reordered_cw[FTX_LDPC_N];
for (int i = 0; i < n; ++i)
reordered_cw[indices[i]] = cw[i];
memcpy(cw, reordered_cw, (size_t)n);
memcpy(message91, cw, FTX_LDPC_K);
if (!check_crc91(message91))
*nhardmin = -*nhardmin;
}
cleanup:
free(genmrb);
free(g2);
free(m0);
free(me);
free(mi);
free(misub);
free(e2sub);
free(e2);
free(ui);
free(r2pat);
free(hdec);
free(c0);
free(ce);
free(nxor);
free(apmaskr);
free(rx);
free(absrx);
free(rel);
free(indices);
}
void ft2_decode174_91_osd(float llr[], int keff, int maxosd, int norder, uint8_t apmask[], uint8_t message91[], uint8_t cw[], int* ntype, int* nharderror, float* dmin)
{
if (keff != FTX_LDPC_K)
{
*ntype = 0;
*nharderror = -1;
*dmin = 0.0f;
return;
}
const int maxiterations = 30;
int nosd = 0;
if (maxosd > 3)
maxosd = 3;
float zsave[3][FTX_LDPC_N] = { { 0 } };
if (maxosd == 0)
{
nosd = 1;
memcpy(zsave[0], llr, sizeof(float) * FTX_LDPC_N);
}
else if (maxosd > 0)
{
nosd = maxosd;
}
float tov[FTX_LDPC_N][3] = { { 0 } };
float toc[FTX_LDPC_M][7] = { { 0 } };
float zsum[FTX_LDPC_N] = { 0 };
uint8_t hdec[FTX_LDPC_N];
uint8_t best_cw[FTX_LDPC_N] = { 0 };
int ncnt = 0;
int nclast = 0;
for (int iter = 0; iter <= maxiterations; ++iter)
{
float zn[FTX_LDPC_N];
for (int i = 0; i < FTX_LDPC_N; ++i)
{
zn[i] = llr[i];
if (apmask[i] != 1)
zn[i] += tov[i][0] + tov[i][1] + tov[i][2];
zsum[i] += zn[i];
}
if (iter > 0 && iter <= maxosd)
memcpy(zsave[iter - 1], zsum, sizeof(zsum));
for (int i = 0; i < FTX_LDPC_N; ++i)
best_cw[i] = (zn[i] > 0.0f) ? 1u : 0u;
int ncheck = ft2_ldpc_check(best_cw);
if (ncheck == 0 && check_crc91(best_cw))
{
memcpy(message91, best_cw, FTX_LDPC_K);
memcpy(cw, best_cw, FTX_LDPC_N);
for (int i = 0; i < FTX_LDPC_N; ++i)
hdec[i] = (llr[i] >= 0.0f) ? 1u : 0u;
*nharderror = 0;
*dmin = 0.0f;
for (int i = 0; i < FTX_LDPC_N; ++i)
{
uint8_t diff = hdec[i] ^ best_cw[i];
*nharderror += diff;
if (diff != 0)
*dmin += fabsf(llr[i]);
}
*ntype = 1;
return;
}
if (iter > 0)
{
int nd = ncheck - nclast;
ncnt = (nd < 0) ? 0 : (ncnt + 1);
if (ncnt >= 5 && iter >= 10 && ncheck > 15)
{
*nharderror = -1;
break;
}
}
nclast = ncheck;
for (int m = 0; m < FTX_LDPC_M; ++m)
{
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
{
int n = kFTX_LDPC_Nm[m][n_idx] - 1;
toc[m][n_idx] = zn[n];
for (int kk = 0; kk < 3; ++kk)
{
if ((kFTX_LDPC_Mn[n][kk] - 1) == m)
toc[m][n_idx] -= tov[n][kk];
}
}
}
for (int m = 0; m < FTX_LDPC_M; ++m)
{
float tanhtoc[7];
for (int i = 0; i < 7; ++i)
tanhtoc[i] = tanhf(-toc[m][i] / 2.0f);
for (int j = 0; j < kFTX_LDPC_Num_rows[m]; ++j)
{
int n = kFTX_LDPC_Nm[m][j] - 1;
float Tmn = 1.0f;
for (int n_idx = 0; n_idx < kFTX_LDPC_Num_rows[m]; ++n_idx)
{
if ((kFTX_LDPC_Nm[m][n_idx] - 1) != n)
Tmn *= tanhtoc[n_idx];
}
for (int kk = 0; kk < 3; ++kk)
{
if ((kFTX_LDPC_Mn[n][kk] - 1) == m)
tov[n][kk] = 2.0f * ft2_platanh(-Tmn);
}
}
}
}
for (int i = 0; i < nosd; ++i)
{
int osd_harderror = -1;
float osd_dmin = 0.0f;
osd174_91(zsave[i], keff, apmask, norder, message91, cw, &osd_harderror, &osd_dmin);
if (osd_harderror > 0)
{
*nharderror = osd_harderror;
*dmin = 0.0f;
for (int j = 0; j < FTX_LDPC_N; ++j)
{
hdec[j] = (llr[j] >= 0.0f) ? 1u : 0u;
if ((hdec[j] ^ cw[j]) != 0)
*dmin += fabsf(llr[j]);
}
*ntype = 2;
return;
}
}
*ntype = 0;
*nharderror = -1;
*dmin = 0.0f;
}
-20
View File
@@ -1,20 +0,0 @@
// SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
#ifndef TRX_FT2_LDPC_H
#define TRX_FT2_LDPC_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
void ft2_decode174_91_osd(float llr[], int keff, int maxosd, int norder, uint8_t apmask[], uint8_t message91[], uint8_t cw[], int* ntype, int* nharderror, float* dmin);
#ifdef __cplusplus
}
#endif
#endif
File diff suppressed because it is too large Load Diff
-208
View File
@@ -1,208 +0,0 @@
// SPDX-FileCopyrightText: 2026 Stanislaw Grams <stanislawgrams@gmail.com>
//
// SPDX-License-Identifier: BSD-2-Clause
use libc::{c_float, c_int, c_void};
use std::ffi::CStr;
use std::ptr::NonNull;
const DEFAULT_F_MIN_HZ: f32 = 200.0;
const DEFAULT_F_MAX_HZ: f32 = 3000.0;
const DEFAULT_TIME_OSR: i32 = 2;
const DEFAULT_FREQ_OSR: i32 = 2;
const FT2_F_MIN_HZ: f32 = 200.0;
const FT2_F_MAX_HZ: f32 = 5000.0;
const FT2_TIME_OSR: i32 = 8;
const FT2_FREQ_OSR: i32 = 4;
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)]
struct Ft8DecodeResultRaw {
text: [libc::c_char; FTX_MAX_MESSAGE_LENGTH],
snr_db: c_float,
dt_s: c_float,
freq_hz: c_float,
}
#[derive(Debug, Clone)]
pub struct Ft8DecodeResult {
pub text: String,
pub snr_db: f32,
pub dt_s: f32,
pub freq_hz: f32,
}
extern "C" {
fn ft8_decoder_create(
sample_rate: c_int,
f_min: c_float,
f_max: c_float,
time_osr: c_int,
freq_osr: c_int,
protocol: c_int,
) -> *mut c_void;
fn ft8_decoder_free(dec: *mut c_void);
fn ft8_decoder_block_size(dec: *const c_void) -> c_int;
fn ft8_decoder_window_samples(dec: *const c_void) -> c_int;
fn ft8_decoder_reset(dec: *mut c_void);
fn ft8_decoder_process(dec: *mut c_void, frame: *const c_float);
fn ft8_decoder_is_ready(dec: *const c_void) -> c_int;
fn ft8_decoder_decode(
dec: *mut c_void,
out: *mut Ft8DecodeResultRaw,
max_results: c_int,
) -> c_int;
}
pub struct Ft8Decoder {
inner: NonNull<c_void>,
block_size: usize,
window_samples: usize,
sample_rate: u32,
}
// SAFETY: Ft8Decoder owns its C-side state and is not shared across threads.
// It is only moved into a single task, so Send is safe.
unsafe impl Send for Ft8Decoder {}
impl Ft8Decoder {
pub fn new(sample_rate: u32) -> Result<Self, String> {
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> {
let (f_min, f_max, time_osr, freq_osr) = match protocol {
PROTOCOL_FT2 => (FT2_F_MIN_HZ, FT2_F_MAX_HZ, FT2_TIME_OSR, FT2_FREQ_OSR),
_ => (
DEFAULT_F_MIN_HZ,
DEFAULT_F_MAX_HZ,
DEFAULT_TIME_OSR,
DEFAULT_FREQ_OSR,
),
};
unsafe {
let ptr = ft8_decoder_create(
sample_rate as c_int,
f_min,
f_max,
time_osr as c_int,
freq_osr as c_int,
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;
let window_samples = ft8_decoder_window_samples(inner.as_ptr()) as usize;
if block_size == 0 {
ft8_decoder_free(inner.as_ptr());
return Err(format!("invalid {label} block size"));
}
if window_samples == 0 {
ft8_decoder_free(inner.as_ptr());
return Err(format!("invalid {label} analysis window"));
}
Ok(Self {
inner,
block_size,
window_samples,
sample_rate,
})
}
}
pub fn block_size(&self) -> usize {
self.block_size
}
pub fn sample_rate(&self) -> u32 {
self.sample_rate
}
pub fn window_samples(&self) -> usize {
self.window_samples
}
pub fn reset(&mut self) {
unsafe {
ft8_decoder_reset(self.inner.as_ptr());
}
}
pub fn process_block(&mut self, block: &[f32]) {
if block.len() < self.block_size {
return;
}
unsafe {
ft8_decoder_process(self.inner.as_ptr(), block.as_ptr());
}
}
pub fn decode_if_ready(&mut self, max_results: usize) -> Vec<Ft8DecodeResult> {
unsafe {
if ft8_decoder_is_ready(self.inner.as_ptr()) == 0 {
return Vec::new();
}
let mut raw = vec![
Ft8DecodeResultRaw {
text: [0; FTX_MAX_MESSAGE_LENGTH],
snr_db: 0.0,
dt_s: 0.0,
freq_hz: 0.0,
};
max_results
];
let count =
ft8_decoder_decode(self.inner.as_ptr(), raw.as_mut_ptr(), max_results as c_int);
let count = count.max(0) as usize;
let mut out = Vec::with_capacity(count);
for item in raw.into_iter().take(count) {
let text = CStr::from_ptr(item.text.as_ptr())
.to_string_lossy()
.to_string();
out.push(Ft8DecodeResult {
text,
snr_db: item.snr_db,
dt_s: item.dt_s,
freq_hz: item.freq_hz,
});
}
out
}
}
}
impl Drop for Ft8Decoder {
fn drop(&mut self) {
unsafe {
ft8_decoder_free(self.inner.as_ptr());
}
}
}
#[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);
assert_eq!(ft2.window_samples(), 45_000);
}
}
+1 -1
View File
@@ -37,7 +37,7 @@ trx-core = { path = "../trx-core" }
trx-aprs = { path = "../decoders/trx-aprs" } trx-aprs = { path = "../decoders/trx-aprs" }
trx-cw = { path = "../decoders/trx-cw" } trx-cw = { path = "../decoders/trx-cw" }
trx-decode-log = { path = "../decoders/trx-decode-log" } trx-decode-log = { path = "../decoders/trx-decode-log" }
trx-ft8 = { path = "../decoders/trx-ft8" } trx-ftx = { path = "../decoders/trx-ftx" }
trx-wspr = { path = "../decoders/trx-wspr" } trx-wspr = { path = "../decoders/trx-wspr" }
trx-protocol = { path = "../trx-protocol" } trx-protocol = { path = "../trx-protocol" }
trx-reporting = { path = "../trx-reporting" } trx-reporting = { path = "../trx-reporting" }
+2 -2
View File
@@ -36,7 +36,7 @@ use trx_core::decode::{
use trx_core::rig::state::{RigMode, RigState}; use trx_core::rig::state::{RigMode, RigState};
use trx_core::vchan::SharedVChanManager; use trx_core::vchan::SharedVChanManager;
use trx_cw::CwDecoder; use trx_cw::CwDecoder;
use trx_ft8::Ft8Decoder; use trx_ftx::Ft8Decoder;
use trx_vdes::VdesDecoder; use trx_vdes::VdesDecoder;
use trx_wspr::WsprDecoder; use trx_wspr::WsprDecoder;
use uuid::Uuid; use uuid::Uuid;
@@ -92,7 +92,7 @@ fn decode_ft2_window(
decoder: &mut Ft8Decoder, decoder: &mut Ft8Decoder,
samples: &[f32], samples: &[f32],
max_results: usize, max_results: usize,
) -> Vec<trx_ft8::Ft8DecodeResult> { ) -> Vec<trx_ftx::Ft8DecodeResult> {
let window_samples = decoder.window_samples(); let window_samples = decoder.window_samples();
if samples.len() < window_samples { if samples.len() < window_samples {
return Vec::new(); return Vec::new();