File wfm_plan.h¶
FileList > doppler > wfm > wfm_plan.h
Go to the source code of this file
#include "doppler/dp_complex.h"#include <stddef.h>#include <stdint.h>
Public Types¶
| Type | Name |
|---|---|
| typedef struct wfm_plan | wfm_plan_t |
Public Functions¶
| Type | Name |
|---|---|
| uint64_t | dp_wfm_plan_anchor_seed (const wfm_plan_t * p) The noise seed that reproduces a full compose. |
| size_t | dp_wfm_plan_at (const wfm_plan_t * p, double snr, uint64_t seed, float _Complex * out) Scalar fast-path for the hot Monte-Carlo/SNR loop (no JSON parse). |
| int | dp_wfm_plan_check_snr (const wfm_plan_t * p) Whether an snr can be applied to this Plan. |
| void | dp_wfm_plan_destroy (wfm_plan_t * p) Destroy a Plan and free its caches. NULL is a no-op. |
| int | dp_wfm_plan_dump (const wfm_plan_t * p, const char * path) Save a Plan to a file ( dp_wfm_plan_save() bytes at path ). |
| size_t | dp_wfm_plan_len (const wfm_plan_t * p) Worst-case materialized length in samples (every ranged gap at its hi bound) — the jm binding's out_len_fn / allocation capacity. |
| wfm_plan_t * | dp_wfm_plan_load (const char * path) Load a Plan from a file written by dp_wfm_plan_dump() . |
| size_t | dp_wfm_plan_n_sources (const wfm_plan_t * p) Number of cached signal sources across every segment (excludes noise floors); the length of the gains /phases /enable arrays. |
| wfm_plan_t * | dp_wfm_plan_prepare (const char * spec_json) Prepare a Plan from a composer spec JSON (Composer.to_json()). |
| size_t | dp_wfm_plan_render (const wfm_plan_t * p, const char * overrides_json, float _Complex * out) General render: apply a JSON override spec, return a cf32 array. |
| wfm_plan_t * | dp_wfm_plan_restore (const void * blob, size_t n) Reconstruct a Plan from a blob produced by dp_wfm_plan_save() . |
| size_t | dp_wfm_plan_save (const wfm_plan_t * p, void * blob) Serialize a Plan into blob (dp_wfm_plan_save_bytes(p) bytes). |
| size_t | dp_wfm_plan_save_bytes (const wfm_plan_t * p) Serialized size of a Plan blob (envelope + spec + cached buffers). |
Macros¶
| Type | Name |
|---|---|
| define | DP_WFM_PLAN_WHY_NO_NOISE /* multi line expression */Why a Plan refuses an snr : its scene carries no noise. |
Public Types Documentation¶
typedef wfm_plan_t¶
Opaque prepared-plan state.
Public Functions Documentation¶
function dp_wfm_plan_anchor_seed¶
The noise seed that reproduces a full compose.
The first noisy segment's default seed (its first source's seed field). Passing this as dp_wfm_plan_at's seed (with the scene's base SNR) yields the byte-identical output of wfm_compose for a single-segment scene; for a multi-segment scene each segment still draws from its own default seed unless overridden. Varying the seed draws independent Monte-Carlo noise (and, for a ranged-gap scene, timing) realizations.
function dp_wfm_plan_at¶
Scalar fast-path for the hot Monte-Carlo/SNR loop (no JSON parse).
out = Σ gain_k·cache_k + gain(snr)·noise(seed) per segment/instance; writes up to dp_wfm_plan_len(p) samples. Equivalent to render with only {"snr":snr,"seed":seed} — seed is always an explicit override here, and so is snr: on a Plan whose scene carries no noise it is refused (see dp_wfm_plan_check_snr()).
Returns:
Samples actually written for this draw (<= dp_wfm_plan_len(p)), or 0 when refused, with out untouched.
function dp_wfm_plan_check_snr¶
Whether an snr can be applied to this Plan.
O(1): the answer is fixed at prepare time. A caller about to sweep SNR checks it once; dp_wfm_plan_at() and dp_wfm_plan_render() apply the same rule themselves.
>>> from doppler.wfm import Composer, prepare
>>> clean = prepare(Composer(type="tone", num_samples=64))
>>> clean.at(6.0)
Traceback (most recent call last):
...
ValueError: this scene carries no noise, so the Plan has no noise floor for snr to move: give a source a finite snr (below 100 dB) and an snr_mode (rc=-4)
>>> len(prepare(Composer(type="tone", num_samples=64, snr=10.0)).at(6.0))
64
Returns:
0 (DP_OK) when some segment carries noise; DP_ERR_INVALID (-4) when none does, for the reason DP_WFM_PLAN_WHY_NO_NOISE.
function dp_wfm_plan_destroy¶
Destroy a Plan and free its caches. NULL is a no-op.
function dp_wfm_plan_dump¶
Save a Plan to a file ( dp_wfm_plan_save() bytes atpath ).
Returns:
0 on success, non-zero on an open/write error.
function dp_wfm_plan_len¶
Worst-case materialized length in samples (every ranged gap at its hi bound) — the jm binding's out_len_fn / allocation capacity.
function dp_wfm_plan_load¶
Load a Plan from a file written by dp_wfm_plan_dump() .
Same fingerprint semantics as dp_wfm_plan_restore(): a matching build loads the cached buffers, a mismatch rebuilds from the embedded spec.
Returns:
Heap Plan (caller dp_wfm_plan_destroy()s it), or NULL on an open/read error or a malformed/foreign-endian file.
function dp_wfm_plan_n_sources¶
Number of cached signal sources across every segment (excludes noise floors); the length of the gains /phases /enable arrays.
function dp_wfm_plan_prepare¶
Prepare a Plan from a composer spec JSON (Composer.to_json()).
Parses + resolves the scene, validates scope per segment, then renders and caches each segment's clean signal ON-time at gain 1. Returns NULL on parse failure or an out-of-scope spec (continuous/repeat scene, a ranged on-time, a ranged per-source field, a non-trailing/multiple noise source within a segment, or a BACKGROUND source carrying clock Doppler).
A source with clock Doppler IS served, and the render is compose() to the bit. A Doppler channel is a resampler with state that runs through the gaps too, so what a burst renders as depends on the leading delay and on the previous instance's gap, and it resamples the AWGN of a bundled source along with the signal. A per-source on-time cache cannot hold either, so the cache holds the signal BEFORE the channel and render() runs the channel over it, through the composer's own renderer: the delay, the gaps, the noise and (for a PERSIST lifetime) the earlier segments are all in hand when it runs. A ranged doppler / doppler_rate is drawn per instance exactly as compose() draws it, and a seed override moves it. Cost: a Doppler scene's render() is no longer a pure re-weight of the cache each Doppler source runs one resampler pass over its timeline. That pass is what render() parallelises: a segment's repeat instances render concurrently when no source PERSISTs (they share no channel), and a PERSIST segment, whose instances its channels chain, fans out across its sources instead. The result is the same bits as the serial render. A single burst from a single source has nothing to fan out, so there the pass is simply the cost.
The one refusal is a BACKGROUND source with Doppler: the background fold sums those into a single composite before the render, and a channel is per source, so it cannot be applied to a sum.
Parameters:
spec_jsonA NUL-terminated composer spec JSON string.
Returns:
Heap Plan (caller dp_wfm_plan_destroy()s it), or NULL.
function dp_wfm_plan_render¶
General render: apply a JSON override spec, return a cf32 array.
size_t dp_wfm_plan_render (
const wfm_plan_t * p,
const char * overrides_json,
float _Complex * out
)
overrides_json is a small JSON object, all keys optional: {"gains":[dB…], "phases":[rad…], "enable":[bool…], "snr":dB, "seed":u} (gains/phases/enable are per-source, flat and segment-major, length = dp_wfm_plan_n_sources()). An empty object (or NULL) renders the baseline — bit-identical to Composer(scene).compose(). Writes up to dp_wfm_plan_len(p) samples to out. An "snr" key on a Plan whose scene carries no noise is refused (see dp_wfm_plan_check_snr()).
Returns:
Samples actually written for this draw (<= dp_wfm_plan_len(p)), or 0 when refused, with out untouched.
function dp_wfm_plan_restore¶
Reconstruct a Plan from a blob produced by dp_wfm_plan_save() .
If the blob's DSP fingerprint matches this build AND its structure matches the embedded spec, the cached buffers are loaded directly (no DSP). Otherwise the Plan is REBUILT from the embedded spec via the full DSP — same result, just paying prepare()'s cost. Returns NULL only on a malformed/foreign-endian blob or an unparseable/out-of-scope embedded spec (the same cases dp_wfm_plan_prepare rejects), never on a mere fingerprint mismatch.
Parameters:
blobBytes from dp_wfm_plan_save().nLength ofblobin bytes.
Returns:
Heap Plan (caller dp_wfm_plan_destroy()s it), or NULL.
function dp_wfm_plan_save¶
Serialize a Plan into blob (dp_wfm_plan_save_bytes(p) bytes).
Native-endian. The blob embeds the spec JSON, so a restore is self-contained. Returns the number of bytes written (== dp_wfm_plan_save_bytes(p)) — the actual-length contract a variable-output binding needs, so save() -> bytes generates with no hand-written glue.
function dp_wfm_plan_save_bytes¶
Serialized size of a Plan blob (envelope + spec + cached buffers).
The number of bytes dp_wfm_plan_save() writes: a small envelope with the DSP fingerprint, the embedded spec JSON, and every cached signal buffer. Dominated by the buffers (Σ per-source num_samples · 8 bytes) — multi-MB for a large scene, which is exactly why the spec-rebuild path is the default.
Macro Definition Documentation¶
define DP_WFM_PLAN_WHY_NO_NOISE¶
Why a Plan refuses an snr : its scene carries no noise.
A segment is noisy when a source in it has an snr below WFM_SYNTH_SNR_CLEAN (100 dB), which gives the segment a noise floor that an snr override moves. A scene with no such segment has nothing for the override to move, so dp_wfm_plan_at() and a "snr" key to dp_wfm_plan_render() are refused rather than returning the clean signal at every SNR a sweep over it would read a perfect receiver. The static reason every face reports.
The documentation for this class was generated from the following file native/inc/doppler/wfm/wfm_plan.h