Skip to content

File acq_core.h

File List > acq > acq_core.h

Go to the documentation of this file

#ifndef DP_ACQ_CORE_H
#define DP_ACQ_CORE_H

#include "doppler/clib_common.h"
#include "doppler/corr2d/corr2d_core.h"
#include "doppler/detection/detection_core.h"
#include "doppler/dp_state.h"
#include "doppler/f32_buffer/f32_buffer_core.h"
#include "doppler/fft/fft_core.h"
#include "doppler/jm_perf.h"
/* detector2d_core.h supplies det_noise_mode_t (guarded typedef). */
#include "doppler/detector2d/detector2d_core.h"
#include "doppler/fft2d/fft2d_core.h"
#include "doppler/dp_parallel.h"
#include "doppler/dp_tlm/dp_tlm_core.h"
#include "doppler/cvt/cvt_core.h"

#ifdef __cplusplus
extern "C"
{
#endif

  typedef struct
  {
    size_t doppler_bin; 
    size_t code_phase; 
    float  peak_mag;   
    float  noise_est;  
    float  test_stat;  
    float cn0_dbhz_est;        
    uint64_t samples_consumed; 
    double chip_phase; 
    double doppler_rate_hz_s; 
  } acq_result_t;

  typedef struct
  {
    dp_tlm_t *ctx;      
    int32_t id_stat;    
    int32_t id_gate;    
    int32_t id_noise;   
    int32_t id_peak;    
    int32_t id_row;     
    int32_t id_col;     
    int32_t id_n_peaks; 
    int32_t id_n_held;  
    int32_t id_conc;    
    int32_t id_hit;     
  } acq_tlm_t;

  typedef void (*acq_surface_sink_fn) (void *ctx, const float *surface,
                                       size_t rows, size_t cols,
                                       uint64_t samples_consumed);

  typedef struct
  {
    float  ref;  
    size_t best; 
  } acq_part_t;

#define ACQ_DELAY_LOSS_NODES 4

  typedef struct
  {
    size_t zone; 
    double delay_loss_mean; 
    double delay_loss[ACQ_DELAY_LOSS_NODES]; 
    double off_peak; 
    double off_peak_amp; 
    double psl; 
    double dsl; 
  } acq_shape_t;

  typedef struct
  {
    dp_corr2d_state_t *corr; 
    dp_fft_state_t *slow_fft; 
    dp_f32_t    *ring;  
    dp_f32_framer_t framer; 
    size_t consumed;        
    float _Complex *ref; 
    float _Complex *yframe;  
    float _Complex *colbuf;  
    float _Complex *colout;  
    float _Complex *out_buf; 
    float *mag_buf;       
    float *noise_scratch; 
    float *nc_surface;    
    /* Wideband mode only (window_bins > 1) — see the file doc comment.
     * Independent of corr/slow_fft/yframe/colbuf/colout above, which this
     * mode does not run: a tiled engine's slow-time transform is the per-tile
     * plans (tile_slow/tile_col). They are still ALLOCATED, at the sizes
     * acq_regrid() gives them -- slow_fft and colbuf at coherent_bins *
     * interp, a one-row correlator -- so with a depth inside the tiles they
     * are not the trivial coherent_bins = 1 size. */
    dp_fft_state_t *wide_fwd; 
    dp_fft_state_t *wide_inv; 
    float _Complex
        *wide_ref_spec; 
    float _Complex
        *wide_spec; 
    float _Complex *wide_prod; 
    size_t
        coherent_bins; 
    size_t window_bins; 
    size_t code_bins; 
    size_t n; 
    size_t n_surf; 
    size_t interp; 
    size_t frame_n; 
    size_t sf;      
    size_t spc;     
    acq_shape_t shape; 
    size_t reps;    
    size_t
        searched_bins; 
    size_t n_noncoh;   
    size_t nc_count; 
    size_t ring_cap; 
    size_t noise_lo; 
    size_t noise_hi; 
    det_noise_mode_t noise_mode; 
    double chip_rate; 
    double fs;        
    double cn0_dbhz;  
    double
        doppler_span_hz; 
    double
        doppler_res_hz; 
    double pfa;         
    double doppler_uncertainty; 
    size_t dsl_window_bins; 
    int dsl_ready; 
    double symbol_rate; 
    double epochs_per_symbol;  
    size_t code_only_epochs; 
    double doppler_rate; 
    size_t rate_hyps;    
    float _Complex *rate_chirp; 
    size_t rate_chirp_d; 
    float _Complex *rate_best; 
    size_t rate_best_cap;      
    size_t rate_best_h;  
    double carrier_freq_hz; 
    float _Complex *blk; 
    float _Complex *blk_raw; 
    size_t blk_epoch;    
    /* The roll per thread (design §2.3): the tiles are independent after
       the one forward transform, so the per-epoch tile loop and the
       block-end column loop run through a persistent pool. The scratch is
       PER TILE, not per thread -- a pocketfft plan carries its own work
       buffers, and a tile lands on whichever worker takes it -- so the
       serial and the fanned paths run the same code on the same buffers
       and the surface is bit-identical either way. */
    dp_pool_t       *pool;      
    int              threads;   
    dp_fft_state_t    **tile_inv;  
    float _Complex **tile_prod; 
    dp_fft_state_t    **tile_slow; 
    float _Complex **tile_col;  
    size_t **tile_rows;         
    acq_part_t *parts;          
    float  threshold; 
    float  eta;       
    float  eta_nc;    
    double pfa_cell;  
    double pd;        
    double pd_predicted;  
    double pd_burst; 
    double straddle_loss; 
    uint8_t underpowered; 
    uint8_t burst; 
    uint64_t
        samples_consumed; 
    /* The peak list (docs/design/async-dsss-receiver.md §7.1): up to
       `max_peaks` peaks per dwell, each above the same gate, strongest
       first, with an exclusion zone of one Doppler row (`interp` surface
       rows) by the reference's first autocorrelation null (`shape.zone`
       columns; one chip for a PN code), circular, around each; every
       listed peak is one acq_result_t. `band_mask` marks the cells outside
       the searched Doppler band (rebuilt with the thresholds); `peak_mask`
       is the per-dwell working copy the list marks its zones into. The
       two-epoch rule: a peak within a chip of an already-listed peak's code
       phase, at any row, is that emitter's candidate twin -- held, not
       listed, unless it was there at the same row on the previous dwell,
       listed or held. `twin_*` are the previous dwell's picks (native
       rows). */
    size_t      max_peaks; 
    size_t      n_peaks;   
    det_peak_t *peaks;     
    uint8_t    *band_mask; 
    uint8_t    *peak_mask; 
    uint32_t   *twin_row;  
    uint32_t   *twin_col;  
    size_t      n_twins;   
    /* Observability (design §2.4): attach-on-demand, nothing in blobs. */
    acq_tlm_t tlm;       
    int       keep_surface; 
    float *stat_surface; 
    uint64_t surface_at; 
    uint64_t dwells;     
    acq_surface_sink_fn sink; 
    void    *sink_ctx;
    uint32_t sink_decim; 
    size_t   n_held;     
    float    peak_conc;  
    /* Last-dump bookkeeping (for inspection): the strongest pick. */
    size_t peak_row;
    size_t peak_col;
    float  peak_mag;
    float  noise_est;
    float  test_stat;
  } dp_acq_state_t;

  typedef struct
  {
    uint16_t has_nc; 
    uint16_t _pad;
    uint32_t n_noncoh;         
    uint64_t n;                
    uint64_t samples_consumed; 
    uint32_t nc_count;     
    uint32_t max_peaks;    
    uint32_t n_twins;      
    uint32_t blk_epoch; 
    uint32_t rate_hyps; 
    uint32_t _pad2;
  } acq_extra_t;

#define ACQ_STATE_MAGIC DP_FOURCC ('A', 'C', 'Q', 'R')
/* v5: the carry is the framer's snapshot (fewer than frame_n samples)
   instead of the ring's raw contents, zero-padded to ring_cap.
   v6: acq_extra_t carries the rate-hypothesis count. */
#define ACQ_STATE_VERSION 6u

#define ACQ_MAX_PEAKS 64u

#define ACQ_COL_CHUNK 32u

#define ACQ_RATE_HYPS_MAX 64u

#define ACQ_N_NONCOH_SAFETY_CEILING 256u

#define ACQ_CN0_NONE NAN

  dp_acq_state_t *dp_acq_create_burst (const float _Complex *tmpl, size_t n,
                                 size_t reps, double fs, double cn0_dbhz,
                                 double doppler_uncertainty, double pfa,
                                 double pd, int noise_mode,
                                 double doppler_rate,
                                 size_t rate_hypotheses);

  dp_acq_state_t *dp_acq_create_continuous (const uint8_t *code, size_t code_len,
                                      size_t spc, double chip_rate,
                                      double symbol_rate, double cn0_dbhz,
                                      double doppler_uncertainty, double pfa,
                                      double pd, int noise_mode,
                                      size_t code_only_epochs,
                                      double doppler_rate);

  double dp_acq_rate_hypothesis (double span, size_t n, size_t h);

  double _Complex dp_acq_rate_phasor (double rate, double t);

  size_t dp_acq_rate_hypotheses_for (double f_epoch, double span,
                                     size_t depth);

  void dp_acq_destroy (dp_acq_state_t *state);

  void dp_acq_reset (dp_acq_state_t *state);

  int dp_acq_configure_search_raw (dp_acq_state_t *state, size_t doppler_bins,
                                size_t n_noncoh);

  int dp_acq_set_max_peaks (dp_acq_state_t *state, size_t n);

  int dp_acq_set_carrier_freq_hz (dp_acq_state_t *state, double carrier_freq_hz);

  int dp_acq_carrier_freq_ok (double carrier_freq_hz, double sample_rate_hz);

  int dp_acq_set_threads (dp_acq_state_t *state, int n);
  int dp_acq_set_telemetry (dp_acq_state_t *state, dp_tlm_t *tlm,
                         const char *prefix, uint32_t decim);

  size_t dp_acq_surface (dp_acq_state_t *state, float *out, size_t n_out);

  size_t dp_acq_surface_doppler_hz (dp_acq_state_t *state, double *out,
                                 size_t n_out);

  size_t dp_acq_surface_chip_phase (dp_acq_state_t *state, double *out,
                                 size_t n_out);

  size_t dp_acq_surface_complex (dp_acq_state_t *state, float _Complex *out,
                              size_t n_out);

  double _Complex dp_acq_cell_corr (const dp_acq_state_t *state,
                                 const float _Complex *x, size_t col,
                                 double f_hz, double t0);

  void dp_acq_cell_corr_grid (const dp_acq_state_t *state, const float _Complex *x,
                           size_t n_epochs, size_t col, const double *f_hz,
                           size_t n_f, double t0, double _Complex *out);

  size_t dp_acq_block_prompt (dp_acq_state_t *state, size_t tile, size_t col,
                           float _Complex *out, size_t n_out);

  size_t dp_acq_block_raw (dp_acq_state_t *state, float _Complex *out,
                        size_t n_out);

  void dp_acq_set_surface_sink (dp_acq_state_t *state, acq_surface_sink_fn fn,
                             void *ctx, uint32_t decim);

  size_t dp_acq_push (dp_acq_state_t *state, const float _Complex *x, size_t n_in,
                   acq_result_t *result, size_t max_results);

  size_t dp_acq_consumed (const dp_acq_state_t *state);

  uint64_t dp_acq_position (const dp_acq_state_t *state);

  typedef struct
  {
    uint64_t samples_consumed; 
    double
        chip_phase; 
    double doppler_hz_est;  
    double doppler_res_hz;  
    double cn0_dbhz_est;    
    float  peak_mag;        
    float  noise_est;       
    float  test_stat;       
  } acq_handoff_t;

  /* The FFT-bin convention this engine reports in -- `0 = DC`, ascending
   * positive, then wrapping negative -- is `dp_fftfreq_index()` in
   * clib_common.h, and its doc comment there is the one definition. It was
   * declared here, and four call sites outside C restated the fold in three
   * mutually inconsistent ways; the engine's wideband search and its own
   * hand-off were two of them, which surfaced as a receiver reporting
   * `tracking == 1` while decoding noise. Every consumer -- this engine's
   * search, its hand-off, and any composing receiver -- now includes the
   * SAME inline rather than restating the formula. */

  static inline size_t
  acq_grid_bins (const dp_acq_state_t *state)
  {
    return state->window_bins * state->coherent_bins;
  }

  static inline double
  acq_bin_doppler_hz (const dp_acq_state_t *state, size_t doppler_bin)
  {
    return (double)dp_fftfreq_index (doppler_bin, acq_grid_bins (state))
           * state->doppler_res_hz;
  }

  double dp_acq_psl_db (const dp_acq_state_t *state);

  double dp_acq_dsl_db (dp_acq_state_t *state);

  double dp_acq_gate_stat (const dp_acq_state_t *state);

  void dp_acq_build_handoff (const dp_acq_state_t *state, const acq_result_t *hit,
                          size_t code_len, size_t spc, acq_handoff_t *out);

  /* ── Serializable state — the elastic / pure-transducer face
   * ─────────────────
   *
   * The OO engine above is convenient but stateful.  These match the rest of
   * the library's serializable objects (lo/cic/fir/ddcr): serialize a
   * channel's cross-call state to a flat POD, ship (descriptor, state, input)
   * to any thread/process/pod, rebuild the engine from the descriptor
   * (acq_create), inject the state, and continue — bit-identical to an
   * uninterrupted run.
   */

  size_t dp_acq_state_bytes (const dp_acq_state_t *state);

  void dp_acq_get_state (const dp_acq_state_t *state, void *blob);

  int dp_acq_set_state (dp_acq_state_t *state, const void *blob);

  size_t dp_acq_run (dp_acq_state_t *state, const void *state_in, void *state_out,
                  const float _Complex *in, size_t n_in, acq_result_t *result,
                  size_t max_results);

#ifdef __cplusplus
}
#endif

#endif /* ACQ_CORE_H */