[fix](trx-rs): improve FT2 coherent decoding
Add coherent FT2 sync and likelihood extraction. Align FT2 frequency and DT reporting with the reference decoder. Fix vendored monitor phase-mode type mismatches. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
Vendored
+1
-1
@@ -192,7 +192,7 @@ void monitor_process(monitor_t* me, const float* frame)
|
||||
}
|
||||
|
||||
#ifdef WATERFALL_USE_PHASE
|
||||
void monitor_resynth(const monitor_t* me, const candidate_t* candidate, float* signal)
|
||||
void monitor_resynth(const monitor_t* me, const ftx_candidate_t* candidate, float* signal)
|
||||
{
|
||||
const int num_ifft = me->nifft;
|
||||
const int num_shift = num_ifft / 2;
|
||||
|
||||
Vendored
+2
-2
@@ -52,11 +52,11 @@ void monitor_process(monitor_t* me, const float* frame);
|
||||
void monitor_free(monitor_t* me);
|
||||
|
||||
#ifdef WATERFALL_USE_PHASE
|
||||
void monitor_resynth(const monitor_t* me, const candidate_t* candidate, float* signal);
|
||||
void monitor_resynth(const monitor_t* me, const ftx_candidate_t* candidate, float* signal);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif // _INCLUDE_MONITOR_H_
|
||||
#endif // _INCLUDE_MONITOR_H_
|
||||
|
||||
Vendored
+158
-2
@@ -5,6 +5,7 @@
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <math.h>
|
||||
#include <complex.h>
|
||||
|
||||
// #define LOG_LEVEL LOG_DEBUG
|
||||
// #include "debug.h"
|
||||
@@ -30,6 +31,7 @@ static const float db_power_sum[40] = {
|
||||
/// @param[in] code_map Symbol encoding map
|
||||
/// @param[out] log174 Output of decoded log likelihoods for each of the 174 message bits
|
||||
static void ft4_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174);
|
||||
static void ft2_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174);
|
||||
static void ft8_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174);
|
||||
|
||||
/// Packs a string of bits each represented as a zero/non-zero byte in bit_array[],
|
||||
@@ -46,9 +48,17 @@ static void heapify_up(ftx_candidate_t heap[], int heap_size);
|
||||
|
||||
static void ftx_normalize_logl(float* log174);
|
||||
static void ft4_extract_symbol(const WF_ELEM_T* wf, float* logl);
|
||||
static void ft2_extract_logl_sequence(const float complex symbols[4][FT2_NN - FT2_NR], int start_sym, int n_syms, float* metrics);
|
||||
static void ft8_extract_symbol(const WF_ELEM_T* wf, float* logl);
|
||||
static void ft8_decode_multi_symbols(const WF_ELEM_T* wf, int num_bins, int n_syms, int bit_idx, float* log174);
|
||||
|
||||
static inline float complex wf_elem_to_complex(const WF_ELEM_T elem)
|
||||
{
|
||||
float mag = WF_ELEM_MAG(elem);
|
||||
float amplitude = powf(10.0f, mag / 20.0f);
|
||||
return amplitude * cexpf(I * elem.phase);
|
||||
}
|
||||
|
||||
static const WF_ELEM_T* get_cand_mag(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int offset = candidate->time_offset;
|
||||
@@ -124,6 +134,44 @@ static int ft8_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* cand
|
||||
return score;
|
||||
}
|
||||
|
||||
static int ft2_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
const WF_ELEM_T* mag_cand = get_cand_mag(wf, candidate);
|
||||
float score = 0.0f;
|
||||
int groups = 0;
|
||||
|
||||
for (int m = 0; m < FT2_NUM_SYNC; ++m)
|
||||
{
|
||||
float complex sum = 0.0f;
|
||||
bool complete = true;
|
||||
for (int k = 0; k < FT2_LENGTH_SYNC; ++k)
|
||||
{
|
||||
int block = 1 + (FT2_SYNC_OFFSET * m) + k;
|
||||
int block_abs = candidate->time_offset + block;
|
||||
if ((block_abs < 0) || (block_abs >= wf->num_blocks))
|
||||
{
|
||||
complete = false;
|
||||
break;
|
||||
}
|
||||
|
||||
const WF_ELEM_T* sym = mag_cand + (block * wf->block_stride);
|
||||
int tone = kFT4_Costas_pattern[m][k];
|
||||
sum += wf_elem_to_complex(sym[tone]);
|
||||
}
|
||||
|
||||
if (!complete)
|
||||
continue;
|
||||
|
||||
score += cabsf(sum);
|
||||
++groups;
|
||||
}
|
||||
|
||||
if (groups == 0)
|
||||
return 0;
|
||||
|
||||
return (int)lroundf((score / groups) * 8.0f);
|
||||
}
|
||||
|
||||
static int ft4_sync_score(const ftx_waterfall_t* wf, const ftx_candidate_t* candidate)
|
||||
{
|
||||
int score = 0;
|
||||
@@ -191,7 +239,7 @@ int ftx_find_candidates(const ftx_waterfall_t* wf, int num_candidates, ftx_candi
|
||||
{
|
||||
bool is_ft2 = (wf->protocol == FTX_PROTOCOL_FT2);
|
||||
int (*sync_fun)(const ftx_waterfall_t*, const ftx_candidate_t*) =
|
||||
ftx_protocol_uses_ft4_layout(wf->protocol) ? ft4_sync_score : ft8_sync_score;
|
||||
is_ft2 ? ft2_sync_score : (ftx_protocol_uses_ft4_layout(wf->protocol) ? ft4_sync_score : ft8_sync_score);
|
||||
int num_tones = ftx_protocol_uses_ft4_layout(wf->protocol) ? 4 : 8;
|
||||
int time_offset_min = -10;
|
||||
int time_offset_max = 20;
|
||||
@@ -290,6 +338,74 @@ static void ft4_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidat
|
||||
}
|
||||
}
|
||||
|
||||
static void ft2_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174)
|
||||
{
|
||||
const WF_ELEM_T* mag = get_cand_mag(wf, cand);
|
||||
float complex symbols[4][FT2_NN - FT2_NR];
|
||||
float metric1[2 * (FT2_NN - FT2_NR)] = { 0 };
|
||||
float metric2[2 * (FT2_NN - FT2_NR)] = { 0 };
|
||||
float metric4[2 * (FT2_NN - FT2_NR)] = { 0 };
|
||||
|
||||
for (int frame_sym = 0; frame_sym < (FT2_NN - FT2_NR); ++frame_sym)
|
||||
{
|
||||
int sym_idx = frame_sym + 1; // skip ramp-up symbol
|
||||
int block = cand->time_offset + sym_idx;
|
||||
if ((block < 0) || (block >= wf->num_blocks))
|
||||
{
|
||||
for (int tone = 0; tone < 4; ++tone)
|
||||
{
|
||||
symbols[tone][frame_sym] = 0.0f;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const WF_ELEM_T* sym = mag + (sym_idx * wf->block_stride);
|
||||
for (int tone = 0; tone < 4; ++tone)
|
||||
{
|
||||
symbols[tone][frame_sym] = wf_elem_to_complex(sym[tone]);
|
||||
}
|
||||
}
|
||||
|
||||
for (int start = 0; start <= (FT2_NN - FT2_NR) - 1; start += 1)
|
||||
{
|
||||
ft2_extract_logl_sequence(symbols, start, 1, metric1 + (2 * start));
|
||||
}
|
||||
for (int start = 0; start <= (FT2_NN - FT2_NR) - 2; start += 2)
|
||||
{
|
||||
ft2_extract_logl_sequence(symbols, start, 2, metric2 + (2 * start));
|
||||
}
|
||||
for (int start = 0; start <= (FT2_NN - FT2_NR) - 4; start += 4)
|
||||
{
|
||||
ft2_extract_logl_sequence(symbols, start, 4, metric4 + (2 * start));
|
||||
}
|
||||
|
||||
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];
|
||||
|
||||
for (int data_sym = 0; data_sym < FT2_ND; ++data_sym)
|
||||
{
|
||||
int frame_sym = data_sym + ((data_sym < 29) ? 4 : ((data_sym < 58) ? 8 : 12));
|
||||
int src_bit = 2 * frame_sym;
|
||||
int dst_bit = 2 * data_sym;
|
||||
|
||||
float a0 = metric1[src_bit + 0];
|
||||
float b0 = metric2[src_bit + 0];
|
||||
float c0 = metric4[src_bit + 0];
|
||||
float a1 = metric1[src_bit + 1];
|
||||
float b1 = metric2[src_bit + 1];
|
||||
float c1 = metric4[src_bit + 1];
|
||||
|
||||
log174[dst_bit + 0] = (fabsf(a0) >= fabsf(b0) && fabsf(a0) >= fabsf(c0)) ? a0 : ((fabsf(b0) >= fabsf(c0)) ? b0 : c0);
|
||||
log174[dst_bit + 1] = (fabsf(a1) >= fabsf(b1) && fabsf(a1) >= fabsf(c1)) ? a1 : ((fabsf(b1) >= fabsf(c1)) ? b1 : c1);
|
||||
}
|
||||
}
|
||||
|
||||
static void ft8_extract_likelihood(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, float* log174)
|
||||
{
|
||||
const WF_ELEM_T* mag = get_cand_mag(wf, cand); // Pointer to 8 magnitude bins of the first symbol
|
||||
@@ -341,7 +457,11 @@ static void ftx_normalize_logl(float* log174)
|
||||
bool ftx_decode_candidate(const ftx_waterfall_t* wf, const ftx_candidate_t* cand, int max_iterations, ftx_message_t* message, ftx_decode_status_t* status)
|
||||
{
|
||||
float log174[FTX_LDPC_N]; // message bits encoded as likelihood
|
||||
if (ftx_protocol_uses_ft4_layout(wf->protocol))
|
||||
if (wf->protocol == FTX_PROTOCOL_FT2)
|
||||
{
|
||||
ft2_extract_likelihood(wf, cand, log174);
|
||||
}
|
||||
else if (ftx_protocol_uses_ft4_layout(wf->protocol))
|
||||
{
|
||||
ft4_extract_likelihood(wf, cand, log174);
|
||||
}
|
||||
@@ -479,6 +599,42 @@ static void ft4_extract_symbol(const WF_ELEM_T* wf, float* logl)
|
||||
logl[1] = max2(s2[1], s2[3]) - max2(s2[0], s2[2]);
|
||||
}
|
||||
|
||||
static void ft2_extract_logl_sequence(const float complex symbols[4][FT2_NN - FT2_NR], int start_sym, int n_syms, float* metrics)
|
||||
{
|
||||
const int n_bits = 2 * n_syms;
|
||||
const int n_sequences = 1 << n_bits;
|
||||
|
||||
for (int bit = 0; bit < n_bits; ++bit)
|
||||
{
|
||||
float max_zero = -INFINITY;
|
||||
float max_one = -INFINITY;
|
||||
for (int seq = 0; seq < n_sequences; ++seq)
|
||||
{
|
||||
float complex sum = 0.0f;
|
||||
for (int sym = 0; sym < n_syms; ++sym)
|
||||
{
|
||||
int shift = 2 * (n_syms - sym - 1);
|
||||
int dibit = (seq >> shift) & 0x3;
|
||||
int tone = kFT4_Gray_map[dibit];
|
||||
sum += symbols[tone][start_sym + sym];
|
||||
}
|
||||
float strength = cabsf(sum);
|
||||
int mask_bit = n_bits - bit - 1;
|
||||
if (((seq >> mask_bit) & 0x1) != 0)
|
||||
{
|
||||
if (strength > max_one)
|
||||
max_one = strength;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (strength > max_zero)
|
||||
max_zero = strength;
|
||||
}
|
||||
}
|
||||
metrics[bit] = max_one - max_zero;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute unnormalized log likelihood log(p(1) / p(0)) of 3 message bits (1 FSK symbol)
|
||||
static void ft8_extract_symbol(const WF_ELEM_T* wf, float* logl)
|
||||
{
|
||||
|
||||
Vendored
+1
-1
@@ -18,7 +18,7 @@ typedef struct
|
||||
float phase;
|
||||
} waterfall_cpx_t;
|
||||
|
||||
// #define WATERFALL_USE_PHASE
|
||||
#define WATERFALL_USE_PHASE
|
||||
|
||||
#ifdef WATERFALL_USE_PHASE
|
||||
#define WF_ELEM_T waterfall_cpx_t
|
||||
|
||||
@@ -115,6 +115,33 @@ typedef struct
|
||||
float freq_hz;
|
||||
} ft8_decode_result_t;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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));
|
||||
@@ -195,7 +222,7 @@ int ft8_decoder_decode(ft8_decoder_t* dec, ft8_decode_result_t* out, int max_res
|
||||
const ftx_waterfall_t* wf = &dec->mon.wf;
|
||||
const bool is_ft2 = (dec->cfg.protocol == FTX_PROTOCOL_FT2);
|
||||
const int kMaxCandidates = is_ft2 ? 400 : 200;
|
||||
const int kMinScore = is_ft2 ? 4 : 10;
|
||||
const int kMinScore = is_ft2 ? 0 : 10;
|
||||
const int kLdpcIters = is_ft2 ? 50 : 30;
|
||||
|
||||
ftx_candidate_t candidate_list[kMaxCandidates];
|
||||
@@ -213,8 +240,8 @@ int ft8_decoder_decode(ft8_decoder_t* dec, ft8_decode_result_t* out, int max_res
|
||||
{
|
||||
const ftx_candidate_t* cand = &candidate_list[idx];
|
||||
|
||||
float freq_hz = (dec->mon.min_bin + cand->freq_offset + (float)cand->freq_sub / wf->freq_osr) / dec->mon.symbol_period;
|
||||
float time_sec = (cand->time_offset + (float)cand->time_sub / wf->time_osr) * dec->mon.symbol_period;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user