Skip to content

File symsync_core.h

File List > inc > symsync > symsync_core.h

Go to the documentation of this file

#ifndef SYMSYNC_CORE_H
#define SYMSYNC_CORE_H

#include "clib_common.h"
#include "dp_state.h"
#include "farrow/farrow_core.h"
#include "jm_perf.h"
#include "lockdet/lockdet_core.h"
#include "loop_filter/loop_filter_core.h"
#include "nco/nco_core.h"
#include "dp_tlm/dp_tlm_core.h"
#include "telemetry/telemetry_core.h"
#include "detection/detection_core.h"
#ifdef __cplusplus
extern "C"
{
#endif

  enum
  {
    SYMSYNC_TED_GARDNER = 0, 
    SYMSYNC_TED_DTTL    = 1  
  };

/* Numerical guard on the on-time+mid-symbol energy sum feeding the lock
 * statistic (not tunable). */
#define SYMSYNC_LOCK_EPS 1e-12

  typedef struct
  {
    dp_tlm_t *ctx;       
    int32_t   id_e;      
    int32_t   id_freq;   
    int32_t   id_rate;   
    int32_t   id_lock;   
    int32_t   id_locked; 
  } symsync_tlm_t;

  typedef struct
  {
    nco_state_t         timing; 
    farrow_state_t      farrow; 
    loop_filter_state_t lf;     
    size_t              sps;    
    uint32_t      base_inc;     
    int           ted;          
    int           have_ontime;  
    float _Complex prev_ontime;  
    float _Complex mid;          
    double        bn;           
    double        zeta;         
    double        last_error;   
    double        rate_est;     
    double        pwr_avg;      
    /* ── lock detector (always on): tumbling-window block average ────── */
    double lock_sum;      
    size_t lock_count;    
    size_t avgs;          
    double lock_stat;     
    lockdet_state_t lock; 
    symsync_tlm_t tlm; 
  } symsync_state_t;

  JM_FORCEINLINE double
  gardner_ted (float _Complex mid, float _Complex diff)
  {
    return (double)(crealf (mid) * crealf (diff)
                    + cimagf (mid) * cimagf (diff));
  }

  enum
  {
    SYMSYNC_PULSE_IANDD = 0, 
    SYMSYNC_PULSE_RRC   = 1  
  };

  double symsync_ted_slope (int ted, int pulse, double beta, size_t span);

  JM_FORCEINLINE double
  dttl_ted (float _Complex mid, float _Complex y, float _Complex prev)
  {
    double si = (crealf (y) >= 0.0f ? 1.0 : -1.0)
                - (crealf (prev) >= 0.0f ? 1.0 : -1.0);
    double sq = (cimagf (y) >= 0.0f ? 1.0 : -1.0)
                - (cimagf (prev) >= 0.0f ? 1.0 : -1.0);
    return (double)crealf (mid) * si + (double)cimagf (mid) * sq;
  }

  JM_FORCEINLINE JM_HOT int
  symsync_step_ted (symsync_state_t *s, float _Complex x, float _Complex *y_out,
                    int ted)
  {
    const uint32_t HALF = 0x80000000u;
    farrow_push (&s->farrow, x);
    uint32_t old    = s->timing.phase;
    uint64_t sum    = (uint64_t)old + s->timing.phase_inc;
    s->timing.phase = (uint32_t)sum;

    int wrapped = sum >> 32 != 0;
    int mid_evt = !wrapped && old < HALF && (uint32_t)sum >= HALF;
    if (!wrapped && !mid_evt)
      return 0;

    double inc = (double)s->timing.phase_inc;
    if (mid_evt)
      {
        float mu = (float)(1.0 - ((double)((uint32_t)sum - HALF)) / inc);
        s->mid   = farrow_eval (&s->farrow, mu);
        return 0;
      }
    float         mu   = (float)(1.0 - (double)s->timing.phase / inc);
    float _Complex y    = farrow_eval (&s->farrow, mu);
    int           emit = 0;
    if (s->have_ontime)
      {
        double num;
        if (ted == SYMSYNC_TED_DTTL)
          num = dttl_ted (s->mid, y, s->prev_ontime);
        else
          {
            float _Complex diff = y - s->prev_ontime;
            num                = gardner_ted (s->mid, diff);
          }
        double inst_pwr
            = (double)(crealf (y) * crealf (y) + cimagf (y) * cimagf (y));
        s->pwr_avg += 0.01 * (inst_pwr - s->pwr_avg);
        double e       = num / (s->pwr_avg + 1e-6);
        s->last_error  = e;
        double control = loop_filter_step (&s->lf, e);
        s->timing.phase_inc
            = (uint32_t)((double)s->base_inc * (1.0 + control));
        double inst = (double)s->sps / (1.0 + control);
        double lo_r = 0.5 * (double)s->sps, hi_r = 1.5 * (double)s->sps;
        if (inst < lo_r)
          inst = lo_r;
        else if (inst > hi_r)
          inst = hi_r;
        s->rate_est += 0.02 * (inst - s->rate_est);
        /* Lock statistic: lock_signal = 2*(|on-time|^2-|mid|^2)
         * /(|on-time|^2+|mid|^2), a Gardner-style eye-opening ratio (the
         * on-time sample vs. the mid-symbol/transition-gate sample already
         * used by the TED, reusing inst_pwr from above). Non-coherently
         * block-averaged over `avgs` looks before the decision, mirroring
         * dll_state_t's lock_sum/lock_count/n_looks tumbling window (a
         * sliding window would break the verify-count independence
         * assumption the same way it would for the DLL -- see
         * dll_configure_lock's derivation). See symsync_configure_lock()
         * for how avgs/threshold are sized from (rolloff, esno_min, pfa,
         * pd). */
        float _Complex md = s->mid;
        double        mid_pwr
            = (double)(crealf (md) * crealf (md) + cimagf (md) * cimagf (md));
        double lock_signal = 2.0 * (inst_pwr - mid_pwr)
                             / (inst_pwr + mid_pwr + SYMSYNC_LOCK_EPS);
        s->lock_sum += lock_signal;
        if (++s->lock_count >= s->avgs)
          {
            s->lock_stat = s->lock_sum / (double)s->avgs;
            (void)lockdet_step (&s->lock, s->lock_stat);
            s->lock_sum   = 0.0;
            s->lock_count = 0;
          }
        *y_out = y;
        emit   = 1;
      }
    else
      s->have_ontime = 1;
    s->prev_ontime = y;
    return emit;
  }

  void symsync_tlm_flush (const symsync_state_t *s);

  JM_FORCEINLINE JM_HOT int
  symsync_step (symsync_state_t *s, float _Complex x, float _Complex *y_out)
  {
    int r = symsync_step_ted (s, x, y_out, s->ted);
    if (r && s->tlm.ctx)
      symsync_tlm_flush (s);
    return r;
  }

  void symsync_init (symsync_state_t *s, size_t sps, double bn, double zeta,
                     int order, int ted);

  symsync_state_t *symsync_create (size_t sps, double bn, double zeta,
                                   int order, int ted);

  void symsync_destroy (symsync_state_t *state);

  void symsync_reset (symsync_state_t *state);

  size_t symsync_steps_max_out (symsync_state_t *state);

  size_t symsync_steps (symsync_state_t *state, const float _Complex *x,
                        size_t x_len, float _Complex *out, size_t max_out);

  void   symsync_configure (symsync_state_t *state, double bn, double zeta);
  double symsync_get_bn (const symsync_state_t *state);
  void   symsync_set_bn (symsync_state_t *state, double val);
  double symsync_get_timing_error (const symsync_state_t *state);
  double symsync_get_rate (const symsync_state_t *state);

  double symsync_get_lock_stat (const symsync_state_t *state);

  int symsync_get_locked (const symsync_state_t *state);

  int symsync_configure_lock (symsync_state_t *state, double rolloff,
                              double esno_min_db, double pfa, double pd);

  void symsync_configure_lock_raw (symsync_state_t *state, size_t avgs,
                                   double up_thresh, double down_thresh,
                                   uint32_t n_up, uint32_t n_down);

  int symsync_set_telemetry (symsync_state_t *state, dp_tlm_t *tlm,
                             const char *prefix, uint32_t decim);
/* ── Serializable state (standard bytes interface; see dp_state.h) ──────────
 * pointer-free composition: nco + farrow + loop_filter embedded by value
 * (all POD) + scalar timing state — a whole-struct snapshot. */
#define SYMSYNC_STATE_MAGIC DP_FOURCC ('S', 'Y', 'N', 'C')
#define SYMSYNC_STATE_VERSION                                                 \
  5u /* v5: block-averaged lock_signal statistic (avgs/lock_sum/lock_count)   \
      */
  size_t symsync_state_bytes (const symsync_state_t *state);
  void   symsync_get_state (const symsync_state_t *state, void *blob);
  int    symsync_set_state (symsync_state_t *state, const void *blob);

#ifdef __cplusplus
}
#endif

#endif /* SYMSYNC_CORE_H */