File wfm_synth_core.h¶
FileList > inc > wfm_synth > wfm_synth_core.h
Go to the source code of this file
Synth component API. More...
#include "clib_common.h"#include "dp_state.h"#include "jm_perf.h"#include "fir/fir_core.h"#include "lo/lo_core.h"#include "awgn/awgn_core.h"#include "pn/pn_core.h"#include "resamp/resamp_core.h"#include <math.h>#include "gold/gold_core.h"#include "mpsk/mpsk_core.h"
Classes¶
| Type | Name |
|---|---|
| struct | wfm_synth_state_t Synth state. |
Public Types¶
| Type | Name |
|---|---|
| enum | wfm__synth__core_8h_1abc5c98fcc1211af2b80116dd6e0a035d |
| enum | wfm__synth__core_8h_1ac36f475ca5b446f4fde4c9b90bec77c8 |
Public Functions¶
| Type | Name |
|---|---|
| JM_FORCEINLINE float _Complex | wfm_synth_bit_symbol (wfm_synth_state_t * s) Next symbol from the user bit pattern, cycled — one mapping, every M. |
| JM_FORCEINLINE int | wfm_synth_bps (int type) Bits carried by one symbol of type — thebps an Eb/No needs. |
| JM_FORCEINLINE float | wfm_synth_cont_dsss_chip (wfm_synth_state_t * s) One continuous-DSSS chip: code[n % n_code] ^ data , as a BPSK sign. |
| wfm_synth_state_t * | wfm_synth_create (int type, double fs, double freq, double snr, int snr_mode, uint32_t seed, int sps, int pn_length, uint64_t pn_poly, int lfsr, double f_end) Allocate and configure a waveform synthesiser. The synthesiser combines a local oscillator (LO), optional AWGN, and an optional PN LFSR into a single streaming source. One call to wfm_synth_step() orwfm_synth_steps() advances all sub-components in lock-step. SNR >= WFM_SYNTH_SNR_CLEAN (100 dB) skips AWGN entirely — clean waveforms pay no noise overhead. When snr_mode is "auto" the library picks the natural reference: Es/No for modulated types (BPSK, QPSK), fs-band SNR for tone/noise/PN. |
| void | wfm_synth_destroy (wfm_synth_state_t * state) Destroy a synth instance and release all memory. Recursively frees the LO, AWGN, and PN sub-objects, then the struct itself. Safe to call with NULL (no-op). |
| float | wfm_synth_get_cur_im (const wfm_synth_state_t * state) Return the imaginary part of the current held symbol. For QPSK this is the Q component (±1/√2); for BPSK/PN it is always 0; for tone/noise it is 0. |
| float | wfm_synth_get_cur_re (const wfm_synth_state_t * state) Return the real part of the current held symbol. For modulated types this is the I component latched at the last symbol boundary (±1 for BPSK/PN, ±1/√2 for QPSK). For tone the synthesiser initialises cur_re to 1.0 so that the held symbol is a clean unit-power carrier; for noise it is 0.0 (noise has no held symbol). |
| int | wfm_synth_get_nsps (const wfm_synth_state_t * state) Return the samples-per-symbol count. For modulated types (BPSK, QPSK, PN) each symbol is held for nsps consecutive output samples. For tone/noise this field is present but unused by the synthesis path. |
| void | wfm_synth_get_state (const wfm_synth_state_t * state, void * blob) |
| int | wfm_synth_get_sym_pos (const wfm_synth_state_t * state) Return the current position within the current symbol (0..nsps-1). Reaches nsps and wraps to 0 each time a new symbol is consumed from the PN LFSR. Useful for frame alignment: sym_pos==0 on a step boundary means the very next sample begins a fresh symbol. |
| int | wfm_synth_get_wtype (const wfm_synth_state_t * state) Return the active waveform type discriminant. Maps to the WFM_SYNTH_* enum: 0=tone, 1=noise, 2=pn, 3=bpsk, 4=qpsk. Use this to inspect which synthesis path is active at runtime. |
| JM_FORCEINLINE uint64_t | wfm_synth_mls_poly (uint32_t n) The MLS primitive polynomial table — pn's, reached by its old name. |
| JM_FORCEINLINE float _Complex | wfm_synth_next_symbol (wfm_synth_state_t * s) Pull the next constellation symbol from the active shaped source. |
| void | wfm_synth_noise_steps (wfm_synth_state_t * state, float _Complex * output, size_t n) Generate n noise-only samples — the synth's additive-AWGN term with no signal — continuing the same noise RNG stream wfm_synth_steps() draws from (no reseed, identical chunked awgn call pattern, so a gap rendered here is the seamless continuation of the on-time noise). Writes exact zeros and advances nothing for a clean synth (no AWGN child). Used by the composer to carry a segment's noise floor through its off-time gap. |
| void | wfm_synth_reseed_noise (wfm_synth_state_t * state, uint32_t seed) Reseed only the additive-noise (AWGN) generator, leaving the signal (LO / PN code / data / pulse shaping) untouched. A no-op for a synth with no noise. Used by the composer to give each repeat a fresh noise realization while the underlying waveform stays bit-identical. |
| void | wfm_synth_reset (wfm_synth_state_t * state) Reset Synth to its post-create state. Resets the LO phase accumulator, AWGN internal state, and PN LFSR register to their initial values so the output sequence is perfectly reproducible from sample 0. |
| int | wfm_synth_set_bits (wfm_synth_state_t * state, const uint8_t * bits, size_t n, int modulation) Attach a user bit pattern to a type=bits synth (no-op otherwise). |
| void | wfm_synth_set_chirp_span (wfm_synth_state_t * state, size_t span) Pin a chirp's sweep span to span samples (no-op for non-chirp). |
| void | wfm_synth_set_cur_im (wfm_synth_state_t * state, float val) Override the held-symbol imaginary (Q) component in-place. Takes effect on the next wfm_synth_step() within the current symbol hold. |
| void | wfm_synth_set_cur_re (wfm_synth_state_t * state, float val) Override the held-symbol real (I) component in-place. Takes effect on the next wfm_synth_step() within the current symbol hold. |
| int | wfm_synth_set_dsss (wfm_synth_state_t * state, const uint8_t * acq_code, size_t acq_len, size_t acq_reps, const uint8_t * data_code, size_t data_len, const uint8_t * sync, size_t sync_len, const uint8_t * payload, size_t payload_len, int crc) Build and attach a two-code DSSS burst to a type=dsss synth (no-op otherwise). |
| int | wfm_synth_set_dsss_chips (wfm_synth_state_t * state, const uint8_t * chips, size_t n_chips) Install an already-assembled DSSS burst as the chip pattern. |
| int | wfm_synth_set_dsss_cont (wfm_synth_state_t * state, const uint8_t * code, size_t code_len, double chips_per_symbol, int data_mode, const uint8_t * data, size_t n_data) Configure a type=dsss synth for CONTINUOUS ASYNCHRONOUS generation. |
| int | wfm_synth_set_dsss_window (wfm_synth_state_t * state, size_t code_only_symbols, size_t frame_symbols) Give the continuous DSSS stream a frame with a pure-code window. |
| void | wfm_synth_set_nsps (wfm_synth_state_t * state, int val) Override the samples-per-symbol count in-place. Does not flush the symbol-position counter (sym_pos); set sym_pos=0 as well when changing sps mid-stream. |
| int | wfm_synth_set_rrc (wfm_synth_state_t * state, const float * taps, size_t ntaps) Enable RRC pulse shaping on a symbol synth (pn/bpsk/qpsk/bits). |
| int | wfm_synth_set_state (wfm_synth_state_t * state, const void * blob) |
| void | wfm_synth_set_sym_pos (wfm_synth_state_t * state, int val) Override the symbol-position counter in-place. Injecting 0 forces the next wfm_synth_step() to latch a new PN chip; any other value fast-forwards into the middle of the current symbol hold. |
| int | wfm_synth_set_symbols (wfm_synth_state_t * state, const float _Complex * symbols, size_t n) Attach a complex-symbol stream to a type=symbols synth (no-op else). |
| void | wfm_synth_set_wtype (wfm_synth_state_t * state, int val) Override the waveform type discriminant in-place. Changing wtype does not reinitialise sub-objects; use with care. |
| JM_FORCEINLINE void | wfm_synth_shape (wfm_synth_state_t * s, float _Complex * out, size_t m, float _Complex * syms) Produce m polyphase-shaped baseband samples intoout . |
| JM_FORCEINLINE void | wfm_synth_shaper_prime (wfm_synth_state_t * s) Prime the shaper's delay line so its output aligns with the dense FIR. |
| JM_FORCEINLINE double | wfm_synth_snr_over_fs (int mode, int bps, double span, double snr) Convert a per-symbol or per-bit SNR to SNR over the full sample rate. |
| size_t | wfm_synth_state_bytes (const wfm_synth_state_t * state) |
| JM_FORCEINLINE JM_HOT float _Complex | wfm_synth_step (wfm_synth_state_t * state) Generate one output sample from internal state. Advances the PN LFSR (modulated types only, on symbol boundaries), the LO phase accumulator, and the AWGN engine, then returns the mixed result: sym * carrier + noise . Inlined and hot-path annotated so tight per-sample loops pay no call overhead. |
| void | wfm_synth_steps (wfm_synth_state_t * state, float _Complex * output, size_t n) Generate a block of output samples. Calls wfm_synth_step() in a tight loop, writing each cf32 sample into output . The Python binding returns a freshly allocated NumPy complex64 array; ownership is transferred to the caller. |
Macros¶
| Type | Name |
|---|---|
| define | WFM_SYNTH_SNR_CLEAN 100.0 |
| define | WFM_SYNTH_STATE_MAGIC [**DP\_FOURCC**](dp__state_8h.md#define-dp_fourcc) ('W','F','M','S') |
| define | WFM_SYNTH_STATE_VERSION 2u /\* v2: + continuous-DSSS chip/symbol clocks \*/ |
Detailed Description¶
Lifecycle: create -> [step / steps / reset]* -> destroy
Example:
wfm_synth_state_t *obj = wfm_synth_create(0, 1000000.0, 0.0, 100.0, 0, 1, 8, 7, 0);
float _Complex y = wfm_synth_step(obj);
wfm_synth_destroy(obj);
Public Types Documentation¶
enum wfm__synth__core_8h_1abc5c98fcc1211af2b80116dd6e0a035d¶
enum wfm__synth__core_8h_1abc5c98fcc1211af2b80116dd6e0a035d {
WFM_SYNTH_TONE = 0,
WFM_SYNTH_NOISE = 1,
WFM_SYNTH_PN = 2,
WFM_SYNTH_BPSK = 3,
WFM_SYNTH_QPSK = 4,
WFM_SYNTH_CHIRP = 5,
WFM_SYNTH_BITS = 6,
WFM_SYNTH_SYMBOLS = 7,
WFM_SYNTH_DSSS = 8
};
Waveform type discriminant (the type create argument / type choice).
enum wfm__synth__core_8h_1ac36f475ca5b446f4fde4c9b90bec77c8¶
enum wfm__synth__core_8h_1ac36f475ca5b446f4fde4c9b90bec77c8 {
WFM_DSSS_DATA_NONE = 0,
WFM_DSSS_DATA_BITS = 1,
WFM_DSSS_DATA_PRBS = 2
};
Continuous-DSSS data-symbol source (wfm_synth_set_dsss_cont's data_mode).
Public Functions Documentation¶
function wfm_synth_bit_symbol¶
Next symbol from the user bit pattern, cycled — one mapping, every M.
The single home for the bits->symbol map. It had four copies: two in this header (wfm_synth_next_symbol and wfm_synth_step) and two in wfm_synth_steps(). wfm_synth_next_symbol's own comment says the kernel is shared "so the single-sample and block paths cannot diverge -- they call
the SAME function rather than each inlining the arithmetic", and the arithmetic was inlined four times anyway.
bit_mod is BITS PER SYMBOL, which is what its existing values already mean (1 = BPSK, 2 = QPSK), so M = 1 << bit_mod and 3 = 8PSK extends the numbering rather than reinterpreting it. One symbol's bits are read MSB-first into a Gray label and handed to mpsk_constellation() the library's canonical mapping, and the one dp_ber_score() inverts to score bit errors.
That shared mapping is the point. The QPSK branches this replaces put b0 on the I sign and b1 on the Q sign: the same CONSTELLATION, but two of the four labels swapped against mpsk_constellation(). Nothing scored a QPSK bit pattern against truth, so it never produced a wrong number but a framed QPSK stream read through the canonical scorer would have shown about half its symbols wrong on a perfectly working receiver, which is the plausible-number failure docs/design/rx-test.md exists to stop.
bit_mod == 0 is not PSK it is the 0/1 amplitude line this type has always emitted so it keeps its own branch.
Parameters:
sSynth state;bits/n_bitsmust be non-empty,bit_idxadvances.
Returns:
Unit-modulus constellation point (a unit-amplitude line at bit_mod == 0), which is what Synth's unit-power SNR reference needs.
function wfm_synth_bps¶
Bits carried by one symbol of type — thebps an Eb/No needs.
QPSK carries two, everything else one. DSSS is one because its payload is BPSK, which is what makes ebno == esno for a DSSS source.
function wfm_synth_cont_dsss_chip¶
One continuous-DSSS chip: code[n % n_code] ^ data , as a BPSK sign.
The per-chip kernel shared by wfm_synth_step and wfm_synth_steps (and the manifest impl), so the single-sample and block paths cannot diverge — they call the SAME function rather than each inlining the arithmetic. Advances the code clock (n % n_code) and the INDEPENDENT symbol clock (floor(n / chips_per_symbol)) off one running chip counter; at each symbol boundary it refreshes the data bit from the configured source (constant 0 for code-only, the cycled payload, or the next PN bit). Non-integer chips_per_symbol is what makes symbol edges land mid-epoch — the asynchronicity.
With a frame set (wfm_synth_set_dsss_window), the frame lives on the SYMBOL clock: of every frame_symbols symbols, the first code_only_symbols carry data 0 — the pure code — and the rest carry the payload, whose index counts data symbols only, so the bits run on across frames. The symbol clock never restarts: it is the same free-running floor(n / chips_per_symbol) with or without a window, so a frame edge falls at whatever chip phase that clock puts it — the chip and data clocks have no fixed relation, and no frame edge is synchronous with a code epoch. frame_symbols == 0 is the windowless stream, bit for bit.
Requires chips_per_symbol >= 1 (chip rate >= symbol rate, always true for a real DSSS waveform), so the symbol index advances by 0 or 1 per chip and the PN is never asked to skip.
function wfm_synth_create¶
Allocate and configure a waveform synthesiser. The synthesiser combines a local oscillator (LO), optional AWGN, and an optional PN LFSR into a single streaming source. One call to wfm_synth_step() orwfm_synth_steps() advances all sub-components in lock-step. SNR >= WFM_SYNTH_SNR_CLEAN (100 dB) skips AWGN entirely — clean waveforms pay no noise overhead. Whensnr_mode is "auto" the library picks the natural reference: Es/No for modulated types (BPSK, QPSK), fs-band SNR for tone/noise/PN.
wfm_synth_state_t * wfm_synth_create (
int type,
double fs,
double freq,
double snr,
int snr_mode,
uint32_t seed,
int sps,
int pn_length,
uint64_t pn_poly,
int lfsr,
double f_end
)
Parameters:
typeWaveform type: 0=tone, 1=noise, 2=pn, 3=bpsk, 4=qpsk, 5=chirp, 6=bits, 7=symbols, 8=dsss. The Python binding accepts strings "tone"|"noise"|"pn"|"bpsk"|"qpsk"|"chirp"|"bits"|"symbols"|"dsss". For "bits" attach the pattern with wfm_synth_set_bits(); for "symbols" attach the complex stream with wfm_synth_set_symbols(); for "dsss" attach the burst with wfm_synth_set_dsss() after create().fsSample rate in Hz. Sets the carrier frequency normalisation and the noise bandwidth. Default 1 000 000.0.freqCarrier frequency offset in Hz (−fs/2 … fs/2). A complex LO is created only when freq != 0. For a chirp this is the start frequency f_start (the instantaneous frequency at t=0). Default 0.0.snrTarget SNR in dB, interpreted persnr_mode. Values >= WFM_SYNTH_SNR_CLEAN (100) disable AWGN. Default 100.0.snr_modeSNR reference: 0=auto, 1=fs (full-band), 2=ebno, 3=esno. The Python binding accepts strings "auto"|"fs"|"ebno"|"esno". Default 0.seedPRNG seed shared by AWGN and the PN LFSR. Default 1.spsSamples per symbol for modulated types (BPSK, QPSK, PN). Ignored for tone/noise. Default 8.pn_lengthLFSR register length (1..64); period = 2^pn_length - 1. Default 7 (period 127).pn_polyGalois tap polynomial for the LFSR. 0 means "look up the canonical MLS polynomial for pn_length" from the wfm_synth_mls_poly table. Default 0.lfsrLFSR realization: PN_GALOIS (0) or PN_FIBONACCI (1).f_endChirp end frequency in Hz (type=chirp only; ignored otherwise). Withfreqas the start, the instantaneous frequency sweeps linearly fromfreqtof_endover the span (set by wfm_synth_set_chirp_span() or the first wfm_synth_steps() call), then holds atf_end.f_end < freqis a down-chirp. Default 0.0.
Returns:
Heap-allocated state, or NULL on allocation failure.
Note:
Caller must call wfm_synth_destroy() when done.
>>> from doppler.wfm import _SynthEngine
>>> import numpy as np
>>> s = _SynthEngine(type="tone", fs=1.0, freq=0.0, snr=100.0)
>>> x = s.steps(4)
>>> x.dtype
dtype('complex64')
>>> x.tolist()
[(1+0j), (1+0j), (1+0j), (1+0j)]
function wfm_synth_destroy¶
Destroy a synth instance and release all memory. Recursively frees the LO, AWGN, and PN sub-objects, then the struct itself. Safe to call with NULL (no-op).
Parameters:
statePointer to heap-allocated state; may be NULL.
function wfm_synth_get_cur_im¶
Return the imaginary part of the current held symbol. For QPSK this is the Q component (±1/√2); for BPSK/PN it is always 0; for tone/noise it is 0.
Parameters:
stateMust be non-NULL.
Returns:
Current symbol imaginary (Q) component.
function wfm_synth_get_cur_re¶
Return the real part of the current held symbol. For modulated types this is the I component latched at the last symbol boundary (±1 for BPSK/PN, ±1/√2 for QPSK). For tone the synthesiser initialises cur_re to 1.0 so that the held symbol is a clean unit-power carrier; for noise it is 0.0 (noise has no held symbol).
Parameters:
stateMust be non-NULL.
Returns:
Current symbol real (I) component.
function wfm_synth_get_nsps¶
Return the samples-per-symbol count. For modulated types (BPSK, QPSK, PN) each symbol is held for nsps consecutive output samples. For tone/noise this field is present but unused by the synthesis path.
Parameters:
stateMust be non-NULL.
Returns:
Samples per symbol (nsps >= 1).
function wfm_synth_get_state¶
function wfm_synth_get_sym_pos¶
Return the current position within the current symbol (0..nsps-1). Reaches nsps and wraps to 0 each time a new symbol is consumed from the PN LFSR. Useful for frame alignment: sym_pos==0 on a step boundary means the very next sample begins a fresh symbol.
Parameters:
stateMust be non-NULL.
Returns:
Symbol position counter (0 <= sym_pos < nsps).
function wfm_synth_get_wtype¶
Return the active waveform type discriminant. Maps to the WFM_SYNTH_* enum: 0=tone, 1=noise, 2=pn, 3=bpsk, 4=qpsk. Use this to inspect which synthesis path is active at runtime.
Parameters:
stateMust be non-NULL.
Returns:
Integer waveform type index (WFM_SYNTH_TONE .. WFM_SYNTH_QPSK).
function wfm_synth_mls_poly¶
The MLS primitive polynomial table — pn's, reached by its old name.
The table itself moved to pn/pn_core.h (pn_mls_poly), because the convention it encodes is pn_create()'s tap mask and not the synth's. This spelling is retained for the call sites that already use it; it forwards and holds no table of its own, so the two cannot disagree.
function wfm_synth_next_symbol¶
Pull the next constellation symbol from the active shaped source.
The single symbol-generation point the polyphase pulse shaper feeds from, dispatching on the waveform type exactly as wfm_synth_step's symbol latch does — the PN LFSR (pn/bpsk one chip, qpsk two Gray chips), the cycled user bit pattern (bits, per bit_mod), the continuous asynchronous DSSS chip, or the cycled complex-symbol stream — and advancing that source's read cursor by one symbol. Only the shaped types (pn/bpsk/qpsk/bits/symbols/dsss, the set wfm_synth_set_rrc accepts) reach here, so the shaper draws the same symbol sequence the dense-FIR path would; only the pulse-shaping filter differs.
function wfm_synth_noise_steps¶
Generate n noise-only samples — the synth's additive-AWGN term with no signal — continuing the same noise RNG stream wfm_synth_steps() draws from (no reseed, identical chunked awgn call pattern, so a gap rendered here is the seamless continuation of the on-time noise). Writes exact zeros and advances nothing for a clean synth (no AWGN child). Used by the composer to carry a segment's noise floor through its off-time gap.
Parameters:
stateSynth state (may be NULL — no-op).outputn complex samples out.nSample count.
function wfm_synth_reseed_noise¶
Reseed only the additive-noise (AWGN) generator, leaving the signal (LO / PN code / data / pulse shaping) untouched. A no-op for a synth with no noise. Used by the composer to give each repeat a fresh noise realization while the underlying waveform stays bit-identical.
Parameters:
stateSynth state (may be NULL).seedNew noise RNG seed.
function wfm_synth_reset¶
Reset Synth to its post-create state. Resets the LO phase accumulator, AWGN internal state, and PN LFSR register to their initial values so the output sequence is perfectly reproducible from sample 0.
Parameters:
stateMust be non-NULL.
function wfm_synth_set_bits¶
Attach a user bit pattern to a type=bits synth (no-op otherwise).
int wfm_synth_set_bits (
wfm_synth_state_t * state,
const uint8_t * bits,
size_t n,
int modulation
)
Copies n bits (each 0/1) into the synth; modulation maps them to symbols (0=none → 0/1 amplitude, 1=bpsk → ±1, 2=qpsk → Gray-coded ±1/√2, two bits per symbol). The pattern is oversampled by the create-time sps and cycled to fill whatever length wfm_synth_steps() requests, so one pass is n * sps samples (2*ceil... — n/2 * sps for qpsk). Replaces any previous pattern; resets the read position. Safe to call repeatedly.
Parameters:
stateMust be non-NULL.bitsArray ofnbytes, each 0 or 1.nNumber of bits (> 0).modulation0=none, 1=bpsk, 2=qpsk.
Returns:
0 on success; -1 on bad args or allocation failure.
function wfm_synth_set_chirp_span¶
Pin a chirp's sweep span to span samples (no-op for non-chirp).
A linear chirp's slope is (f_end − f_start) / span, so the span — the number of samples the sweep occupies — must be known before generation. The composer/CLI call this with the segment length; a standalone synth that is never pinned locks its span to the first wfm_synth_steps() call instead. Only the first pin (while the span is still 0) takes effect, so it is safe to call unconditionally after wfm_synth_create().
Parameters:
stateMust be non-NULL.spanSweep length in samples (> 0).
function wfm_synth_set_cur_im¶
Override the held-symbol imaginary (Q) component in-place. Takes effect on the next wfm_synth_step() within the current symbol hold.
Parameters:
stateMust be non-NULL.valNew cur_im value.
function wfm_synth_set_cur_re¶
Override the held-symbol real (I) component in-place. Takes effect on the next wfm_synth_step() within the current symbol hold.
Parameters:
stateMust be non-NULL.valNew cur_re value.
function wfm_synth_set_dsss¶
Build and attach a two-code DSSS burst to a type=dsss synth (no-op otherwise).
int wfm_synth_set_dsss (
wfm_synth_state_t * state,
const uint8_t * acq_code,
size_t acq_len,
size_t acq_reps,
const uint8_t * data_code,
size_t data_len,
const uint8_t * sync,
size_t sync_len,
const uint8_t * payload,
size_t payload_len,
int crc
)
Assembles the burst chip pattern through wfm_frame_dsss_chips() — an unmodulated preamble (acq_code repeated acq_reps times, the coherent acquisition target) followed by the frame sync | payload | CRC-16, each frame bit XOR-spread by the distinct data_code — and installs it as the synth's BPSK chip stream (each chip held for the create-time sps samples, i.e. sps is samples per chip here). This is the transmit side of BurstDemod's frame contract: the same codes, sync word, and payload length hand to burst_demod_set_preamble/set_sync on receive.
One pass of the pattern is one burst (n_chips * sps samples); like the bits pattern it cycles if more samples are requested — the composer sizes a dsss segment's on-time to exactly one burst. Replaces any previous pattern; resets the read position.
NOTE: snr_mode semantics — the raw engine's create-time esno refers to the chip (the output symbol). The Segment/Synth faces convert a data-symbol Es/N0 (snr_mode="esno") to the over-fs value with 10*log10(sf*sps) before create; see wfm_snr_over_fs().
Parameters:
stateMust be non-NULL.acq_codePreamble code (0/1), lengthacq_len; NULL whenacq_len*acq_reps == 0.acq_lenPreamble code length in chips.acq_repsPreamble repetitions.data_codePayload spreading code (0/1), lengthdata_len.data_lenChips per frame symbol (the spreading factor).syncFrame-sync word bits (0/1); NULL for none.sync_lenSync word length in bits.payloadPayload bits (0/1); NULL for a preamble-only burst.payload_lenPayload length in bits.crcNon-zero: append a CRC-16-CCITT trailer (dp_crc16.h) over the payload bits.
Returns:
0 on success; -1 on invalid geometry (frame bits with no data code, or an empty burst) or allocation failure.
function wfm_synth_set_dsss_chips¶
Install an already-assembled DSSS burst as the chip pattern.
The spreading half of wfm_synth_set_dsss(), split out so a caller who assembled the frame from a wfm_frame_desc_t a burst carrying an inner code, an ASM, an outer code or a randomiser installs it through the same path as the four-field form rather than through a second one. Chips are copied; chips stays the caller's.
Parameters:
stateSynth (no-op unlesswtype == WFM_SYNTH_DSSS).chipsBurst chips, one per byte (0/1), BPSK-mapped by the synth.n_chipsChip count; must be non-zero.
Returns:
0 on success, -1 on a NULL/empty pattern or allocation failure.
function wfm_synth_set_dsss_cont¶
Configure a type=dsss synth for CONTINUOUS ASYNCHRONOUS generation.
int wfm_synth_set_dsss_cont (
wfm_synth_state_t * state,
const uint8_t * code,
size_t code_len,
double chips_per_symbol,
int data_mode,
const uint8_t * data,
size_t n_data
)
The continuous counterpart to wfm_synth_set_dsss(): the same type="dsss" waveform, switched to the endless mode by supplying chips_per_symbol (= chip_rate / symbol_rate). One waveform type, one discriminator — no tenth entry in the five hand-maintained name tables wfm_names.h records rotting once already.
Lazy, not materialised. Chips are generated per sample by wfm_synth_cont_dsss_chip off a running counter, so the stream is genuinely endless — there is no pattern length to pick and the standalone Synth face works unbounded. The data-symbol source is chosen by data_mode:
* WFM_DSSS_DATA_NONE — code-only: the pure spreading code, no data.
* WFM_DSSS_DATA_BITS — data, cycled mod n_data (caller holds it).
* WFM_DSSS_DATA_PRBS — the synth's own seeded PN (create it in create(); a receiver regenerates the bits via doppler.wfm.PN).
The burst frame parameters have no meaning here (no preamble, sync, or CRC); the caller rejects that combination upstream rather than ignoring it (see wfmgen's --symbol-rate validation), so this function does not revisit it.
Parameters:
stateSynth (no-op unlesswtype == WFM_SYNTH_DSSS).codeSpreading code chips (0/1), lengthcode_len; copied.code_lenSpreading code length in chips (> 0) — the SF.chips_per_symbolChips per data symbol (>= 1),chip_rate / symbol_rate. Non-integer is the normal, asynchronous case.data_modeWFM_DSSS_DATA_{NONE,BITS,PRBS}.dataPayload bits (0/1) for WFM_DSSS_DATA_BITS, lengthn_data; copied. Ignored (may be NULL) otherwise.n_dataPayload length in bits (> 0 for WFM_DSSS_DATA_BITS).
Returns:
0 on success; -1 on invalid geometry or allocation failure.
function wfm_synth_set_dsss_window¶
Give the continuous DSSS stream a frame with a pure-code window.
int wfm_synth_set_dsss_window (
wfm_synth_state_t * state,
size_t code_only_symbols,
size_t frame_symbols
)
The frame is on the DATA clock: of every frame_symbols symbols, the first code_only_symbols carry the pure spreading code and no data, and the rest carry the payload, running on from the previous frame. The symbol clock is the stream's own free-running one (see wfm_synth_cont_dsss_chip), so a frame edge lands at whatever chip phase it lands at: the chip and data clocks have no fixed relation, and no frame edge is synchronous with a code epoch. This is the multi-emitter waveform's frame — 450 code-only symbols then 4500 of data in the application it was written for — and the searcher's coherent depth is what the window makes possible. Configuration, not running state: it is kept by reset() and is not serialized. The order against wfm_synth_set_dsss_cont() does not matter.
Parameters:
stateSynth (no-op unlesswtype == WFM_SYNTH_DSSS).code_only_symbolsPure-code symbols opening each frame, at mostframe_symbols. Equal to it means code only, for ever.frame_symbolsFrame length in symbols; 0 means no window — the stream exactly as without this call.
Returns:
0 on success (and for a non-dsss synth); -1 if code_only_symbols exceeds a non-zero frame_symbols.
function wfm_synth_set_nsps¶
Override the samples-per-symbol count in-place. Does not flush the symbol-position counter (sym_pos); set sym_pos=0 as well when changing sps mid-stream.
Parameters:
stateMust be non-NULL.valNew nsps value (>= 1).
function wfm_synth_set_rrc¶
Enable RRC pulse shaping on a symbol synth (pn/bpsk/qpsk/bits).
Replaces the default rectangular sample-and-hold with a root-raised-cosine pulse: the symbol-rate impulse train is filtered by taps (a real FIR of ntaps coefficients, typically wfm_rrc_taps(beta, sps, span)). The taps are scaled by sqrt(sps) internally for unit transmit power, so every caller passes the raw taps and gets byte-identical shaping. No-op for types with no symbol stream (tone/noise/chirp). Replaces any existing shaper and clears its delay line.
Parameters:
stateMust be non-NULL.tapsReal FIR taps (copied).ntapsNumber of taps (> 0).
Returns:
0 on success; -1 on bad args / allocation failure.
function wfm_synth_set_state¶
function wfm_synth_set_sym_pos¶
Override the symbol-position counter in-place. Injecting 0 forces the next wfm_synth_step() to latch a new PN chip; any other value fast-forwards into the middle of the current symbol hold.
Parameters:
stateMust be non-NULL.valNew sym_pos value (0 <= val < nsps).
function wfm_synth_set_symbols¶
Attach a complex-symbol stream to a type=symbols synth (no-op else).
Copies n complex symbols into the synth. Each symbol is the constellation point — there is no bit→symbol mapping, so this generalises every modulation (pi/4-QPSK, QAM, custom shaping) into "compute the symbols,
pass them in". The stream is oversampled by the create-time sps and cycled to fill whatever length wfm_synth_steps() requests (one pass is n * sps samples), and is RRC-shaped when wfm_synth_set_rrc() is active. Replaces any previous stream; resets the read position. Safe to call repeatedly.
Parameters:
stateMust be non-NULL.symbolsArray ofncomplex symbols (copied).nNumber of symbols (> 0).
Returns:
0 on success; -1 on bad args or allocation failure.
>>> import numpy as np
>>> from doppler.wfm import _SynthEngine, rrc_taps
>>> s = _SynthEngine(
... type="symbols", fs=1.0, freq=0.0, snr=100.0, sps=4)
>>> s.set_symbols(np.array([1+0j, 1j, -1+0j, -1j], np.complex64))
>>> s.steps(4)[::4].tolist() # symbol centres (rect hold)
[(1+0j), (1+0j), (1+0j), (1+0j)]
function wfm_synth_set_wtype¶
Override the waveform type discriminant in-place. Changing wtype does not reinitialise sub-objects; use with care.
Parameters:
stateMust be non-NULL.valNew wtype value (WFM_SYNTH_TONE .. WFM_SYNTH_QPSK).
function wfm_synth_shape¶
Produce m polyphase-shaped baseband samples intoout .
JM_FORCEINLINE void wfm_synth_shape (
wfm_synth_state_t * s,
float _Complex * out,
size_t m,
float _Complex * syms
)
The one shaping kernel shared by wfm_synth_step (m == 1) and wfm_synth_steps (m == block): prime once, generate exactly the resamp_interp_inputs_needed(shaper, m) symbols this call consumes into the caller's syms scratch, and fill m outputs. Because the resampler is block-boundary invariant and both faces call this identical routine, a single m-sample call and m one-sample calls produce bit-identical output — the step()==steps() guarantee. Carrier mix and noise are applied by the caller.
Parameters:
sShaper-attached synth state (s->shaper != NULL).outOutput buffer, capacity >=m.mNumber of baseband samples to produce.symsCaller scratch, capacity >= resamp_interp_inputs_needed(s, m).
function wfm_synth_shaper_prime¶
Prime the shaper's delay line so its output aligns with the dense FIR.
The polyphase interpolator emits its first meaningful sample only after the delay line fills, so its output lags the dense-FIR path by exactly nsps samples. Discarding that many leading outputs once, at stream start (which consumes exactly the first source symbol into the delay line), realigns the shaped waveform to the dense path to float precision — so switching a source to polyphase shaping does not shift downstream sample timing. Idempotent via the primed flag; re-armed by wfm_synth_reset.
function wfm_synth_snr_over_fs¶
Convert a per-symbol or per-bit SNR to SNR over the full sample rate.
The one place this arithmetic lives. A noise amplitude is always referenced to fs, so every SNR mode is a conversion into that: an Es/N0 spreads the symbol's energy over span samples, and an Eb/No does the same after first multiplying by the bits the symbol carries. Getting it wrong is silent — the waveform is still a waveform, at an SNR nobody asked for — so having it written twice is how a generator and a composer come to place different noise for the same requested number.
Parameters:
modeRESOLVED mode: 1 fs, 2 Eb/No, 3 Es/No. Never 0 (auto) — see below.bpsBits per symbol, from wfm_synth_bps().spanSamples one symbol's energy is spread over.snrThe requested figure, in dB, inmode'sreference.
Returns:
SNR in dB over fs, ready for awgn_amplitude_for_snr().
**auto and span are deliberately the CALLER's**, and that is not an oversight: they are the two things that legitimately differ. wfm_synth resolves auto to fs for a DSSS source because at create() time it cannot do better — the codes attach afterwards, so the spreading factor that sets the symbol span is not yet known — while the composer resolves the same source to Es/No and passes the true span (sf * sps for a burst, or fs/symbol_rate for a continuous asynchronous stream, which coincide only in the synchronous case that mode exists to avoid). Those differences are inputs, not a second formula.
// Es/No 12 dB at 8 samples/symbol -> 2.969 dB over fs
double fs_db = wfm_synth_snr_over_fs (3, 1, 8.0, 12.0);
// the same figure read as Eb/No on QPSK is 3.010 dB hotter
double eb_db = wfm_synth_snr_over_fs (2, wfm_synth_bps (WFM_SYNTH_QPSK),
8.0, 12.0);
function wfm_synth_state_bytes¶
function wfm_synth_step¶
Generate one output sample from internal state. Advances the PN LFSR (modulated types only, on symbol boundaries), the LO phase accumulator, and the AWGN engine, then returns the mixed result: sym * carrier + noise . Inlined and hot-path annotated so tight per-sample loops pay no call overhead.
Parameters:
stateMust be non-NULL.
Returns:
Next output sample (float _Complex).
>>> from doppler.wfm import _SynthEngine
>>> s = _SynthEngine(type="tone", fs=1.0, freq=0.0, snr=100.0)
>>> s.step()
(1+0j)
function wfm_synth_steps¶
Generate a block of output samples. Calls wfm_synth_step() in a tight loop, writing each cf32 sample intooutput . The Python binding returns a freshly allocated NumPy complex64 array; ownership is transferred to the caller.
Parameters:
stateInitialised Synth state returned bywfm_synth_create.outputOutput buffer of at leastncf32 elements.nNumber of samples to generate.
Macro Definition Documentation¶
define WFM_SYNTH_SNR_CLEAN¶
define WFM_SYNTH_STATE_MAGIC¶
define WFM_SYNTH_STATE_VERSION¶
The documentation for this class was generated from the following file native/inc/wfm_synth/wfm_synth_core.h