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 "clib_common.h"
  • #include "jm_perf.h"
  • #include "buffer/buffer.h"
  • #include "dp_state.h"
  • #include "burst_acq/burst_acq_core.h"
  • #include "acq/acq_core.h"
  • #include "corr2d/corr2d_core.h"
  • #include "fft2d/fft2d_core.h"
  • #include "fft/fft_core.h"
  • #include "detection/detection_core.h"
  • #include "pn/pn_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 burst_capture_state_t
BurstCapture state.

Public Functions

Type Name
int burst_capture_configure_search_raw (burst_capture_state_t * state, size_t doppler_bins, size_t n_noncoh)
Pin the embedded acquisition's search grid directly.
burst_capture_state_t * burst_capture_create (const uint8_t * acq_code, size_t acq_code_len, size_t burst_len, size_t reps, size_t spc, double chip_rate, double cn0_dbhz, double doppler_uncertainty, double pfa, double pd, int noise_mode)
Create a burst capture: acquisition, refine and retention behind one push().
burst_capture_state_t * burst_capture_create_backed (const char * path, const uint8_t * acq_code, size_t acq_code_len, size_t burst_len, size_t reps, size_t spc, double chip_rate, double cn0_dbhz, double doppler_uncertainty, double pfa, double pd, int noise_mode)
Create a capture whose look-back lives in a FILE.
void burst_capture_destroy (burst_capture_state_t * state)
Release a capture and everything it owns. NULL-safe.
size_t burst_capture_detections (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 burst_capture_detections_max_out (burst_capture_state_t * state, size_t n)
Raw detections available from the last push(). n is ignored.
const burst_capture_event_t * burst_capture_event_at (const burst_capture_state_t * state, size_t i)
Borrow event i of the last push(), or NULL if out of range.
size_t burst_capture_events (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 burst_capture_events_max_out (burst_capture_state_t * state, size_t n)
Records available from the last push(). n is ignored.
double burst_capture_get_cn0_dbhz_est (const burst_capture_state_t * state)
size_t burst_capture_get_code_bins (const burst_capture_state_t * state)
Code-phase hypotheses per Doppler row.
size_t burst_capture_get_doppler_bins (const burst_capture_state_t * state)
Doppler hypotheses searched (the coherent depth).
double burst_capture_get_doppler_hz_est (const burst_capture_state_t * state)
double burst_capture_get_doppler_res_hz (const burst_capture_state_t * state)
double burst_capture_get_doppler_span_hz (const burst_capture_state_t * state)
Unambiguous Doppler half-range, Hz (+/- this).
uint64_t burst_capture_get_dropped (const burst_capture_state_t * state)
double burst_capture_get_eta (const burst_capture_state_t * state)
Coherent detection gate; in force when n_noncoh == 1 .
double burst_capture_get_eta_nc (const burst_capture_state_t * state)
Non-coherent gate; in force when n_noncoh > 1 (the usual case).
size_t burst_capture_get_min_gap (const burst_capture_state_t * state)
Dead air a caller must leave between bursts, edge to edge.
uint64_t burst_capture_get_n_bursts (const burst_capture_state_t * state)
size_t burst_capture_get_n_noncoh (const burst_capture_state_t * state)
Non-coherent looks combined per decision.
double burst_capture_get_pd_predicted (const burst_capture_state_t * state)
Detection probability the sized grid actually predicts.
size_t burst_capture_get_pending (const burst_capture_state_t * state)
uint64_t burst_capture_get_preamble_start (const burst_capture_state_t * state)
double burst_capture_get_refine_margin (const burst_capture_state_t * state)
void burst_capture_get_state (const burst_capture_state_t * state, void * blob)
Serialize into blob , which must be state_bytes() long.
double burst_capture_get_straddle_loss (const burst_capture_state_t * state)
Correlation kept, worst case, by a burst landing between bins.
size_t burst_capture_push (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 burst_capture_push_max_out (burst_capture_state_t * state, size_t x_len)
Upper bound on samples push() can return for x_len input.
size_t burst_capture_ready (const burst_capture_state_t * state)
Windows the last push() completed.
int burst_capture_release (burst_capture_state_t * state, size_t i)
Give back the span that window i of the last push() claimed.
void burst_capture_reset (burst_capture_state_t * state)
Return to the searching state.
int burst_capture_set_state (burst_capture_state_t * state, const void * blob)
Restore from blob .
size_t burst_capture_state_bytes (const burst_capture_state_t * state)
Bytes one blob occupies: a pure function of CONFIGURATION.
const float _Complex * burst_capture_window (const 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_HITS 16u
Detections collected from acquisition per batch.
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 2u
State blob layout version.

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.

uint8_t code[31];
for (size_t i = 0; i < 31; i++) code[i] = (uint8_t)(i & 1u);
burst_capture_state_t *cap = burst_capture_create (
    code, 31, 4096, 4, 4, 1.0e6, 55.0, 0.0, 1e-3, 0.9, 0);
float _Complex x[2048] = { 0 };
float _Complex win[4096];
size_t n = burst_capture_push (cap, x, 2048, win, 4096);
// n is a multiple of burst_len: burst i starts at i*burst_len
burst_capture_destroy (cap);

Public Functions Documentation

function burst_capture_configure_search_raw

Pin the embedded acquisition's search grid directly.

int burst_capture_configure_search_raw (
    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.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> cap.configure_search_raw(4, 1)   # 4 Doppler bins, coherent only

function burst_capture_create

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

burst_capture_state_t * burst_capture_create (
    const uint8_t * acq_code,
    size_t acq_code_len,
    size_t burst_len,
    size_t reps,
    size_t spc,
    double chip_rate,
    double cn0_dbhz,
    double doppler_uncertainty,
    double pfa,
    double pd,
    int noise_mode
) 

Give it the preamble code 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.

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.

Parameters:

  • acq_code Preamble PN chips (0/1), length acq_code_len.
  • acq_code_len Preamble code length, chips.
  • burst_len Samples in one burst what gets captured.
  • reps Preamble code repetitions.
  • spc Samples per chip.
  • chip_rate Chip rate, Hz.
  • cn0_dbhz C/N0 the search is sized for, dB-Hz.
  • 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.

Returns:

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

>>> import numpy as np
>>> from doppler.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> cap.burst_len
512
>>> cap.retain_span == cap.refine_span + cap.burst_len
True

function burst_capture_create_backed

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

burst_capture_state_t * burst_capture_create_backed (
    const char * path,
    const uint8_t * acq_code,
    size_t acq_code_len,
    size_t burst_len,
    size_t reps,
    size_t spc,
    double chip_rate,
    double cn0_dbhz,
    double doppler_uncertainty,
    double pfa,
    double pd,
    int noise_mode
) 

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: 2.57 MB at a 1029-symbol frame, 16.68 MB at 8029). Backed, state_bytes() is a few hundred bytes plus the acquisition child, 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 byte size, which zeroes it. An existing file of exactly that size is adopted as it stands. Because the capacity rounds up to a page, that size is capacity * sizeof(float _Complex) — do not compute it from burst_len.

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.
  • acq_code Preamble PN chips (0/1), length acq_code_len.
  • acq_code_len Preamble code length, chips.
  • burst_len Samples in one burst what gets captured.
  • reps Preamble code repetitions.
  • spc Samples per chip.
  • chip_rate Chip rate, Hz.
  • cn0_dbhz C/N0 the search is sized for, dB-Hz.
  • 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.

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.dsss import BurstCapture, PersistentBurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> path = os.path.join(tempfile.mkdtemp(), "ring.cf32")
>>> cap = PersistentBurstCapture(path, code, burst_len=512,
...                             reps=4, spc=2)
>>> ram = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> _ = cap.push(np.zeros(4096, dtype=np.complex64))
>>> # the look-back is in the file, so the blob stops carrying it
>>> ram.state_bytes() - cap.state_bytes() == ram.retain_span * 8
True
>>> os.path.getsize(path) > 0
True

function burst_capture_destroy

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

void burst_capture_destroy (
    burst_capture_state_t * state
) 


function burst_capture_detections

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

size_t burst_capture_detections (
    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.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> _ = 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 burst_capture_detections_max_out

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

size_t burst_capture_detections_max_out (
    burst_capture_state_t * state,
    size_t n
) 


function burst_capture_event_at

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

const burst_capture_event_t * burst_capture_event_at (
    const burst_capture_state_t * state,
    size_t i
) 


function burst_capture_events

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

size_t burst_capture_events (
    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.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> win = cap.push(np.zeros(4096, dtype=np.complex64))
>>> len(cap.events()) == win.size // cap.burst_len
True

function burst_capture_events_max_out

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

size_t burst_capture_events_max_out (
    burst_capture_state_t * state,
    size_t n
) 


function burst_capture_get_cn0_dbhz_est

double burst_capture_get_cn0_dbhz_est (
    const burst_capture_state_t * state
) 

function burst_capture_get_code_bins

Code-phase hypotheses per Doppler row.

size_t burst_capture_get_code_bins (
    const burst_capture_state_t * state
) 


function burst_capture_get_doppler_bins

Doppler hypotheses searched (the coherent depth).

size_t burst_capture_get_doppler_bins (
    const burst_capture_state_t * state
) 


function burst_capture_get_doppler_hz_est

double burst_capture_get_doppler_hz_est (
    const burst_capture_state_t * state
) 

function burst_capture_get_doppler_res_hz

double burst_capture_get_doppler_res_hz (
    const burst_capture_state_t * state
) 

function burst_capture_get_doppler_span_hz

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

double burst_capture_get_doppler_span_hz (
    const burst_capture_state_t * state
) 


function burst_capture_get_dropped

uint64_t burst_capture_get_dropped (
    const burst_capture_state_t * state
) 

function burst_capture_get_eta

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

double burst_capture_get_eta (
    const burst_capture_state_t * state
) 


function burst_capture_get_eta_nc

Non-coherent gate; in force when n_noncoh > 1 (the usual case).

double burst_capture_get_eta_nc (
    const burst_capture_state_t * state
) 


function burst_capture_get_min_gap

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

size_t burst_capture_get_min_gap (
    const burst_capture_state_t * state
) 


function burst_capture_get_n_bursts

uint64_t burst_capture_get_n_bursts (
    const burst_capture_state_t * state
) 

function burst_capture_get_n_noncoh

Non-coherent looks combined per decision.

size_t burst_capture_get_n_noncoh (
    const burst_capture_state_t * state
) 


function burst_capture_get_pd_predicted

Detection probability the sized grid actually predicts.

double burst_capture_get_pd_predicted (
    const burst_capture_state_t * state
) 


function burst_capture_get_pending

size_t burst_capture_get_pending (
    const burst_capture_state_t * state
) 

function burst_capture_get_preamble_start

uint64_t burst_capture_get_preamble_start (
    const burst_capture_state_t * state
) 

function burst_capture_get_refine_margin

double burst_capture_get_refine_margin (
    const burst_capture_state_t * state
) 

function burst_capture_get_state

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

void burst_capture_get_state (
    const burst_capture_state_t * state,
    void * blob
) 


function burst_capture_get_straddle_loss

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

double burst_capture_get_straddle_loss (
    const burst_capture_state_t * state
) 


function burst_capture_push

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

size_t burst_capture_push (
    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.

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.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> 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 burst_capture_push_max_out

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

size_t burst_capture_push_max_out (
    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 burst_capture_ready

Windows the last push() completed.

size_t burst_capture_ready (
    const burst_capture_state_t * state
) 

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


function burst_capture_release

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

int burst_capture_release (
    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.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> _ = 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 burst_capture_reset

Return to the searching state.

void burst_capture_reset (
    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.dsss import BurstCapture
>>> code = np.array([1, 1, 1, 0, 1, 0, 0], dtype=np.uint8)
>>> cap = BurstCapture(code, burst_len=512, reps=4, spc=2)
>>> cap.push(np.zeros(4096, dtype=np.complex64)).size
0
>>> cap.reset()
>>> cap.pending
0

function burst_capture_set_state

Restore from blob .

int burst_capture_set_state (
    burst_capture_state_t * state,
    const void * blob
) 

Returns:

DP_OK or DP_ERR_INVALID.

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.


function burst_capture_state_bytes

Bytes one blob occupies: a pure function of CONFIGURATION.

size_t burst_capture_state_bytes (
    const burst_capture_state_t * state
) 


function burst_capture_window

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

const float _Complex * burst_capture_window (
    const 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_HITS

Detections collected from acquisition per batch.

#define BURST_CAPTURE_HITS `16u`

A BATCHING parameter, never a correctness one: push() loops until acq has absorbed the whole chunk, so a smaller array means more iterations and nothing else. Growing it to "be safe" would hide the fact that acq_push() stops once its result array is full and abandons the rest of its input.


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.

#define BURST_CAPTURE_STATE_VERSION `2u`



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