Skip to content

File burst_capture_core.h

FileList > burst_capture > burst_capture_core.h

Go to the source code of this file

BurstCapture — acquisition's output turned into aligned bursts. More...

  • #include "doppler/clib_common.h"
  • #include "doppler/jm_perf.h"
  • #include "doppler/buffer/buffer.h"
  • #include "doppler/dp_state.h"
  • #include "doppler/burst_acq/burst_acq_core.h"
  • #include "doppler/acq/acq_core.h"
  • #include "doppler/corr2d/corr2d_core.h"
  • #include "doppler/fft2d/fft2d_core.h"
  • #include "doppler/fft/fft_core.h"
  • #include "doppler/detection/detection_core.h"
  • #include "doppler/pn/pn_core.h"
  • #include "doppler/cvt/cvt_core.h"

Classes

Type Name
struct burst_capture_detection_t
One raw detection, as the search reported it.
struct burst_capture_event_t
One captured burst's event, as events() hands it back.
struct burst_capture_pending_t
One detection between acquisition and emission.
struct dp_burst_capture_state_t
BurstCapture state.

Public Functions

Type Name
int dp_burst_capture_configure_search_raw (dp_burst_capture_state_t * state, size_t doppler_bins, size_t n_noncoh)
Pin the embedded acquisition's search grid directly.
dp_burst_capture_state_t * dp_burst_capture_create (const float _Complex * preamble, size_t preamble_len, size_t burst_len, 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)
Create a burst capture: acquisition, refine and retention behind one push().
dp_burst_capture_state_t * dp_burst_capture_create_backed (const char * path, const float _Complex * preamble, size_t preamble_len, size_t burst_len, 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)
Create a capture whose look-back lives in a FILE.
void dp_burst_capture_destroy (dp_burst_capture_state_t * state)
Release a capture and everything it owns. NULL-safe.
size_t dp_burst_capture_detections (dp_burst_capture_state_t * state, size_t n, burst_capture_detection_t * out, size_t max_out)
Every hit the search made in the last push(), unfiltered.
size_t dp_burst_capture_detections_max_out (dp_burst_capture_state_t * state, size_t n)
Raw detections available from the last push(). n is ignored.
const burst_capture_event_t * dp_burst_capture_event_at (const dp_burst_capture_state_t * state, size_t i)
Borrow event i of the last push(), or NULL if out of range.
size_t dp_burst_capture_events (dp_burst_capture_state_t * state, size_t n, burst_capture_event_t * out, size_t max_out)
The event record for each burst the last push() returned.
size_t dp_burst_capture_events_max_out (dp_burst_capture_state_t * state, size_t n)
Records available from the last push(). n is ignored.
double dp_burst_capture_get_cn0_dbhz_est (const dp_burst_capture_state_t * state)
size_t dp_burst_capture_get_code_bins (const dp_burst_capture_state_t * state)
Code-phase hypotheses per Doppler row.
size_t dp_burst_capture_get_doppler_bins (const dp_burst_capture_state_t * state)
Doppler hypotheses searched: the coherent depth, or the window-tile count past the native span acq_grid_bins() (doppler#1512).
double dp_burst_capture_get_doppler_hz_est (const dp_burst_capture_state_t * state)
double dp_burst_capture_get_doppler_rate (const dp_burst_capture_state_t * state)
Doppler rate (Hz/s) the coherent depth is bounded against; 0 is no bound.
double dp_burst_capture_get_doppler_rate_hz_s (const dp_burst_capture_state_t * state)
double dp_burst_capture_get_doppler_res_hz (const dp_burst_capture_state_t * state)
double dp_burst_capture_get_doppler_span_hz (const dp_burst_capture_state_t * state)
Unambiguous Doppler half-range, Hz (+/- this).
uint64_t dp_burst_capture_get_dropped (const dp_burst_capture_state_t * state)
double dp_burst_capture_get_eta (const dp_burst_capture_state_t * state)
Coherent detection gate; in force when n_noncoh == 1 .
double dp_burst_capture_get_eta_nc (const dp_burst_capture_state_t * state)
Non-coherent gate; in force only when n_noncoh > 1 , which a burst search never chooses (it reads 0 unless a pin sets looks).
size_t dp_burst_capture_get_min_gap (const dp_burst_capture_state_t * state)
Dead air a caller must leave between bursts, edge to edge.
uint64_t dp_burst_capture_get_n_bursts (const dp_burst_capture_state_t * state)
size_t dp_burst_capture_get_n_noncoh (const dp_burst_capture_state_t * state)
Non-coherent looks combined per decision.
double dp_burst_capture_get_pd_burst (const dp_burst_capture_state_t * state)
Detection probability of one burst: any dwell its preamble spans, at a uniform alignment (doppler#1498). What underpowered reads.
double dp_burst_capture_get_pd_predicted (const dp_burst_capture_state_t * state)
Detection probability of one dwell wholly inside the preamble.
size_t dp_burst_capture_get_pending (const dp_burst_capture_state_t * state)
uint64_t dp_burst_capture_get_preamble_start (const dp_burst_capture_state_t * state)
double dp_burst_capture_get_psl_db (const dp_burst_capture_state_t * state)
The preamble's peak sidelobe level, dB: dp_acq_psl_db() of the engine. A burst clearing the threshold by more than-psl_db also detects its own sidelobe, outside the peak zone; -INFINITY for a perfect sequence.
size_t dp_burst_capture_get_rate_hypotheses (const dp_burst_capture_state_t * state)
Doppler-rate hypotheses searched; 1 is none __doppler_rate then only caps the coherent depth.
void dp_burst_capture_get_state (const dp_burst_capture_state_t * state, void * blob)
Serialize into blob , which must be state_bytes() long.
double dp_burst_capture_get_straddle_loss (const dp_burst_capture_state_t * state)
Correlation kept, worst case, by a burst landing between bins.
size_t dp_burst_capture_push (dp_burst_capture_state_t * state, const float _Complex * x, size_t x_len, float _Complex * out, size_t max_out)
Stream samples; get back every burst whose window has arrived.
size_t dp_burst_capture_push_max_out (dp_burst_capture_state_t * state, size_t x_len)
Upper bound on samples push() can return for x_len input.
size_t dp_burst_capture_ready (const dp_burst_capture_state_t * state)
Windows the last push() completed.
int dp_burst_capture_release (dp_burst_capture_state_t * state, size_t i)
Give back the span that window i of the last push() claimed.
void dp_burst_capture_reset (dp_burst_capture_state_t * state)
Return to the searching state.
int dp_burst_capture_set_state (dp_burst_capture_state_t * state, const void * blob)
Restore from blob .
size_t dp_burst_capture_state_bytes (const dp_burst_capture_state_t * state)
Bytes one blob occupies: a pure function of CONFIGURATION.
const float _Complex * dp_burst_capture_window (const dp_burst_capture_state_t * state, size_t i)
Borrow window i of the last push(), or NULL if out of range.

Macros

Type Name
define BURST_CAPTURE_EDGE_TWINS 4u
define BURST_CAPTURE_HITS 16u
Detections collected from acquisition per batch.
define BURST_CAPTURE_MAX_PHASES 4u
define BURST_CAPTURE_REFINE_INTERP 4u
define BURST_CAPTURE_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('B', 'C', 'A', 'P')
State blob magic — a wrong blob is rejected, not reinterpreted.
define BURST_CAPTURE_STATE_VERSION 6u
State blob layout version. 6: every queued burst and the last event carry refine's winning rate hypothesis (doppler#2219). 5: the blob carries the whole ring, and dropped counts look-back discarded while a burst still needed it, not refused input (doppler#2015). An older blob is refused rather than carried over: a v5 blob, written by v0.66.0 through v0.71.0, is restored by the release that wrote it.

Detailed Description

Between a detector and whatever consumes a burst there is a stage nobody owned: acquisition reports an END anchor and a code phase that is a lag MODULO one code period, so it fixes the alignment WITHIN a repetition and never says WHICH one. A burst has a frame that begins in one specific repetition, so somebody has to resolve the period and reach BACK to a start that has already gone past. This object is that somebody.

It searches, refines, retains, and emits the burst's SAMPLES. It stops there — demodulating, recording, or shipping a window elsewhere is the caller's business. DsssBurstReceiver is this plus BurstDemod.

It OWNS its acquisition engine rather than accepting someone else's results, and that is a correctness choice rather than a convenience one: acq_result_t::samples_consumed is stream-absolute only for an engine fed continuously and never reset, in the caller's own sample coordinates. An object taking foreign results would have to require that and could not check it — and a violated assumption is not a slightly wrong window, it is refine searching the wrong repetition, which returns noise rather than a degraded frame. push() defining the coordinate system makes the invariant internal. See docs/design/dsss-burst-receiver.md §11.

Lifecycle: create, then push() repeatedly, then destroy. There is no step()/steps(): a burst is a frame, not a sample.

// a 31-chip code held 4 samples a chip (bin_to_nrz, then the hold)
uint8_t code[31];
for (size_t i = 0; i < 31; i++) code[i] = (uint8_t)(i & 1u);
float nrz[31];
bin_to_nrz (code, 31, nrz, 31);
float _Complex pre[124];
for (size_t i = 0; i < 124; i++) pre[i] = nrz[i / 4];
dp_burst_capture_state_t *cap = dp_burst_capture_create (
    pre, 124, 4096, 4, 4.0e6, 55.0, 0.0, 1e-3, 0.9, 0, 0.0, 1);
float _Complex x[2048] = { 0 };
float _Complex win[4096];
size_t n = dp_burst_capture_push (cap, x, 2048, win, 4096);
// n is a multiple of burst_len: burst i starts at i*burst_len
dp_burst_capture_destroy (cap);

Public Functions Documentation

function dp_burst_capture_configure_search_raw

Pin the embedded acquisition's search grid directly.

int dp_burst_capture_configure_search_raw (
    dp_burst_capture_state_t * state,
    size_t doppler_bins,
    size_t n_noncoh
) 

The escape hatch for a caller who wants a specific (doppler_bins, n_noncoh). Forwards to the engine, with one refusal of this object's own: a grid whose anchor can lag the preamble by more than refine reaches n_noncoh * doppler_bins code periods against k_lo is rejected rather than accepted and silently mis-refined. Acquisition stamps a hit at the end of the LAST accumulated look, so every look past the one holding the preamble moves the anchor a whole frame later; a burst has one frame of preamble, so n_noncoh = 1 is the grid a capture wants and the sizer now always picks (doppler#1181).

Returns:

DP_OK, or DP_ERR_INVALID if this object or the engine refused the grid.

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> cap.configure_search_raw(4, 1)   # 4 Doppler bins, coherent only

function dp_burst_capture_create

Create a burst capture: acquisition, refine and retention behind one push().

dp_burst_capture_state_t * dp_burst_capture_create (
    const float _Complex * preamble,
    size_t preamble_len,
    size_t burst_len,
    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
) 

Give it the preamble one period of its SAMPLES and the geometry, say how long a burst is, and stream samples in. It searches blindly, recovers the exact preamble start, and hands back the burst's samples once they have all arrived. A doppler_uncertainty wider than the native span +/- fs/(2n) is searched in window tiles, and the event carries the absolute Doppler, tile included (doppler#1512) for a preamble with a single-peak ambiguity, such as a PN code. A Zadoff-Chu preamble cannot be resolved past the native span: its delay-Doppler ridge correlates at full magnitude at (k tiles, k*u^-1 samples) for every k, so keep its Doppler inside the span.

The look-back buffer is NOT a parameter. Its span is derived from the geometry here (detection lag + refine search + the burst itself), because every term is already known and a caller asked to size a history buffer is a caller handed a way to lose bursts silently.

Any repeated preamble: a chirp, a Zadoff-Chu sequence, shaped PSK, or a PN code mapped by dp_bin_to_nrz() and held spc samples a chip (at fs = chip_rate*spc). One chip is one sample: a period is preamble_len samples. Refine correlates each candidate position against the acquisition engine's own reference row the preamble at unit RMS so there is one replica of it (doppler#1470).

Parameters:

  • preamble One period of the preamble, preamble_len samples; not all zero, every sample finite. Read, not kept.
  • preamble_len Samples per repetition (>= 1).
  • burst_len Samples in one burst what gets captured.
  • reps Preamble repetitions (>= 1).
  • fs Sample rate, Hz (> 0); 1 for normalized units.
  • cn0_dbhz C/N0 the search is sized for, dB-Hz, of the preamble's mean power: any finite value, or NaN (ACQ_CN0_NONE) for no design point.
  • doppler_uncertainty Doppler search half-range, Hz (0 = native).
  • pfa Target false-alarm probability, in (0, 1).
  • pd Target detection probability, in (0, 1).
  • noise_mode CFAR reference: 0=mean, 1=median, 2=min, 3=max.
  • doppler_rate Doppler rate, Hz/s (>= 0), that caps the acquisition's coherent depth at f_epoch/sqrt(2*doppler_rate) repetitions (doppler#1482); 0 is no bound. With rate_hypotheses > 1 it is the span instead.
  • rate_hypotheses Doppler-rate hypotheses to search (0 = auto, else 1 to ACQ_RATE_HYPS_MAX; auto picks the fewest the span needs, see dp_acq_create_burst(), and dp_burst_capture_get_rate_hypotheses() reads the choice); 1 searches none and doppler_rate only caps the depth. Above 1 the capture COMPENSATES the rate: doppler_rate is the span [-R, +R], the acquisition de-chirps its coherent block at this many rates across it, and the depth is bounded by the RESIDUAL, R/(n - 1) deeper blocks, more sensitivity, about n times the per-dwell cost, and a false-alarm budget split over the hypotheses (dp_acq_create_burst() owns the semantics). Refine follows the engine: its coherent sum over the preamble's periods tries the rates too, on a finer grid because it integrates the whole preamble, and its Doppler cells widen by the shift a rate puts between the detecting dwell's middle and the preamble's so a burst the engine found by rate is placed by rate. Needs a non-zero doppler_rate (the span). Measured: validate_acq_rate_pd.

Returns:

Heap state, or NULL if any parameter is out of range.

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> cap.burst_len
512
>>> cap.retain_span == cap.refine_span + cap.burst_len
True

A carrier that sweeps caps the coherent depth at its rate; searching
Doppler-rate hypotheses compensates it, and buys the depth back:

>>> import warnings
>>> k = np.arange(127)
>>> zc = np.exp(-1j * np.pi * 5 * k * (k + 1) / 127).astype(np.complex64)
>>> def make(**kw):
...     with warnings.catch_warnings():
...         warnings.simplefilter("ignore")   # under-powered, below
...         return BurstCapture(zc, burst_len=2432, reps=16, fs=1.0,
...                             cn0_dbhz=-12.5, doppler_rate=9e-7, **kw)
>>> make().doppler_bins, make().underpowered      # the rate caps it
(5, True)
>>> c = make(rate_hypotheses=9)
>>> c.rate_hypotheses, c.doppler_bins, c.underpowered
(9, 9, False)

function dp_burst_capture_create_backed

Create a capture whose look-back lives in a FILE.

dp_burst_capture_state_t * dp_burst_capture_create_backed (
    const char * path,
    const float _Complex * preamble,
    size_t preamble_len,
    size_t burst_len,
    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
) 

Same object, same behaviour, one difference in where the history ring's pages come from: they are a MAP_SHARED mapping of path, so the ring's samples ARE the file's contents. There is no copy and no separate flush path — the kernel writes the pages back, and get_state() forces the point so a checkpoint and its history agree.

Two things follow, and they are the reason to reach for this constructor:

  • The blob stops carrying the look-back. For an in-RAM capture the retained history IS the blob (measured 2026-10-10: 8.47 MB at a 1029-symbol frame, 33.64 MB at 8029). Backed, state_bytes() is the detection queue plus the acquisition child (83-88 kB there), because the samples are already durable and the blob only has to name where in the ring they sit.
  • The history outlives the process. Point a new capture at the same path and the samples are there; restore the blob and it reaches back across the restart into a burst that began before it.

The file is created if absent and truncated to the ring's mapped size, which zeroes it. An existing file of exactly that size is adopted as it stands. That size is the capacity in bytes, capacity * sizeof(float _Complex), rounded up to a whole page do not compute it from burst_len. The blob saves exactly the capacity in bytes; on a large-page system the file can be bigger.

A blob from a backed capture does NOT restore into an in-RAM one, or the reverse: state_bytes() differs, so jm's length check rejects it. That is the intent — they are different configurations, and silently accepting one for the other would resume a capture whose history was somewhere else.

Parameters:

  • path File to back the ring with; not NULL and not empty.
  • preamble One period of the preamble, preamble_len samples.
  • preamble_len Samples per repetition (>= 1).
  • burst_len Samples in one burst what gets captured.
  • reps Preamble repetitions (>= 1).
  • fs Sample rate, Hz (> 0).
  • cn0_dbhz C/N0 the search is sized for, dB-Hz: any finite value, or NaN (ACQ_CN0_NONE) for no design point.
  • doppler_uncertainty Doppler search half-range, Hz (0 = native).
  • pfa Target false-alarm probability, in (0, 1).
  • pd Target detection probability, in (0, 1).
  • noise_mode CFAR reference: 0=mean, 1=median, 2=min, 3=max.
  • doppler_rate Doppler rate, Hz/s (>= 0), that caps the acquisition's coherent depth at f_epoch/sqrt(2*doppler_rate) repetitions (doppler#1482); 0 is no bound. With rate_hypotheses > 1 it is the span instead.
  • rate_hypotheses Doppler-rate hypotheses to search; see dp_burst_capture_create().

Returns:

Heap state, or NULL if a parameter is out of range or the file could not be opened, sized or mapped.

>>> import numpy as np, tempfile, os
>>> from doppler.acquire import BurstCapture, PersistentBurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> path = os.path.join(tempfile.mkdtemp(), "ring.cf32")
>>> cap = PersistentBurstCapture(path, pre, burst_len=512,
...                             reps=4, fs=2e6)
>>> ram = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> # the look-back is in the file, so the blob stops carrying the
>>> # ring: twice retain_span rounded up to a power of two, 8 B a sample
>>> ring = 1 << (2 * ram.retain_span - 1).bit_length()
>>> ram.state_bytes() - cap.state_bytes() == ring * 8
True
>>> os.path.getsize(path) >= ring * 8   # rounded up to a page
True

function dp_burst_capture_destroy

Release a capture and everything it owns. NULL-safe.

void dp_burst_capture_destroy (
    dp_burst_capture_state_t * state
) 


function dp_burst_capture_detections

Every hit the search made in the last push(), unfiltered.

size_t dp_burst_capture_detections (
    dp_burst_capture_state_t * state,
    size_t n,
    burst_capture_detection_t * out,
    size_t max_out
) 

BEFORE the claim rule and the suppression window: several rows can name one preamble, and a row can be a false alarm. That is the point this is what acquisition FOUND, and events() is what survived. Valid until the next push(), reset() or set_state().

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> # what the search found, against what became a burst
>>> len(cap.detections()) >= len(cap.events())
True

function dp_burst_capture_detections_max_out

Raw detections available from the last push(). n is ignored.

size_t dp_burst_capture_detections_max_out (
    dp_burst_capture_state_t * state,
    size_t n
) 


function dp_burst_capture_event_at

Borrow event i of the last push(), or NULL if out of range.

const burst_capture_event_t * dp_burst_capture_event_at (
    const dp_burst_capture_state_t * state,
    size_t i
) 


function dp_burst_capture_events

The event record for each burst the last push() returned.

size_t dp_burst_capture_events (
    dp_burst_capture_state_t * state,
    size_t n,
    burst_capture_event_t * out,
    size_t max_out
) 

Row i describes the window at i*burst_len. Valid until the next push(), reset() or set_state().

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> win = cap.push(np.zeros(4096, dtype=np.complex64))
>>> len(cap.events()) == win.size // cap.burst_len
True

function dp_burst_capture_events_max_out

Records available from the last push(). n is ignored.

size_t dp_burst_capture_events_max_out (
    dp_burst_capture_state_t * state,
    size_t n
) 


function dp_burst_capture_get_cn0_dbhz_est

double dp_burst_capture_get_cn0_dbhz_est (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_code_bins

Code-phase hypotheses per Doppler row.

size_t dp_burst_capture_get_code_bins (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_doppler_bins

Doppler hypotheses searched: the coherent depth, or the window-tile count past the native span acq_grid_bins() (doppler#1512).

size_t dp_burst_capture_get_doppler_bins (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_doppler_hz_est

double dp_burst_capture_get_doppler_hz_est (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_doppler_rate

Doppler rate (Hz/s) the coherent depth is bounded against; 0 is no bound.

double dp_burst_capture_get_doppler_rate (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_doppler_rate_hz_s

double dp_burst_capture_get_doppler_rate_hz_s (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_doppler_res_hz

double dp_burst_capture_get_doppler_res_hz (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_doppler_span_hz

Unambiguous Doppler half-range, Hz (+/- this).

double dp_burst_capture_get_doppler_span_hz (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_dropped

uint64_t dp_burst_capture_get_dropped (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_eta

Coherent detection gate; in force when n_noncoh == 1 .

double dp_burst_capture_get_eta (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_eta_nc

Non-coherent gate; in force only when n_noncoh > 1 , which a burst search never chooses (it reads 0 unless a pin sets looks).

double dp_burst_capture_get_eta_nc (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_min_gap

Dead air a caller must leave between bursts, edge to edge.

size_t dp_burst_capture_get_min_gap (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_n_bursts

uint64_t dp_burst_capture_get_n_bursts (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_n_noncoh

Non-coherent looks combined per decision.

size_t dp_burst_capture_get_n_noncoh (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_pd_burst

Detection probability of one burst: any dwell its preamble spans, at a uniform alignment (doppler#1498). What underpowered reads.

double dp_burst_capture_get_pd_burst (
    const dp_burst_capture_state_t * state
) 

It models the ENGINE; what refine loses afterwards is not in the model. Measured on a Zadoff-Chu 127 x 8 preamble at every depth, the capture delivers at least this at a 0.6 design point and at 0.9 (the default pd): its loss against the engine is refine naming the wrong repetition, about 2% of trials (native/validation/capture_dwell_pd.c; validation report §2.8). Until doppler#1519 it also lost a code phase along the ZC delay-Doppler ridge and fell 0.026 short at 0.9.


function dp_burst_capture_get_pd_predicted

Detection probability of one dwell wholly inside the preamble.

double dp_burst_capture_get_pd_predicted (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_pending

size_t dp_burst_capture_get_pending (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_preamble_start

uint64_t dp_burst_capture_get_preamble_start (
    const dp_burst_capture_state_t * state
) 

function dp_burst_capture_get_psl_db

The preamble's peak sidelobe level, dB: dp_acq_psl_db() of the engine. A burst clearing the threshold by more than-psl_db also detects its own sidelobe, outside the peak zone; -INFINITY for a perfect sequence.

double dp_burst_capture_get_psl_db (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_rate_hypotheses

Doppler-rate hypotheses searched; 1 is none __doppler_rate then only caps the coherent depth.

size_t dp_burst_capture_get_rate_hypotheses (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_get_state

Serialize into blob , which must be state_bytes() long.

void dp_burst_capture_get_state (
    const dp_burst_capture_state_t * state,
    void * blob
) 


function dp_burst_capture_get_straddle_loss

Correlation kept, worst case, by a burst landing between bins.

double dp_burst_capture_get_straddle_loss (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_push

Stream samples; get back every burst whose window has arrived.

size_t dp_burst_capture_push (
    dp_burst_capture_state_t * state,
    const float _Complex * x,
    size_t x_len,
    float _Complex * out,
    size_t max_out
) 

Windows are concatenated: burst i occupies burst_len samples starting at i*burst_len, and events() returns the matching record for each. Every sample of x is consumed. An empty return is normal it means no burst completed in this call.

It aborts the process in two places, both a defect in this object and neither reachable from any input or from any blob set_state() accepts: a history ring with no room after trim (the bound in the implementation's trim proves room), and an acquisition call that took nothing while input was left (each call starts with its result empty, and a push with its result empty takes at least the next frame, or the rest as carry). Each stops rather than spinning forever and in a Python host it ends the interpreter.

Parameters:

  • state Capture.
  • x Input samples, x_len long.
  • x_len Samples in x.
  • out Written with the completed windows; may be NULL to drop.
  • max_out Capacity of out, in samples.

Returns:

Samples written always a multiple of burst_len.

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> win = cap.push(np.zeros(4096, dtype=np.complex64))
>>> win.size % cap.burst_len        # whole windows, never a partial
0
>>> win.size                        # silence, so no burst completed
0

function dp_burst_capture_push_max_out

Upper bound on samples push() can return for x_len input.

size_t dp_burst_capture_push_max_out (
    dp_burst_capture_state_t * state,
    size_t x_len
) 

Distinct bursts cannot overlap, so x_len samples complete at most x_len/burst_len + 1 of them, plus whatever is already queued.


function dp_burst_capture_ready

Windows the last push() completed.

size_t dp_burst_capture_ready (
    const dp_burst_capture_state_t * state
) 

The C consumer's face, and the reason a composing object pays no second copy: dp_burst_capture_window() borrows straight out of the scratch that push() filled.


function dp_burst_capture_release

Give back the span that window i of the last push() claimed.

int dp_burst_capture_release (
    dp_burst_capture_state_t * state,
    size_t i
) 

An emitted window owns its whole span: a detection inside it is the payload firing against the acquisition code, not a new burst, so it is HELD rather than reported. Whether the window WAS a burst is a verdict this object cannot reach it stops at samples; error detection, whatever form the frame gives it, is the consumer's so a consumer that knows better calls this for that window, and the held detections are searched again on the next push(). Unreleased, they are dropped when the next push() begins, which is exactly the behaviour a consumer with no verdict always had.

What it prevents (doppler#1181): a spurious window ending just after a real burst begins used to swallow that burst's first detections the receiver's own design says only a DECODED burst may own a span (§10.3, doppler#1004), and the capture underneath had been owning it on emission.

Must be called BEFORE the next push(): i indexes THIS push's windows.

Returns:

DP_OK, or DP_ERR_INVALID if i is not a window of the last push().

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> cap.release(0)   # no window 0 in a quiet push
Traceback (most recent call last):
  ...
ValueError: release failed (rc=-4)

function dp_burst_capture_reset

Return to the searching state.

void dp_burst_capture_reset (
    dp_burst_capture_state_t * state
) 

Resets the embedded acquisition, rewinds the history ring, clears every queued detection and every read-back. Construction parameters are untouched; dropped deliberately survives, because a lost burst stays lost.

>>> import numpy as np
>>> from doppler.acquire import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> pre = np.repeat(np.where(code, -1.0, 1.0), 2).astype(np.complex64)
>>> cap = BurstCapture(pre, burst_len=512, reps=4, fs=2e6)
>>> cap.push(np.zeros(4096, dtype=np.complex64)).size
0
>>> cap.reset()
>>> cap.pending
0

function dp_burst_capture_set_state

Restore from blob .

int dp_burst_capture_set_state (
    dp_burst_capture_state_t * state,
    const void * blob
) 

Returns:

DP_OK or DP_ERR_INVALID.

A refusal writes nothing of the capture: every check runs before any of it is written. The acquisition child is restored to check where it stands, and on a mismatch handed its own blob back, which resets the acq read-backs no blob carries (dwells, surface_at). So the capture's next get_state() is byte-identical to its last, and its next push() is the one it would have made.

A wrong-object, wrong-version, wrong-size or foreign-endian blob is refused, never reinterpreted; so is a blob from the other flavour (a backed and an in-RAM capture have different state_bytes()). A backed capture restores POSITIONS only the samples are the file's so it also refuses a blob whose retained span the file cannot hold: a file create() made fresh that this capture has not written that far into, or a span the ring has since wrapped past (more than the ring's capacity pushed since the checkpoint). A capture restoring a checkpoint it took itself is the normal case and is accepted (doppler#1190): set_state(blob) -> push(chunk) -> get_state() per call is a service shape this object supports, on both flavours.

The blob holds all the look-back the ring held, so a capture resumes with every burst it would have emitted. A blob is checked rather than trusted: a stream position below 2^63 and a suppression span that ends by it; the queue's phases within their array, its readings finite, its anchors in order and none past the stream position, and a refined start within the range refine chooses from; a look-back no longer than the stream; and an acquisition child that fits its region and stands at the same stream position as the capture. A queued entry its look-back cannot reach is dropped counted in dropped if it was a burst, uncounted if it was shadowed as a forged blob can name one.


function dp_burst_capture_state_bytes

Bytes one blob occupies: a pure function of CONFIGURATION.

size_t dp_burst_capture_state_bytes (
    const dp_burst_capture_state_t * state
) 


function dp_burst_capture_window

Borrow window i of the last push(), or NULL if out of range.

const float _Complex * dp_burst_capture_window (
    const dp_burst_capture_state_t * state,
    size_t i
) 

Contiguous, burst_len samples, valid until the next push(), reset() or set_state(). The caller must not free it.


Macro Definition Documentation

define BURST_CAPTURE_EDGE_TWINS

#define BURST_CAPTURE_EDGE_TWINS `4u`

Extra Doppler cells refine scores at the other alias when a cell sits at the edge of the native span (doppler#1519).


define BURST_CAPTURE_HITS

Detections collected from acquisition per batch.

#define BURST_CAPTURE_HITS `16u`

A BATCHING parameter, never a correctness one: push() keeps looping until acq has absorbed the whole chunk, offering it the rest again after every call, so a smaller array means more iterations and nothing else.


define BURST_CAPTURE_MAX_PHASES

#define BURST_CAPTURE_MAX_PHASES `4u`

Distinct code phases a pending burst remembers for refine to score. Two is what a Zadoff-Chu preamble at the edge of the native span produces (doppler#1519); the rest is room for a false alarm merged into the same burst. A phase past the cap is not recorded, and refine then behaves as it did before phases were kept.


define BURST_CAPTURE_REFINE_INTERP

#define BURST_CAPTURE_REFINE_INTERP `4u`

Refine's Doppler cells per native bin at depth reps: twice acquisition's own zero-padding. Choosing a repetition compares two close scores, and the straddle that costs a detection little tips that comparison: measured on Zadoff-Chu 127 x 8, 2 cells a bin chose the wrong period for 45 of 656 engine hits at D = 5, 4 for 26, and 8 for 25 (doppler#1502).


define BURST_CAPTURE_STATE_MAGIC

State blob magic — a wrong blob is rejected, not reinterpreted.

#define BURST_CAPTURE_STATE_MAGIC `DP_FOURCC ('B', 'C', 'A', 'P')`


define BURST_CAPTURE_STATE_VERSION

State blob layout version. 6: every queued burst and the last event carry refine's winning rate hypothesis (doppler#2219). 5: the blob carries the whole ring, and dropped counts look-back discarded while a burst still needed it, not refused input (doppler#2015). An older blob is refused rather than carried over: a v5 blob, written by v0.66.0 through v0.71.0, is restored by the release that wrote it.

#define BURST_CAPTURE_STATE_VERSION `6u`



The documentation for this class was generated from the following file native/inc/doppler/burst_capture/burst_capture_core.h