From 5dabe321067e3ae2e1f6dea73d9fc901e8c24d1c Mon Sep 17 00:00:00 2001 From: Stan Grams Date: Wed, 18 Mar 2026 22:22:20 +0100 Subject: [PATCH] [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 Signed-off-by: Stan Grams --- Cargo.lock | 10 +- Cargo.toml | 1 - src/decoders/trx-ft8/Cargo.toml | 14 - src/decoders/trx-ft8/build.rs | 54 - src/decoders/trx-ft8/src/ft2_ldpc.c | 821 ----------- src/decoders/trx-ft8/src/ft2_ldpc.h | 20 - src/decoders/trx-ft8/src/ft8_wrapper.c | 1799 ------------------------ src/decoders/trx-ft8/src/lib.rs | 208 --- src/trx-server/Cargo.toml | 2 +- src/trx-server/src/audio.rs | 4 +- 10 files changed, 4 insertions(+), 2929 deletions(-) delete mode 100644 src/decoders/trx-ft8/Cargo.toml delete mode 100644 src/decoders/trx-ft8/build.rs delete mode 100644 src/decoders/trx-ft8/src/ft2_ldpc.c delete mode 100644 src/decoders/trx-ft8/src/ft2_ldpc.h delete mode 100644 src/decoders/trx-ft8/src/ft8_wrapper.c delete mode 100644 src/decoders/trx-ft8/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 877f3d7..fc851b4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2642,14 +2642,6 @@ dependencies = [ "trx-protocol", ] -[[package]] -name = "trx-ft8" -version = "0.1.0" -dependencies = [ - "cc", - "libc", -] - [[package]] name = "trx-ftx" version = "0.1.0" @@ -2712,7 +2704,7 @@ dependencies = [ "trx-core", "trx-cw", "trx-decode-log", - "trx-ft8", + "trx-ftx", "trx-protocol", "trx-reporting", "trx-vdes", diff --git a/Cargo.toml b/Cargo.toml index d4869eb..f32f83c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,7 +8,6 @@ members = [ "src/decoders/trx-aprs", "src/decoders/trx-cw", "src/decoders/trx-decode-log", - "src/decoders/trx-ft8", "src/decoders/trx-ftx", "src/decoders/trx-rds", "src/decoders/trx-vdes", diff --git a/src/decoders/trx-ft8/Cargo.toml b/src/decoders/trx-ft8/Cargo.toml deleted file mode 100644 index e692937..0000000 --- a/src/decoders/trx-ft8/Cargo.toml +++ /dev/null @@ -1,14 +0,0 @@ -# SPDX-FileCopyrightText: 2026 Stanislaw Grams -# -# SPDX-License-Identifier: BSD-2-Clause - -[package] -name = "trx-ft8" -version.workspace = true -edition = "2021" - -[dependencies] -libc = "0.2" - -[build-dependencies] -cc = "1" diff --git a/src/decoders/trx-ft8/build.rs b/src/decoders/trx-ft8/build.rs deleted file mode 100644 index ec352b0..0000000 --- a/src/decoders/trx-ft8/build.rs +++ /dev/null @@ -1,54 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Stanislaw Grams -// -// 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"); -} diff --git a/src/decoders/trx-ft8/src/ft2_ldpc.c b/src/decoders/trx-ft8/src/ft2_ldpc.c deleted file mode 100644 index 58d671d..0000000 --- a/src/decoders/trx-ft8/src/ft2_ldpc.c +++ /dev/null @@ -1,821 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -#include "ft2_ldpc.h" - -#include -#include - -#include -#include -#include -#include - -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; -} diff --git a/src/decoders/trx-ft8/src/ft2_ldpc.h b/src/decoders/trx-ft8/src/ft2_ldpc.h deleted file mode 100644 index a6f5759..0000000 --- a/src/decoders/trx-ft8/src/ft2_ldpc.h +++ /dev/null @@ -1,20 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -#ifndef TRX_FT2_LDPC_H -#define TRX_FT2_LDPC_H - -#include - -#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 diff --git a/src/decoders/trx-ft8/src/ft8_wrapper.c b/src/decoders/trx-ft8/src/ft8_wrapper.c deleted file mode 100644 index 9eb2eed..0000000 --- a/src/decoders/trx-ft8/src/ft8_wrapper.c +++ /dev/null @@ -1,1799 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Stanislaw Grams -// -// SPDX-License-Identifier: BSD-2-Clause - -#include -#include -#include -#include -#include -#include -#define LOG_LEVEL LOG_INFO -#include -#include -#include -#include - -#include -#include -#include -#include -#include - -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 - -typedef struct -{ - uint32_t hash; - char callsign[12]; -} callsign_hashtable_entry_t; - -static callsign_hashtable_entry_t callsign_hashtable[CALLSIGN_HASHTABLE_SIZE]; -static int callsign_hashtable_size = 0; - -static void hashtable_init(void) -{ - callsign_hashtable_size = 0; - memset(callsign_hashtable, 0, sizeof(callsign_hashtable)); -} - -static void hashtable_cleanup(uint8_t max_age) -{ - for (int idx_hash = 0; idx_hash < CALLSIGN_HASHTABLE_SIZE; ++idx_hash) - { - if (callsign_hashtable[idx_hash].callsign[0] != '\0') - { - uint8_t age = (uint8_t)(callsign_hashtable[idx_hash].hash >> 24); - if (age >= max_age) - { - callsign_hashtable[idx_hash].callsign[0] = '\0'; - callsign_hashtable[idx_hash].hash = 0; - callsign_hashtable_size--; - } - else - { - callsign_hashtable[idx_hash].hash = (((uint32_t)age + 1u) << 24) | (callsign_hashtable[idx_hash].hash & 0x3FFFFFu); - } - } - } -} - -static void hashtable_add(const char* callsign, uint32_t hash) -{ - int idx_hash = hash % CALLSIGN_HASHTABLE_SIZE; - while (callsign_hashtable[idx_hash].callsign[0] != '\0') - { - if (((callsign_hashtable[idx_hash].hash & 0x3FFFFFu) == hash) && (0 == strcmp(callsign_hashtable[idx_hash].callsign, callsign))) - { - callsign_hashtable[idx_hash].hash &= 0x3FFFFFu; - return; - } - idx_hash = (idx_hash + 1) % CALLSIGN_HASHTABLE_SIZE; - } - callsign_hashtable_size++; - strncpy(callsign_hashtable[idx_hash].callsign, callsign, 11); - callsign_hashtable[idx_hash].callsign[11] = '\0'; - callsign_hashtable[idx_hash].hash = hash; -} - -static bool hashtable_lookup(ftx_callsign_hash_type_t hash_type, uint32_t hash, char* callsign) -{ - int hash_shift = (hash_type == FTX_CALLSIGN_HASH_22_BITS) ? 0 : (hash_type == FTX_CALLSIGN_HASH_12_BITS) ? 10 : 12; - uint32_t mask = (hash_type == FTX_CALLSIGN_HASH_22_BITS) ? 0x3FFFFFu : (hash_type == FTX_CALLSIGN_HASH_12_BITS) ? 0xFFFu : 0x3FFu; - - int idx_hash = hash % CALLSIGN_HASHTABLE_SIZE; - while (callsign_hashtable[idx_hash].callsign[0] != '\0') - { - if (((callsign_hashtable[idx_hash].hash & 0x3FFFFFu) >> hash_shift) == (hash & mask)) - { - strcpy(callsign, callsign_hashtable[idx_hash].callsign); - return true; - } - idx_hash = (idx_hash + 1) % CALLSIGN_HASHTABLE_SIZE; - } - callsign[0] = '\0'; - return false; -} - -static ftx_callsign_hash_interface_t hash_if = { - .lookup_hash = hashtable_lookup, - .save_hash = hashtable_add, -}; - -static bool ft2_unpack_message(const uint8_t plain174[], ftx_message_t* message); - -// Decoder wrapper - -typedef struct -{ - monitor_t mon; - monitor_config_t cfg; - float* ft2_raw; - int ft2_raw_capacity; - int ft2_raw_len; -} ft8_decoder_t; - -typedef struct -{ - char text[FTX_MAX_MESSAGE_LENGTH]; - float snr_db; - float dt_s; - float freq_hz; -} ft8_decode_result_t; - -static int decode_from_waterfall_candidates( - const ft8_decoder_t* dec, - ft8_decode_result_t* out, - int max_results); - -static float ft2_frequency_offset_hz(void) -{ - return -1.5f / FT2_SYMBOL_PERIOD; -} - -static float decoder_candidate_freq_hz(const ft8_decoder_t* dec, const ftx_candidate_t* cand) -{ - const ftx_waterfall_t* wf = &dec->mon.wf; - float freq_hz = (dec->mon.min_bin + cand->freq_offset + (float)cand->freq_sub / wf->freq_osr) / dec->mon.symbol_period; - if (dec->cfg.protocol == FTX_PROTOCOL_FT2) - { - freq_hz += ft2_frequency_offset_hz(); - } - return freq_hz; -} - -static float decoder_candidate_dt_s(const ft8_decoder_t* dec, const ftx_candidate_t* cand) -{ - const ftx_waterfall_t* wf = &dec->mon.wf; - float time_sec = (cand->time_offset + (float)cand->time_sub / wf->time_osr) * dec->mon.symbol_period; - if (dec->cfg.protocol == FTX_PROTOCOL_FT2) - { - time_sec -= 0.5f; - } - return time_sec; -} - -#define FT2_NDOWN 9 -#define FT2_NFFT1 1152 -#define FT2_NH1 (FT2_NFFT1 / 2) -#define FT2_NSTEP 288 -#define FT2_NMAX 45000 -#define FT2_MAX_RAW_CANDIDATES 96 -#define FT2_MAX_SCAN_HITS 128 -#define FT2_SYNC_TWEAK_MIN (-16) -#define FT2_SYNC_TWEAK_MAX (16) -#define FT2_NSS (FT2_NSTEP / FT2_NDOWN) -#define FT2_FRAME_SYMBOLS (FT2_NN - FT2_NR) -#define FT2_FRAME_SAMPLES (FT2_FRAME_SYMBOLS * FT2_NSS) - -typedef struct -{ - float freq_hz; - float score; -} ft2_raw_candidate_t; - -typedef struct -{ - int index; - float reliability; -} ft2_reliability_t; - -typedef struct -{ - int peaks_found; - int hits_found; - float best_peak_score; - float best_sync_score; -} ft2_scan_stats_t; - -typedef struct -{ - int ntype[5]; - int nharderror[5]; - float dmin[5]; -} ft2_pass_diag_t; - -typedef struct -{ - int nraw; - int nfft2; - float df; - float* window; - kiss_fft_cpx* spectrum; - kiss_fft_cpx* band; - kiss_fft_cfg ifft_cfg; - void* ifft_mem; -} ft2_downsample_ctx_t; - -typedef enum -{ - FT2_FAIL_NONE = 0, - FT2_FAIL_REFINED_SYNC, - FT2_FAIL_FREQ_RANGE, - FT2_FAIL_FINAL_DOWNSAMPLE, - FT2_FAIL_BITMETRICS, - FT2_FAIL_SYNC_QUAL, - FT2_FAIL_LDPC, - FT2_FAIL_CRC, - FT2_FAIL_UNPACK -} ft2_fail_stage_t; - -typedef struct -{ - float freq_hz; - float snr0; - float sync_score; - int start; - int idf; -} ft2_scan_hit_t; - -static void ft2_nuttall_window(float* window, int n) -{ - const float a0 = 0.355768f; - const float a1 = 0.487396f; - const float a2 = 0.144232f; - const float a3 = 0.012604f; - for (int i = 0; i < n; ++i) - { - float phase = (2.0f * (float)M_PI * i) / (float)(n - 1); - window[i] = a0 - a1 * cosf(phase) + a2 * cosf(2.0f * phase) - a3 * cosf(3.0f * phase); - } -} - -static int ft2_cmp_candidates_desc(const void* lhs, const void* rhs) -{ - const ft2_raw_candidate_t* a = (const ft2_raw_candidate_t*)lhs; - const ft2_raw_candidate_t* b = (const ft2_raw_candidate_t*)rhs; - if (a->score < b->score) - return 1; - if (a->score > b->score) - return -1; - return 0; -} - -static int ft2_cmp_reliability_asc(const void* lhs, const void* rhs) -{ - const ft2_reliability_t* a = (const ft2_reliability_t*)lhs; - const ft2_reliability_t* b = (const ft2_reliability_t*)rhs; - if (a->reliability < b->reliability) - return -1; - if (a->reliability > b->reliability) - return 1; - return 0; -} - -static int ft2_cmp_scan_hits_desc(const void* lhs, const void* rhs) -{ - const ft2_scan_hit_t* a = (const ft2_scan_hit_t*)lhs; - const ft2_scan_hit_t* b = (const ft2_scan_hit_t*)rhs; - if (a->sync_score < b->sync_score) - return 1; - if (a->sync_score > b->sync_score) - return -1; - return 0; -} - -static int ft2_find_frequency_peaks( - const ft8_decoder_t* dec, - ft2_raw_candidate_t* candidates, - int max_candidates) -{ - if (!dec->ft2_raw || dec->ft2_raw_len < FT2_NFFT1) - return 0; - - const float fs = (float)dec->cfg.sample_rate; - const float df = fs / FT2_NFFT1; - const int n_frames = 1 + (dec->ft2_raw_len - FT2_NFFT1) / FT2_NSTEP; - float* avg = (float*)calloc(FT2_NH1, sizeof(float)); - float* smooth = (float*)calloc(FT2_NH1, sizeof(float)); - float* baseline = (float*)calloc(FT2_NH1, sizeof(float)); - float* window = (float*)malloc(sizeof(float) * FT2_NFFT1); - kiss_fft_scalar* timebuf = (kiss_fft_scalar*)malloc(sizeof(kiss_fft_scalar) * FT2_NFFT1); - kiss_fft_cpx* freqbuf = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * (FT2_NH1 + 1)); - if (!avg || !smooth || !baseline || !window || !timebuf || !freqbuf) - { - free(avg); - free(smooth); - free(baseline); - free(window); - free(timebuf); - free(freqbuf); - return 0; - } - - ft2_nuttall_window(window, FT2_NFFT1); - size_t fft_mem_len = 0; - kiss_fftr_alloc(FT2_NFFT1, 0, NULL, &fft_mem_len); - void* fft_mem = malloc(fft_mem_len); - kiss_fftr_cfg fft_cfg = kiss_fftr_alloc(FT2_NFFT1, 0, fft_mem, &fft_mem_len); - if (!fft_cfg) - { - free(avg); - free(smooth); - free(baseline); - free(window); - free(timebuf); - free(freqbuf); - free(fft_mem); - return 0; - } - - for (int frame = 0; frame < n_frames; ++frame) - { - int start = frame * FT2_NSTEP; - for (int i = 0; i < FT2_NFFT1; ++i) - { - timebuf[i] = dec->ft2_raw[start + i] * window[i]; - } - kiss_fftr(fft_cfg, timebuf, freqbuf); - for (int bin = 1; bin < FT2_NH1; ++bin) - { - float power = freqbuf[bin].r * freqbuf[bin].r + freqbuf[bin].i * freqbuf[bin].i; - avg[bin] += power; - } - } - - for (int bin = 1; bin < FT2_NH1; ++bin) - { - avg[bin] /= (float)n_frames; - } - for (int bin = 8; bin < FT2_NH1 - 8; ++bin) - { - float sum = 0.0f; - for (int i = bin - 7; i <= bin + 7; ++i) - sum += avg[i]; - smooth[bin] = sum / 15.0f; - } - for (int bin = 32; bin < FT2_NH1 - 32; ++bin) - { - float sum = 0.0f; - for (int i = bin - 31; i <= bin + 31; ++i) - sum += smooth[i]; - baseline[bin] = (sum / 63.0f) + 1e-9f; - } - - const int min_bin = (int)lroundf(200.0f / df); - const int max_bin = (int)lroundf(4910.0f / df); - int count = 0; - for (int bin = min_bin + 1; bin < max_bin - 1 && count < max_candidates; ++bin) - { - if (baseline[bin] <= 0.0f) - continue; - float value = smooth[bin] / baseline[bin]; - if (value < 1.03f) - continue; - if (!(value >= (smooth[bin - 1] / fmaxf(baseline[bin - 1], 1e-9f)) && - value >= (smooth[bin + 1] / fmaxf(baseline[bin + 1], 1e-9f)))) - continue; - - float left = smooth[bin - 1] / fmaxf(baseline[bin - 1], 1e-9f); - float right = smooth[bin + 1] / fmaxf(baseline[bin + 1], 1e-9f); - float den = left - 2.0f * value + right; - float delta = (fabsf(den) > 1e-6f) ? (0.5f * (left - right) / den) : 0.0f; - float freq_hz = (bin + delta) * df + ft2_frequency_offset_hz(); - if (freq_hz < 200.0f || freq_hz > 4910.0f) - continue; - candidates[count].freq_hz = freq_hz; - candidates[count].score = value; - ++count; - } - - qsort(candidates, count, sizeof(candidates[0]), ft2_cmp_candidates_desc); - - free(avg); - free(smooth); - free(baseline); - free(window); - free(timebuf); - free(freqbuf); - free(fft_mem); - return count; -} - -static void ft2_prepare_sync_waveforms(float complex sync_wave[4][64], float complex tweak_wave[33][64]) -{ - const float fs_down = 12000.0f / FT2_NDOWN; - const float nss = FT2_SYMBOL_PERIOD * fs_down; - for (int group = 0; group < 4; ++group) - { - int idx = 0; - float phase = 0.0f; - for (int tone_idx = 0; tone_idx < 4; ++tone_idx) - { - int tone = kFT4_Costas_pattern[group][tone_idx]; - float dphase = 4.0f * (float)M_PI * tone / nss; - for (int step = 0; step < (int)(nss / 2.0f); ++step) - { - sync_wave[group][idx++] = cexpf(I * phase); - phase = fmodf(phase + dphase, 2.0f * (float)M_PI); - } - } - } - - for (int idf = FT2_SYNC_TWEAK_MIN; idf <= FT2_SYNC_TWEAK_MAX; ++idf) - { - for (int n = 0; n < 64; ++n) - { - float phase = 4.0f * (float)M_PI * idf * n / fs_down; - tweak_wave[idf - FT2_SYNC_TWEAK_MIN][n] = cexpf(I * phase); - } - } -} - -static int ft2_downsample_candidate( - const ft2_downsample_ctx_t* ctx, - float freq_hz, - float complex* out_samples, - int out_len) -{ - if (!ctx || !ctx->spectrum || !ctx->window || !ctx->band || !ctx->ifft_cfg) - return 0; - - const int nraw = ctx->nraw; - const int nfft2 = ctx->nfft2; - if (nraw <= 0 || out_len < nfft2) - return 0; - - memset(ctx->band, 0, sizeof(kiss_fft_cpx) * nfft2); - const int i0 = (int)lroundf(freq_hz / ctx->df); - if (i0 >= 0 && i0 <= nraw / 2) - ctx->band[0] = ctx->spectrum[i0]; - for (int i = 1; i <= nfft2 / 2; ++i) - { - if ((i0 + i) >= 0 && (i0 + i) <= nraw / 2) - ctx->band[i] = ctx->spectrum[i0 + i]; - if ((i0 - i) >= 0 && (i0 - i) <= nraw / 2) - ctx->band[nfft2 - i] = ctx->spectrum[i0 - i]; - } - - for (int i = 0; i < nfft2; ++i) - { - ctx->band[i].r = ctx->band[i].r * ctx->window[i] / nfft2; - ctx->band[i].i = ctx->band[i].i * ctx->window[i] / nfft2; - } - kiss_fft(ctx->ifft_cfg, ctx->band, ctx->band); - for (int i = 0; i < nfft2; ++i) - out_samples[i] = ctx->band[i].r + I * ctx->band[i].i; - - return nfft2; -} - -static void ft2_downsample_ctx_free(ft2_downsample_ctx_t* ctx) -{ - if (!ctx) - return; - free(ctx->window); - free(ctx->spectrum); - free(ctx->band); - free(ctx->ifft_mem); - memset(ctx, 0, sizeof(*ctx)); -} - -static bool ft2_downsample_ctx_init(const ft8_decoder_t* dec, ft2_downsample_ctx_t* ctx) -{ - if (!dec || !ctx || !dec->ft2_raw) - return false; - - memset(ctx, 0, sizeof(*ctx)); - ctx->nraw = dec->ft2_raw_len; - ctx->nfft2 = ctx->nraw / FT2_NDOWN; - if (ctx->nraw <= 0 || ctx->nfft2 <= 0) - return false; - - ctx->df = (float)dec->cfg.sample_rate / ctx->nraw; - ctx->window = (float*)calloc(ctx->nfft2, sizeof(float)); - ctx->spectrum = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * (ctx->nraw / 2 + 1)); - ctx->band = (kiss_fft_cpx*)malloc(sizeof(kiss_fft_cpx) * ctx->nfft2); - if (!ctx->window || !ctx->spectrum || !ctx->band) - { - ft2_downsample_ctx_free(ctx); - return false; - } - - const float baud = 1.0f / FT2_SYMBOL_PERIOD; - const int iwt = (int)((0.5f * baud) / ctx->df); - const int iwf = (int)((4.0f * baud) / ctx->df); - const int iws = (int)(baud / ctx->df); - if (iwt <= 0) - { - ft2_downsample_ctx_free(ctx); - return false; - } - for (int i = 0; i < iwt && i < ctx->nfft2; ++i) - ctx->window[i] = 0.5f * (1.0f + cosf((float)M_PI * (float)(iwt - 1 - i) / (float)iwt)); - for (int i = iwt; i < iwt + iwf && i < ctx->nfft2; ++i) - ctx->window[i] = 1.0f; - for (int i = iwt + iwf; i < 2 * iwt + iwf && i < ctx->nfft2; ++i) - ctx->window[i] = 0.5f * (1.0f + cosf((float)M_PI * (float)(i - (iwt + iwf)) / (float)iwt)); - if (iws > 0) - { - float* shifted = (float*)calloc(ctx->nfft2, sizeof(float)); - if (!shifted) - { - ft2_downsample_ctx_free(ctx); - return false; - } - for (int i = 0; i < ctx->nfft2; ++i) - shifted[i] = ctx->window[(i + iws) % ctx->nfft2]; - memcpy(ctx->window, shifted, sizeof(float) * ctx->nfft2); - free(shifted); - } - - kiss_fft_scalar* timedata = (kiss_fft_scalar*)malloc(sizeof(kiss_fft_scalar) * ctx->nraw); - if (!timedata) - { - ft2_downsample_ctx_free(ctx); - return false; - } - - size_t rfft_mem_len = 0; - kiss_fftr_alloc(ctx->nraw, 0, NULL, &rfft_mem_len); - void* rfft_mem = malloc(rfft_mem_len); - kiss_fftr_cfg rfft_cfg = kiss_fftr_alloc(ctx->nraw, 0, rfft_mem, &rfft_mem_len); - if (!rfft_cfg) - { - free(timedata); - free(rfft_mem); - ft2_downsample_ctx_free(ctx); - return false; - } - for (int i = 0; i < ctx->nraw; ++i) - timedata[i] = dec->ft2_raw[i]; - kiss_fftr(rfft_cfg, timedata, ctx->spectrum); - free(timedata); - free(rfft_mem); - - size_t ifft_mem_len = 0; - kiss_fft_alloc(ctx->nfft2, 1, NULL, &ifft_mem_len); - ctx->ifft_mem = malloc(ifft_mem_len); - ctx->ifft_cfg = kiss_fft_alloc(ctx->nfft2, 1, ctx->ifft_mem, &ifft_mem_len); - if (!ctx->ifft_cfg) - { - ft2_downsample_ctx_free(ctx); - return false; - } - - return true; -} - -static float ft2_sync2d_score( - const float complex* samples, - int n_samples, - int start, - int idf, - const float complex sync_wave[4][64], - const float complex tweak_wave[33][64]) -{ - const int nss = FT2_NSS; - const int positions[4] = { - start, - start + 33 * nss, - start + 66 * nss, - start + 99 * nss, - }; - float score = 0.0f; - const float complex* tweak = tweak_wave[idf - FT2_SYNC_TWEAK_MIN]; - - for (int group = 0; group < 4; ++group) - { - int pos = positions[group]; - float complex sum = 0.0f; - int usable = 0; - for (int i = 0; i < 64; ++i) - { - int sample_idx = pos + 2 * i; - if (sample_idx < 0 || sample_idx >= n_samples) - continue; - sum += samples[sample_idx] * conjf(sync_wave[group][i] * tweak[i]); - ++usable; - } - if (usable > 16) - score += cabsf(sum) / (2.0f * nss); - } - return score; -} - -static void ft2_normalize_downsampled(float complex* samples, int n_samples, int ref_count) -{ - float power = 0.0f; - for (int i = 0; i < n_samples; ++i) - { - power += crealf(samples[i] * conjf(samples[i])); - } - if (power <= 0.0f) - return; - if (ref_count <= 0) - ref_count = n_samples; - float scale = sqrtf((float)ref_count / power); - for (int i = 0; i < n_samples; ++i) - { - samples[i] *= scale; - } -} - -static int ft2_find_scan_hits( - const ft8_decoder_t* dec, - const ft2_downsample_ctx_t* downsample_ctx, - ft2_scan_hit_t* out, - int max_hits, - ft2_scan_stats_t* stats) -{ - if (!downsample_ctx) - return 0; - - ft2_raw_candidate_t peaks[FT2_MAX_RAW_CANDIDATES]; - int n_peaks = ft2_find_frequency_peaks(dec, peaks, FT2_MAX_RAW_CANDIDATES); - if (stats) - { - stats->peaks_found = n_peaks; - stats->hits_found = 0; - stats->best_peak_score = 0.0f; - stats->best_sync_score = 0.0f; - for (int i = 0; i < n_peaks; ++i) - { - if (peaks[i].score > stats->best_peak_score) - stats->best_peak_score = peaks[i].score; - } - } - if (n_peaks <= 0) - return 0; - - const int nfft2 = downsample_ctx ? downsample_ctx->nfft2 : 0; - float complex* down = (float complex*)malloc(sizeof(float complex) * nfft2); - float complex sync_wave[4][64]; - float complex tweak_wave[33][64]; - ft2_prepare_sync_waveforms(sync_wave, tweak_wave); - - int count = 0; - for (int peak = 0; peak < n_peaks && count < max_hits; ++peak) - { - int produced = ft2_downsample_candidate(downsample_ctx, peaks[peak].freq_hz, down, nfft2); - if (produced <= 0) - continue; - ft2_normalize_downsampled(down, produced, produced); - - float best_score = -1.0f; - int best_start = 0; - int best_idf = 0; - for (int idf = -12; idf <= 12; idf += 3) - { - for (int start = -688; start <= 2024; start += 4) - { - float score = ft2_sync2d_score(down, produced, start, idf, sync_wave, tweak_wave); - if (score > best_score) - { - best_score = score; - best_start = start; - best_idf = idf; - } - } - } - if (best_score < 0.50f) - continue; - - for (int idf = best_idf - 4; idf <= best_idf + 4; ++idf) - { - if (idf < FT2_SYNC_TWEAK_MIN || idf > FT2_SYNC_TWEAK_MAX) - continue; - for (int start = best_start - 5; start <= best_start + 5; ++start) - { - float score = ft2_sync2d_score(down, produced, start, idf, sync_wave, tweak_wave); - if (score > best_score) - { - best_score = score; - best_start = start; - best_idf = idf; - } - } - } - if (best_score < 0.50f) - continue; - - out[count].freq_hz = peaks[peak].freq_hz; - out[count].snr0 = peaks[peak].score - 1.0f; - out[count].sync_score = best_score; - out[count].start = best_start; - out[count].idf = best_idf; - if (stats && best_score > stats->best_sync_score) - stats->best_sync_score = best_score; - ++count; - } - - qsort(out, count, sizeof(out[0]), ft2_cmp_scan_hits_desc); - if (stats) - stats->hits_found = count; - free(down); - return count; -} - -static void ft2_extract_signal_region(const float complex* input, int input_len, int start, float complex* out_signal) -{ - for (int i = 0; i < FT2_FRAME_SAMPLES; ++i) - { - int src = start + i; - out_signal[i] = (src >= 0 && src < input_len) ? input[src] : 0.0f; - } -} - -static void ft2_normalize_metric(float* metric, int count) -{ - float sum = 0.0f; - float sum2 = 0.0f; - for (int i = 0; i < count; ++i) - { - sum += metric[i]; - sum2 += metric[i] * metric[i]; - } - float mean = sum / count; - float variance = (sum2 / count) - (mean * mean); - float sigma = (variance > 0.0f) ? sqrtf(variance) : sqrtf(fmaxf(sum2 / count, 0.0f)); - if (sigma <= 1.0e-6f) - return; - for (int i = 0; i < count; ++i) - metric[i] /= sigma; -} - -static void ft2_normalize_log174(float* log174) -{ - float sum = 0.0f; - float sum2 = 0.0f; - for (int i = 0; i < FTX_LDPC_N; ++i) - { - sum += log174[i]; - sum2 += log174[i] * log174[i]; - } - float inv_n = 1.0f / FTX_LDPC_N; - float variance = (sum2 - (sum * sum * inv_n)) * inv_n; - if (variance <= 1.0e-12f) - return; - - float sigma = sqrtf(variance); - for (int i = 0; i < FTX_LDPC_N; ++i) - log174[i] /= sigma; -} - -static bool ft2_extract_bitmetrics_raw(const float complex* signal, float bitmetrics[2 * FT2_FRAME_SYMBOLS][3]) -{ - float complex symbols[4][FT2_FRAME_SYMBOLS]; - float s4[4][FT2_FRAME_SYMBOLS]; - memset(bitmetrics, 0, sizeof(float) * 2 * FT2_FRAME_SYMBOLS * 3); - - size_t fft_mem_len = 0; - kiss_fft_alloc(FT2_NSS, 0, NULL, &fft_mem_len); - void* fft_mem = malloc(fft_mem_len); - kiss_fft_cfg fft_cfg = kiss_fft_alloc(FT2_NSS, 0, fft_mem, &fft_mem_len); - if (!fft_cfg) - { - free(fft_mem); - return false; - } - - for (int sym = 0; sym < FT2_FRAME_SYMBOLS; ++sym) - { - kiss_fft_cpx csymb[FT2_NSS]; - for (int i = 0; i < FT2_NSS; ++i) - { - float complex sample = signal[sym * FT2_NSS + i]; - csymb[i].r = crealf(sample); - csymb[i].i = cimagf(sample); - } - kiss_fft(fft_cfg, csymb, csymb); - for (int tone = 0; tone < 4; ++tone) - { - float complex bin = csymb[tone].r + I * csymb[tone].i; - symbols[tone][sym] = bin; - s4[tone][sym] = cabsf(bin); - } - } - - free(fft_mem); - - static bool one_ready = false; - static uint8_t one_mask[256][8]; - if (!one_ready) - { - for (int i = 0; i < 256; ++i) - { - for (int j = 0; j < 8; ++j) - one_mask[i][j] = ((i & (1 << j)) != 0) ? 1u : 0u; - } - one_ready = true; - } - - int sync_ok = 0; - for (int group = 0; group < 4; ++group) - { - int base = group * 33; - for (int i = 0; i < 4; ++i) - { - int best = 0; - for (int tone = 1; tone < 4; ++tone) - { - if (s4[tone][base + i] > s4[best][base + i]) - best = tone; - } - if (best == kFT4_Costas_pattern[group][i]) - ++sync_ok; - } - } - if (sync_ok < 4) - return false; - - float metric1[2 * FT2_FRAME_SYMBOLS] = { 0 }; - float metric2[2 * FT2_FRAME_SYMBOLS] = { 0 }; - float metric4[2 * FT2_FRAME_SYMBOLS] = { 0 }; - float s2[256]; - - for (int nseq = 0; nseq < 3; ++nseq) - { - int nsym = (nseq == 0) ? 1 : (nseq == 1 ? 2 : 4); - int nt = 1 << (2 * nsym); - for (int ks = 0; ks <= FT2_FRAME_SYMBOLS - nsym; ks += nsym) - { - for (int i = 0; i < nt; ++i) - { - int i1 = i / 64; - int i2 = (i & 63) / 16; - int i3 = (i & 15) / 4; - int i4 = i & 3; - if (nsym == 1) - { - s2[i] = cabsf(symbols[kFT4_Gray_map[i4]][ks]); - } - else if (nsym == 2) - { - s2[i] = cabsf(symbols[kFT4_Gray_map[i3]][ks] + symbols[kFT4_Gray_map[i4]][ks + 1]); - } - else - { - s2[i] = cabsf( - symbols[kFT4_Gray_map[i1]][ks] + - symbols[kFT4_Gray_map[i2]][ks + 1] + - symbols[kFT4_Gray_map[i3]][ks + 2] + - symbols[kFT4_Gray_map[i4]][ks + 3]); - } - } - - int ipt = 2 * ks; - int ibmax = (nsym == 1) ? 1 : (nsym == 2 ? 3 : 7); - for (int ib = 0; ib <= ibmax; ++ib) - { - float max_one = -INFINITY; - float max_zero = -INFINITY; - for (int i = 0; i < nt; ++i) - { - if (one_mask[i][ibmax - ib]) - { - if (s2[i] > max_one) - max_one = s2[i]; - } - else if (s2[i] > max_zero) - { - max_zero = s2[i]; - } - } - if ((ipt + ib) >= 2 * FT2_FRAME_SYMBOLS) - continue; - if (nseq == 0) - metric1[ipt + ib] = max_one - max_zero; - else if (nseq == 1) - metric2[ipt + ib] = max_one - max_zero; - else - metric4[ipt + ib] = max_one - max_zero; - } - } - } - - metric2[204] = metric1[204]; - metric2[205] = metric1[205]; - metric4[200] = metric2[200]; - metric4[201] = metric2[201]; - metric4[202] = metric2[202]; - metric4[203] = metric2[203]; - metric4[204] = metric1[204]; - metric4[205] = metric1[205]; - - ft2_normalize_metric(metric1, 2 * FT2_FRAME_SYMBOLS); - ft2_normalize_metric(metric2, 2 * FT2_FRAME_SYMBOLS); - ft2_normalize_metric(metric4, 2 * FT2_FRAME_SYMBOLS); - - for (int i = 0; i < 2 * FT2_FRAME_SYMBOLS; ++i) - { - bitmetrics[i][0] = metric1[i]; - bitmetrics[i][1] = metric2[i]; - bitmetrics[i][2] = metric4[i]; - } - return true; -} - -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; -} - -// OSD-1 / OSD-2 trial decoder. -// Tries flipping each of the K least-reliable bits (OSD-1), and all pairs when -// nharderror <= 4 (OSD-2). On success cw holds the corrected codeword. -// The caller must verify the message (CRC) separately. - -static void ft2_pack_bits(const uint8_t bit_array[], int num_bits, uint8_t packed[]) -{ - int num_bytes = (num_bits + 7) / 8; - for (int i = 0; i < num_bytes; ++i) - packed[i] = 0; - - uint8_t mask = 0x80; - int byte_idx = 0; - for (int i = 0; i < num_bits; ++i) - { - if (bit_array[i]) - packed[byte_idx] |= mask; - mask >>= 1; - if (!mask) - { - mask = 0x80; - ++byte_idx; - } - } -} - -static uint8_t ft2_parity8(uint8_t x) -{ - x ^= x >> 4; - x ^= x >> 2; - x ^= x >> 1; - return x & 1u; -} - -static void ft2_encode_codeword_from_a91(const uint8_t a91[FTX_LDPC_K_BYTES], uint8_t codeword[FTX_LDPC_N]) -{ - for (int i = 0; i < FTX_LDPC_K; ++i) - { - codeword[i] = (a91[i / 8] >> (7 - (i % 8))) & 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 ^= ft2_parity8(a91[j] & kFTX_LDPC_generator[i][j]); - } - codeword[FTX_LDPC_K + i] = nsum & 0x1u; - } -} - - -// Count how many of the 174 coded bits in a candidate codeword disagree -// with the received hard decisions (sign of LLRs). A legitimate OSD -// correction should disagree in very few positions; a false CRC match on -// noise will disagree in ~half of the 83 parity positions. -static int ft2_count_hard_errors_vs_llr(const float log174[FTX_LDPC_N], const uint8_t codeword[FTX_LDPC_N]) -{ - int errors = 0; - for (int i = 0; i < FTX_LDPC_N; ++i) - { - uint8_t received = (log174[i] >= 0.0f) ? 1 : 0; - if (received != codeword[i]) - ++errors; - } - return errors; -} - -// Maximum hard-error count for accepting an OSD result. -// The (174,91) code has minimum distance ~20, so a legitimate near-threshold -// decode should disagree in far fewer than 36 positions. Random CRC matches -// typically disagree in ~40-50 parity positions alone. -#define FT2_OSD_MAX_HARD_ERRORS 36 - -static bool ft2_try_crc_candidate(const uint8_t a91[FTX_LDPC_K_BYTES], - const float log174[FTX_LDPC_N], - ftx_message_t* message) -{ - uint8_t codeword[FTX_LDPC_N]; - ft2_encode_codeword_from_a91(a91, codeword); - if (!ft2_unpack_message(codeword, message)) - return false; - // Verify the codeword is consistent with what we actually received. - if (log174 && ft2_count_hard_errors_vs_llr(log174, codeword) > FT2_OSD_MAX_HARD_ERRORS) - return false; - return true; -} - -static bool ft2_osd_lite_decode(const float log174[FTX_LDPC_N], ftx_message_t* message) -{ - uint8_t base_a91[FTX_LDPC_K_BYTES]; - memset(base_a91, 0, sizeof(base_a91)); - for (int i = 0; i < FTX_LDPC_K; ++i) - { - if (log174[i] >= 0.0f) - base_a91[i / 8] |= (uint8_t)(0x80u >> (i % 8)); - } - - if (ft2_try_crc_candidate(base_a91, log174, message)) - return true; - - ft2_reliability_t reliabilities[FTX_LDPC_K]; - for (int i = 0; i < FTX_LDPC_K; ++i) - { - reliabilities[i].index = i; - reliabilities[i].reliability = fabsf(log174[i]); - } - qsort(reliabilities, FTX_LDPC_K, sizeof(reliabilities[0]), ft2_cmp_reliability_asc); - - const int max_candidates = 16; - const int n = (FTX_LDPC_K < max_candidates) ? FTX_LDPC_K : max_candidates; - uint8_t trial_a91[FTX_LDPC_K_BYTES]; - - // OSD-1: try flipping each of the n least-reliable systematic bits. - for (int i = 0; i < n; ++i) - { - memcpy(trial_a91, base_a91, sizeof(trial_a91)); - int b0 = reliabilities[i].index; - trial_a91[b0 / 8] ^= (uint8_t)(0x80u >> (b0 % 8)); - if (ft2_try_crc_candidate(trial_a91, log174, message)) - return true; - } - - // OSD-2: try all pairs from the n least-reliable systematic bits. - for (int i = 0; i < n; ++i) - { - for (int j = i + 1; j < n; ++j) - { - memcpy(trial_a91, base_a91, sizeof(trial_a91)); - int b0 = reliabilities[i].index; - int b1 = reliabilities[j].index; - trial_a91[b0 / 8] ^= (uint8_t)(0x80u >> (b0 % 8)); - trial_a91[b1 / 8] ^= (uint8_t)(0x80u >> (b1 % 8)); - if (ft2_try_crc_candidate(trial_a91, log174, message)) - return true; - } - } - - return false; -} - -static bool ft2_unpack_message(const uint8_t plain174[], ftx_message_t* message) -{ - uint8_t a91[FTX_LDPC_K_BYTES]; - ft2_pack_bits(plain174, 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); - if (crc_extracted != crc_calculated) - return false; - - message->hash = crc_calculated; - for (int i = 0; i < 10; ++i) - { - message->payload[i] = a91[i] ^ kFT4_XOR_sequence[i]; - } - return true; -} - -// Compute post-decode SNR using all decoded symbols. -// -// For each symbol we know the exact transmitted tone (by re-encoding), so we -// compare the signal-bin power against the minimum power of the remaining -// (noise-only) bins. This avoids the systematic under-reporting caused by -// using only the adjacent bin as a noise reference: on a crowded band that -// adjacent bin is often occupied by another station, inflating the apparent -// noise floor. -// -// The per-symbol dB differences are averaged across all valid symbols, then -// converted to the WSJT-X convention (signal power relative to noise in a -// 2500 Hz reference bandwidth). -static float ftx_post_decode_snr( - const ftx_waterfall_t* wf, - const ftx_candidate_t* cand, - const ftx_message_t* message) -{ - int is_ft4 = ftx_protocol_uses_ft4_layout(wf->protocol); - int nn = is_ft4 ? FT4_NN : FT8_NN; - int num_tones = is_ft4 ? 4 : 8; - - uint8_t tones[FT4_NN]; // FT4_NN (105) >= FT8_NN (79) - if (is_ft4) - ft4_encode(message->payload, tones); - else - ft8_encode(message->payload, tones); - - // Replicate get_cand_mag() from decode.c (which is static there). - int offset = cand->time_offset; - offset = (offset * wf->time_osr) + cand->time_sub; - offset = (offset * wf->freq_osr) + cand->freq_sub; - offset = (offset * wf->num_bins) + cand->freq_offset; - const WF_ELEM_T* mag_cand = wf->mag + offset; - - float sum_snr = 0.0f; - int n_valid = 0; - - for (int sym = 0; sym < nn; sym++) - { - int block_abs = cand->time_offset + sym; - if (block_abs < 0 || block_abs >= wf->num_blocks) - continue; - - const WF_ELEM_T* p = mag_cand + (sym * wf->block_stride); - float sig_db = WF_ELEM_MAG(p[tones[sym]]); - - float noise_min = 0.0f; - int found_noise = 0; - for (int t = 0; t < num_tones; t++) - { - if (t == tones[sym]) - continue; - float db = WF_ELEM_MAG(p[t]); - if (!found_noise || db < noise_min) - { - noise_min = db; - found_noise = 1; - } - } - - if (found_noise) - { - sum_snr += sig_db - noise_min; - n_valid++; - } - } - - if (n_valid == 0) - return cand->score * 0.5f - 29.0f; - - // bin_width_hz = 1 / (symbol_period * freq_osr) - // bw_correction = 10*log10(2500 / bin_width_hz) - // = 10*log10(2500 * symbol_period * freq_osr) - float symbol_period = ftx_protocol_symbol_period(wf->protocol); - float bw_correction = 10.0f * log10f(2500.0f * symbol_period * (float)wf->freq_osr); - - return (sum_snr / (float)n_valid) - bw_correction; -} - -static int decode_from_waterfall_candidates( - const ft8_decoder_t* dec, - ft8_decode_result_t* out, - int max_results) -{ - if (!dec || !out || max_results <= 0) - return 0; - - const ftx_waterfall_t* wf = &dec->mon.wf; - const int kMaxCandidates = 200; - const int kMinScore = 10; - const int kLdpcIters = 30; - - int num_decoded = 0; - ftx_message_t decoded[200]; - ftx_message_t* decoded_hashtable[200]; - for (int i = 0; i < 200; ++i) - decoded_hashtable[i] = NULL; - - ftx_candidate_t candidate_list[kMaxCandidates]; - int num_candidates = ftx_find_candidates(wf, kMaxCandidates, candidate_list, kMinScore); - - for (int idx = 0; idx < num_candidates && num_decoded < max_results; ++idx) - { - const ftx_candidate_t* cand = &candidate_list[idx]; - - float freq_hz = decoder_candidate_freq_hz(dec, cand); - float time_sec = decoder_candidate_dt_s(dec, cand); - - ftx_message_t message; - ftx_decode_status_t status; - if (!ftx_decode_candidate(wf, cand, kLdpcIters, &message, &status)) - continue; - - int idx_hash = message.hash % 200; - bool found_empty_slot = false; - bool found_duplicate = false; - do - { - if (decoded_hashtable[idx_hash] == NULL) - { - found_empty_slot = true; - } - else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == memcmp(decoded_hashtable[idx_hash]->payload, message.payload, sizeof(message.payload)))) - { - found_duplicate = true; - } - else - { - idx_hash = (idx_hash + 1) % 200; - } - } while (!found_empty_slot && !found_duplicate); - - if (!found_empty_slot) - continue; - - memcpy(&decoded[idx_hash], &message, sizeof(message)); - decoded_hashtable[idx_hash] = &decoded[idx_hash]; - - char text[FTX_MAX_MESSAGE_LENGTH]; - ftx_message_offsets_t offsets; - ftx_message_rc_t unpack_status = ftx_message_decode(&message, &hash_if, text, &offsets); - if (unpack_status != FTX_MESSAGE_RC_OK) - continue; - - ft8_decode_result_t* dst = &out[num_decoded]; - strncpy(dst->text, text, sizeof(dst->text) - 1); - dst->text[sizeof(dst->text) - 1] = '\0'; - dst->dt_s = time_sec; - dst->freq_hz = freq_hz; - dst->snr_db = ftx_post_decode_snr(wf, cand, &message); - - num_decoded++; - } - - return num_decoded; -} - -static bool ft2_decode_hit( - const ft2_downsample_ctx_t* downsample_ctx, - const ft2_scan_hit_t* hit, - ftx_message_t* message, - float* dt_s, - float* freq_hz, - float* snr_db, - ft2_fail_stage_t* fail_stage, - ft2_pass_diag_t* pass_diag) -{ - if (!downsample_ctx) - return false; - - if (fail_stage) - *fail_stage = FT2_FAIL_NONE; - if (pass_diag) - { - for (int i = 0; i < 5; ++i) - { - pass_diag->ntype[i] = 0; - pass_diag->nharderror[i] = -1; - pass_diag->dmin[i] = INFINITY; - } - } - const int nfft2 = downsample_ctx ? downsample_ctx->nfft2 : 0; - float complex* cd2 = (float complex*)malloc(sizeof(float complex) * nfft2); - float complex* cb = (float complex*)malloc(sizeof(float complex) * nfft2); - float complex signal[FT2_FRAME_SAMPLES]; - float bitmetrics[2 * FT2_FRAME_SYMBOLS][3]; - float complex sync_wave[4][64]; - float complex tweak_wave[33][64]; - if (!cd2 || !cb) - { - free(cd2); - free(cb); - return false; - } - - ft2_prepare_sync_waveforms(sync_wave, tweak_wave); - - int produced = ft2_downsample_candidate(downsample_ctx, hit->freq_hz, cd2, nfft2); - if (produced <= 0) - { - if (fail_stage) - *fail_stage = FT2_FAIL_FINAL_DOWNSAMPLE; - free(cd2); - free(cb); - return false; - } - ft2_normalize_downsampled(cd2, produced, produced); - - float best_score = -1.0f; - int best_start = hit->start; - int best_idf = hit->idf; - for (int idf = hit->idf - 4; idf <= hit->idf + 4; ++idf) - { - if (idf < FT2_SYNC_TWEAK_MIN || idf > FT2_SYNC_TWEAK_MAX) - continue; - for (int start = hit->start - 5; start <= hit->start + 5; ++start) - { - float score = ft2_sync2d_score(cd2, produced, start, idf, sync_wave, tweak_wave); - if (score > best_score) - { - best_score = score; - best_start = start; - best_idf = idf; - } - } - } - if (best_score < 0.65f) - { - if (fail_stage) - *fail_stage = FT2_FAIL_REFINED_SYNC; - free(cd2); - free(cb); - return false; - } - - float corrected_freq_hz = hit->freq_hz + best_idf; - if (corrected_freq_hz <= 10.0f || corrected_freq_hz >= 4990.0f) - { - if (fail_stage) - *fail_stage = FT2_FAIL_FREQ_RANGE; - free(cd2); - free(cb); - return false; - } - - produced = ft2_downsample_candidate(downsample_ctx, corrected_freq_hz, cb, nfft2); - if (produced <= 0) - { - if (fail_stage) - *fail_stage = FT2_FAIL_FINAL_DOWNSAMPLE; - free(cd2); - free(cb); - return false; - } - ft2_normalize_downsampled(cb, produced, FT2_FRAME_SAMPLES); - ft2_extract_signal_region(cb, produced, best_start, signal); - - if (!ft2_extract_bitmetrics_raw(signal, bitmetrics)) - { - if (fail_stage) - *fail_stage = FT2_FAIL_BITMETRICS; - free(cd2); - free(cb); - return false; - } - - static const uint8_t sync_bits_a[8] = { 0, 0, 0, 1, 1, 0, 1, 1 }; - static const uint8_t sync_bits_b[8] = { 0, 1, 0, 0, 1, 1, 1, 0 }; - static const uint8_t sync_bits_c[8] = { 1, 1, 1, 0, 0, 1, 0, 0 }; - static const uint8_t sync_bits_d[8] = { 1, 0, 1, 1, 0, 0, 0, 1 }; - int sync_qual = 0; - for (int i = 0; i < 8; ++i) - { - sync_qual += ((bitmetrics[i][0] >= 0.0f) ? 1 : 0) == sync_bits_a[i]; - sync_qual += ((bitmetrics[66 + i][0] >= 0.0f) ? 1 : 0) == sync_bits_b[i]; - sync_qual += ((bitmetrics[132 + i][0] >= 0.0f) ? 1 : 0) == sync_bits_c[i]; - sync_qual += ((bitmetrics[198 + i][0] >= 0.0f) ? 1 : 0) == sync_bits_d[i]; - } - if (sync_qual < 10) - { - if (fail_stage) - *fail_stage = FT2_FAIL_SYNC_QUAL; - free(cd2); - free(cb); - return false; - } - - float llr_passes[5][FTX_LDPC_N]; - for (int i = 0; i < 58; ++i) - { - llr_passes[0][i] = bitmetrics[8 + i][0]; - llr_passes[0][58 + i] = bitmetrics[74 + i][0]; - llr_passes[0][116 + i] = bitmetrics[140 + i][0]; - llr_passes[1][i] = bitmetrics[8 + i][1]; - llr_passes[1][58 + i] = bitmetrics[74 + i][1]; - llr_passes[1][116 + i] = bitmetrics[140 + i][1]; - llr_passes[2][i] = bitmetrics[8 + i][2]; - llr_passes[2][58 + i] = bitmetrics[74 + i][2]; - llr_passes[2][116 + i] = bitmetrics[140 + i][2]; - } - for (int i = 0; i < FTX_LDPC_N; ++i) - { - llr_passes[0][i] *= 3.2f; - llr_passes[1][i] *= 3.2f; - llr_passes[2][i] *= 3.2f; - float a = llr_passes[0][i]; - float b = llr_passes[1][i]; - float c = llr_passes[2][i]; - llr_passes[3][i] = (fabsf(a) >= fabsf(b) && fabsf(a) >= fabsf(c)) ? a : ((fabsf(b) >= fabsf(c)) ? b : c); - llr_passes[4][i] = (a + b + c) / 3.0f; - } - - bool ok = false; - uint8_t cw[FTX_LDPC_N] = { 0 }; - int global_best_errors = FTX_LDPC_M; - for (int pass = 0; pass < 5 && !ok; ++pass) - { - float log174[FTX_LDPC_N]; - memcpy(log174, llr_passes[pass], sizeof(log174)); - ft2_normalize_log174(log174); - int ntype = 0; // 1: bp_decode, 2: ldpc_decode - int nharderror = FTX_LDPC_M; - float dmin = 0.0f; - - uint8_t bp_plain[FTX_LDPC_N]; - int bp_errors = FTX_LDPC_M; - bp_decode(log174, 50, bp_plain, &bp_errors); - if (bp_errors < nharderror) - { - nharderror = bp_errors; - memcpy(cw, bp_plain, sizeof(cw)); - ntype = 1; - } - if (bp_errors == 0 && ft2_unpack_message(bp_plain, message)) - { - ok = true; - memcpy(cw, bp_plain, sizeof(cw)); - ntype = 1; - nharderror = 0; - } - - if (!ok) - { - uint8_t sp_plain[FTX_LDPC_N]; - int sp_errors = FTX_LDPC_M; - ldpc_decode(log174, 50, sp_plain, &sp_errors); - if (sp_errors < nharderror) - { - nharderror = sp_errors; - memcpy(cw, sp_plain, sizeof(cw)); - ntype = 2; - } - if (sp_errors == 0 && ft2_unpack_message(sp_plain, message)) - { - ok = true; - memcpy(cw, sp_plain, sizeof(cw)); - ntype = 2; - nharderror = 0; - } - } - - if (nharderror < global_best_errors) - global_best_errors = nharderror; - - if (pass_diag) - { - pass_diag->ntype[pass] = ntype; - pass_diag->nharderror[pass] = nharderror; - pass_diag->dmin[pass] = dmin; - } - } - - // CRC-based OSD-1/OSD-2: only when LDPC was within 6 parity errors of - // converging, so the LLRs are reliable enough to trust bit-flip search. - // 16 least-reliable systematic bits, OSD-2 = 16+120 = 136 CRC checks per - // pass, 680 total — keeps false-positive rate acceptably low. - if (!ok && global_best_errors <= 6) - { - for (int pass = 0; pass < 5 && !ok; ++pass) - { - float osd_log174[FTX_LDPC_N]; - memcpy(osd_log174, llr_passes[pass], sizeof(osd_log174)); - ft2_normalize_log174(osd_log174); - if (ft2_osd_lite_decode(osd_log174, message)) - ok = true; - } - } - - if (!ok && fail_stage) - *fail_stage = FT2_FAIL_LDPC; - - if (ok) - { - float sm1 = ft2_sync2d_score(cd2, produced, best_start - 1, best_idf, sync_wave, tweak_wave); - float sp1 = ft2_sync2d_score(cd2, produced, best_start + 1, best_idf, sync_wave, tweak_wave); - float xstart = (float)best_start; - float den = sm1 - 2.0f * best_score + sp1; - if (fabsf(den) > 1.0e-6f) - xstart += 0.5f * (sm1 - sp1) / den; - - *dt_s = xstart / (12000.0f / FT2_NDOWN) - 0.5f; - *freq_hz = corrected_freq_hz; - if (hit->snr0 > 0.0f) - *snr_db = fmaxf(-21.0f, 10.0f * log10f(hit->snr0) - 13.0f); - else - *snr_db = -21.0f; - } - free(cd2); - free(cb); - return ok; -} - -ft8_decoder_t* ft8_decoder_create(int sample_rate, float f_min, float f_max, int time_osr, int freq_osr, int protocol) -{ - ft8_decoder_t* dec = (ft8_decoder_t*)calloc(1, sizeof(ft8_decoder_t)); - if (!dec) - { - return NULL; - } - dec->cfg.f_min = f_min; - dec->cfg.f_max = f_max; - dec->cfg.sample_rate = sample_rate; - dec->cfg.time_osr = time_osr; - dec->cfg.freq_osr = freq_osr; - 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); - if (dec->cfg.protocol == FTX_PROTOCOL_FT2) - { - dec->ft2_raw_capacity = FT2_NMAX; - dec->ft2_raw = (float*)calloc(dec->ft2_raw_capacity, sizeof(float)); - dec->ft2_raw_len = 0; - if (!dec->ft2_raw) - { - monitor_free(&dec->mon); - free(dec); - return NULL; - } - } - return dec; -} - -void ft8_decoder_free(ft8_decoder_t* dec) -{ - if (!dec) - return; - free(dec->ft2_raw); - monitor_free(&dec->mon); - free(dec); -} - -int ft8_decoder_block_size(const ft8_decoder_t* dec) -{ - return dec ? dec->mon.block_size : 0; -} - -int ft8_decoder_window_samples(const ft8_decoder_t* dec) -{ - if (!dec) - return 0; - if (dec->cfg.protocol == FTX_PROTOCOL_FT2) - return FT2_NMAX; - return dec->mon.block_size * dec->mon.wf.max_blocks; -} - -void ft8_decoder_reset(ft8_decoder_t* dec) -{ - if (!dec) - return; - monitor_reset(&dec->mon); - dec->ft2_raw_len = 0; - if (dec->ft2_raw && dec->ft2_raw_capacity > 0) - { - memset(dec->ft2_raw, 0, sizeof(float) * dec->ft2_raw_capacity); - } -} - -void ft8_decoder_process(ft8_decoder_t* dec, const float* frame) -{ - if (!dec || !frame) - return; - if (dec->cfg.protocol == FTX_PROTOCOL_FT2 && dec->ft2_raw && dec->ft2_raw_capacity > 0) - { - int remaining = dec->ft2_raw_capacity - dec->ft2_raw_len; - if (remaining > 0) - { - int copy_len = (remaining < dec->mon.block_size) ? remaining : dec->mon.block_size; - memcpy(dec->ft2_raw + dec->ft2_raw_len, frame, sizeof(float) * copy_len); - dec->ft2_raw_len += copy_len; - } - } - monitor_process(&dec->mon, frame); -} - -int ft8_decoder_is_ready(const ft8_decoder_t* dec) -{ - if (!dec) - return 0; - if (dec->cfg.protocol == FTX_PROTOCOL_FT2) - { - return (dec->ft2_raw_len >= dec->ft2_raw_capacity) ? 1 : 0; - } - return (dec->mon.wf.num_blocks >= dec->mon.wf.max_blocks) ? 1 : 0; -} - -int ft8_decoder_decode(ft8_decoder_t* dec, ft8_decode_result_t* out, int max_results) -{ - if (!dec || !out || max_results <= 0) - return 0; - - const bool is_ft2 = (dec->cfg.protocol == FTX_PROTOCOL_FT2); - - int num_decoded = 0; - - if (is_ft2) - { - ft2_downsample_ctx_t downsample_ctx; - if (!ft2_downsample_ctx_init(dec, &downsample_ctx)) - { - LOG(LOG_WARN, "FT2 decode: downsample context init failed\n"); - return 0; - } - ft2_scan_hit_t hit_list[FT2_MAX_SCAN_HITS]; - ft2_scan_stats_t scan_stats; - int num_hits = ft2_find_scan_hits(dec, &downsample_ctx, hit_list, FT2_MAX_SCAN_HITS, &scan_stats); - int fail_refined_sync = 0; - int fail_freq_range = 0; - int fail_final_downsample = 0; - int fail_bitmetrics = 0; - int fail_sync_qual = 0; - int fail_ldpc = 0; - int fail_crc = 0; - int fail_unpack = 0; - int fallback_decoded = 0; - ftx_message_t decoded[200]; - ftx_message_t* decoded_hashtable[200]; - for (int i = 0; i < 200; ++i) - decoded_hashtable[i] = NULL; - int pass_bp[5] = { 0 }; - int pass_sp[5] = { 0 }; - float pass_best_dmin[5] = { INFINITY, INFINITY, INFINITY, INFINITY, INFINITY }; - int pass_best_errors[5] = { FTX_LDPC_M, FTX_LDPC_M, FTX_LDPC_M, FTX_LDPC_M, FTX_LDPC_M }; - for (int idx = 0; idx < num_hits && num_decoded < max_results; ++idx) - { - const ft2_scan_hit_t* hit = &hit_list[idx]; - ftx_message_t message; - float time_sec = 0.0f; - float freq_hz = 0.0f; - float snr_db = -21.0f; - ft2_fail_stage_t fail_stage = FT2_FAIL_NONE; - ft2_pass_diag_t pass_diag; - if (!ft2_decode_hit(&downsample_ctx, hit, &message, &time_sec, &freq_hz, &snr_db, &fail_stage, &pass_diag)) - { - for (int pass = 0; pass < 5; ++pass) - { - if (pass_diag.ntype[pass] == 1) - ++pass_bp[pass]; - else if (pass_diag.ntype[pass] == 2) - ++pass_sp[pass]; - if (pass_diag.dmin[pass] < pass_best_dmin[pass]) - pass_best_dmin[pass] = pass_diag.dmin[pass]; - if (pass_diag.nharderror[pass] >= 0 && pass_diag.nharderror[pass] < pass_best_errors[pass]) - pass_best_errors[pass] = pass_diag.nharderror[pass]; - } - switch (fail_stage) - { - case FT2_FAIL_REFINED_SYNC: - ++fail_refined_sync; - break; - case FT2_FAIL_FREQ_RANGE: - ++fail_freq_range; - break; - case FT2_FAIL_FINAL_DOWNSAMPLE: - ++fail_final_downsample; - break; - case FT2_FAIL_BITMETRICS: - ++fail_bitmetrics; - break; - case FT2_FAIL_SYNC_QUAL: - ++fail_sync_qual; - break; - case FT2_FAIL_LDPC: - ++fail_ldpc; - break; - case FT2_FAIL_CRC: - ++fail_crc; - break; - case FT2_FAIL_UNPACK: - ++fail_unpack; - break; - default: - break; - } - continue; - } - for (int pass = 0; pass < 5; ++pass) - { - if (pass_diag.ntype[pass] == 1) - ++pass_bp[pass]; - else if (pass_diag.ntype[pass] == 2) - ++pass_sp[pass]; - if (pass_diag.dmin[pass] < pass_best_dmin[pass]) - pass_best_dmin[pass] = pass_diag.dmin[pass]; - if (pass_diag.nharderror[pass] >= 0 && pass_diag.nharderror[pass] < pass_best_errors[pass]) - pass_best_errors[pass] = pass_diag.nharderror[pass]; - } - - int idx_hash = message.hash % 200; - bool found_empty_slot = false; - bool found_duplicate = false; - do - { - if (decoded_hashtable[idx_hash] == NULL) - { - found_empty_slot = true; - } - else if ((decoded_hashtable[idx_hash]->hash == message.hash) && (0 == memcmp(decoded_hashtable[idx_hash]->payload, message.payload, sizeof(message.payload)))) - { - found_duplicate = true; - } - else - { - idx_hash = (idx_hash + 1) % 200; - } - } while (!found_empty_slot && !found_duplicate); - - if (!found_empty_slot) - continue; - - memcpy(&decoded[idx_hash], &message, sizeof(message)); - decoded_hashtable[idx_hash] = &decoded[idx_hash]; - - char text[FTX_MAX_MESSAGE_LENGTH]; - ftx_message_offsets_t offsets; - ftx_message_rc_t unpack_status = ftx_message_decode(&message, &hash_if, text, &offsets); - if (unpack_status != FTX_MESSAGE_RC_OK) - { - ++fail_unpack; - continue; - } - - ft8_decode_result_t* dst = &out[num_decoded]; - strncpy(dst->text, text, sizeof(dst->text) - 1); - dst->text[sizeof(dst->text) - 1] = '\0'; - dst->dt_s = time_sec; - dst->freq_hz = freq_hz; - dst->snr_db = snr_db; - - num_decoded++; - } - if (num_decoded == 0) - { - fallback_decoded = decode_from_waterfall_candidates(dec, out, max_results); - if (fallback_decoded > 0) - num_decoded = fallback_decoded; - } - LOG(LOG_INFO, - "FT2 window: raw=%d peaks=%d hits=%d best_peak=%.3f best_sync=%.3f decoded=%d fallback=%d fail(sync=%d freq=%d down=%d bits=%d qual=%d ldpc=%d crc=%d unpack=%d) pass(bp=%d/%d/%d/%d/%d sp=%d/%d/%d/%d/%d err=%d/%d/%d/%d/%d dmin=%.2f/%.2f/%.2f/%.2f/%.2f)\n", - dec->ft2_raw_len, - scan_stats.peaks_found, - scan_stats.hits_found, - scan_stats.best_peak_score, - scan_stats.best_sync_score, - num_decoded, - fallback_decoded, - fail_refined_sync, - fail_freq_range, - fail_final_downsample, - fail_bitmetrics, - fail_sync_qual, - fail_ldpc, - fail_crc, - fail_unpack, - pass_bp[0], pass_bp[1], pass_bp[2], pass_bp[3], pass_bp[4], - pass_sp[0], pass_sp[1], pass_sp[2], pass_sp[3], pass_sp[4], - (pass_best_errors[0] < FTX_LDPC_M) ? pass_best_errors[0] : -1, - (pass_best_errors[1] < FTX_LDPC_M) ? pass_best_errors[1] : -1, - (pass_best_errors[2] < FTX_LDPC_M) ? pass_best_errors[2] : -1, - (pass_best_errors[3] < FTX_LDPC_M) ? pass_best_errors[3] : -1, - (pass_best_errors[4] < FTX_LDPC_M) ? pass_best_errors[4] : -1, - isfinite(pass_best_dmin[0]) ? pass_best_dmin[0] : -1.0f, - isfinite(pass_best_dmin[1]) ? pass_best_dmin[1] : -1.0f, - isfinite(pass_best_dmin[2]) ? pass_best_dmin[2] : -1.0f, - isfinite(pass_best_dmin[3]) ? pass_best_dmin[3] : -1.0f, - isfinite(pass_best_dmin[4]) ? pass_best_dmin[4] : -1.0f); - ft2_downsample_ctx_free(&downsample_ctx); - } - else - { - num_decoded = decode_from_waterfall_candidates(dec, out, max_results); - } - - hashtable_cleanup(10); - return num_decoded; -} diff --git a/src/decoders/trx-ft8/src/lib.rs b/src/decoders/trx-ft8/src/lib.rs deleted file mode 100644 index 9a2b091..0000000 --- a/src/decoders/trx-ft8/src/lib.rs +++ /dev/null @@ -1,208 +0,0 @@ -// SPDX-FileCopyrightText: 2026 Stanislaw Grams -// -// 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, - 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::new_with_protocol(sample_rate, PROTOCOL_FT8, "FT8") - } - - pub fn new_ft4(sample_rate: u32) -> Result { - Self::new_with_protocol(sample_rate, PROTOCOL_FT4, "FT4") - } - - pub fn new_ft2(sample_rate: u32) -> Result { - Self::new_with_protocol(sample_rate, PROTOCOL_FT2, "FT2") - } - - fn new_with_protocol(sample_rate: u32, protocol: c_int, label: &str) -> Result { - 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 { - 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); - } -} diff --git a/src/trx-server/Cargo.toml b/src/trx-server/Cargo.toml index 98dd98f..b8082e8 100644 --- a/src/trx-server/Cargo.toml +++ b/src/trx-server/Cargo.toml @@ -37,7 +37,7 @@ trx-core = { path = "../trx-core" } trx-aprs = { path = "../decoders/trx-aprs" } trx-cw = { path = "../decoders/trx-cw" } 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-protocol = { path = "../trx-protocol" } trx-reporting = { path = "../trx-reporting" } \ No newline at end of file diff --git a/src/trx-server/src/audio.rs b/src/trx-server/src/audio.rs index ec771e8..6cc9e13 100644 --- a/src/trx-server/src/audio.rs +++ b/src/trx-server/src/audio.rs @@ -36,7 +36,7 @@ use trx_core::decode::{ use trx_core::rig::state::{RigMode, RigState}; use trx_core::vchan::SharedVChanManager; use trx_cw::CwDecoder; -use trx_ft8::Ft8Decoder; +use trx_ftx::Ft8Decoder; use trx_vdes::VdesDecoder; use trx_wspr::WsprDecoder; use uuid::Uuid; @@ -92,7 +92,7 @@ fn decode_ft2_window( decoder: &mut Ft8Decoder, samples: &[f32], max_results: usize, -) -> Vec { +) -> Vec { let window_samples = decoder.window_samples(); if samples.len() < window_samples { return Vec::new();