Skip to content

File doppler_channel_core.h

File List > doppler > doppler_channel > doppler_channel_core.h

Go to the documentation of this file

#ifndef DP_DOPPLER_CHANNEL_CORE_H
#define DP_DOPPLER_CHANNEL_CORE_H

#include <stdint.h>

#include "doppler/clib_common.h"
#include "doppler/dp_state.h"
#include "doppler/jm_perf.h"
#include "doppler/resamp/resamp_core.h"
#ifdef __cplusplus
extern "C" {
#endif

#define DOPPLER_CHANNEL_STATE_MAGIC DP_FOURCC('D', 'P', 'C', 'H')
#define DOPPLER_CHANNEL_STATE_VERSION 2u

#define DOPPLER_CHANNEL_MAX_BLOCK 65536u

typedef struct {
    double fs;                 /* receive sample rate, Hz                  */
    double carrier_hz;         /* RF carrier fc, Hz — drives the offset    */
    double doppler_ppm;        /* d0, ppm of nominal                       */
    double doppler_rate_ppm_s; /* d-dot, ppm/s                             */

    resamp_state_t *rs; /* the dilation — never hand-rolled here     */

    /* Two separate clocks, on purpose. The resampler's per-sample rate
       deviation is indexed by INPUT sample; the carrier phase is a function of
       receive time, which is the OUTPUT sample index. They differ by the
       dilation itself, so conflating them would fold a second copy of the
       Doppler into the carrier. */
    uint64_t n_in;  /* input samples consumed                    */
    uint64_t n_out; /* output samples produced                   */

    double *ctrl;         /* per-sample rate deviation scratch         */
    size_t ctrl_cap;

    /* Profile mode (dp_doppler_channel_execute_profile). The carrier needs no
       state of its own there: it is read off the resampler's position (see
       dp_resamp_execute_ctrl_pos), which the resampler blob and the two
       clocks already carry. What a profile does leave behind is the most
       recent instantaneous d, the one thing the closed form can no longer
       report -- hence these two, and the layout version. */
    double  prof_d;   /* most recent instantaneous d (dimensionless) */
    uint8_t profiled; /* a profile has driven this stream            */

    double *pos;      /* per-output input position scratch (not state) */
    size_t  pos_cap;
} dp_doppler_channel_state_t;

static inline double
doppler_channel_excess(const dp_doppler_channel_state_t *s, double t)
{
    return (s->doppler_ppm * t + 0.5 * s->doppler_rate_ppm_s * t * t) * 1e-6;
}

static inline double
doppler_channel_scale(const dp_doppler_channel_state_t *s, double t)
{
    return 1.0 + (s->doppler_ppm + s->doppler_rate_ppm_s * t) * 1e-6;
}

static inline double
doppler_channel_phase(const dp_doppler_channel_state_t *s, double t)
{
    return s->carrier_hz * doppler_channel_excess(s, t);
}

dp_doppler_channel_state_t *dp_doppler_channel_create(double fs, double carrier_hz, double doppler_ppm, double doppler_rate_ppm_s);

void dp_doppler_channel_destroy(dp_doppler_channel_state_t *state);

void dp_doppler_channel_reset(dp_doppler_channel_state_t *state);

size_t dp_doppler_channel_state_bytes(const dp_doppler_channel_state_t *state);

void dp_doppler_channel_get_state(const dp_doppler_channel_state_t *state, void *blob);

int dp_doppler_channel_set_state(dp_doppler_channel_state_t *state, const void *blob);

size_t dp_doppler_channel_execute_max_out(dp_doppler_channel_state_t *state);

size_t dp_doppler_channel_execute(dp_doppler_channel_state_t *state, const float _Complex *x, size_t x_len, float _Complex *out, size_t max_out);

size_t dp_doppler_channel_execute_profile_max_out(dp_doppler_channel_state_t *state, size_t n);

int64_t dp_doppler_channel_execute_profile(dp_doppler_channel_state_t *state, const float _Complex *x, size_t x_len, const double *ppm, size_t ppm_len, float _Complex *out, size_t max_out);

double dp_doppler_channel_get_elapsed_s(const dp_doppler_channel_state_t *state);

double dp_doppler_channel_get_offset_hz(const dp_doppler_channel_state_t *state);

double dp_doppler_channel_get_delay_samples(const dp_doppler_channel_state_t *state);
#ifdef __cplusplus
}
#endif

#endif /* DOPPLER_CHANNEL_CORE_H */