Skip to content

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. Whensnr_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 intooutput . 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.

JM_FORCEINLINE float _Complex wfm_synth_bit_symbol (
    wfm_synth_state_t * s
) 

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:

  • s Synth state; bits/n_bits must be non-empty, bit_idx advances.

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.

JM_FORCEINLINE int wfm_synth_bps (
    int type
) 

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.

JM_FORCEINLINE float wfm_synth_cont_dsss_chip (
    wfm_synth_state_t * s
) 

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:

  • type Waveform 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().
  • fs Sample rate in Hz. Sets the carrier frequency normalisation and the noise bandwidth. Default 1 000 000.0.
  • freq Carrier 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.
  • snr Target SNR in dB, interpreted per snr_mode. Values >= WFM_SYNTH_SNR_CLEAN (100) disable AWGN. Default 100.0.
  • snr_mode SNR reference: 0=auto, 1=fs (full-band), 2=ebno, 3=esno. The Python binding accepts strings "auto"|"fs"|"ebno"|"esno". Default 0.
  • seed PRNG seed shared by AWGN and the PN LFSR. Default 1.
  • sps Samples per symbol for modulated types (BPSK, QPSK, PN). Ignored for tone/noise. Default 8.
  • pn_length LFSR register length (1..64); period = 2^pn_length - 1. Default 7 (period 127).
  • pn_poly Galois 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.
  • lfsr LFSR realization: PN_GALOIS (0) or PN_FIBONACCI (1).
  • f_end Chirp end frequency in Hz (type=chirp only; ignored otherwise). With freq as the start, the instantaneous frequency sweeps linearly from freq to f_end over the span (set by wfm_synth_set_chirp_span() or the first wfm_synth_steps() call), then holds at f_end. f_end < freq is 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).

void wfm_synth_destroy (
    wfm_synth_state_t * state
) 

Parameters:

  • state Pointer to heap-allocated state; may be NULL.
    >>> from doppler.wfm import _SynthEngine
    >>> s = _SynthEngine(type="tone", fs=1.0, freq=0.0, snr=100.0)
    >>> s.destroy()   # explicit teardown; no exception
    

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.

float wfm_synth_get_cur_im (
    const wfm_synth_state_t * state
) 

Parameters:

  • state Must 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).

float wfm_synth_get_cur_re (
    const wfm_synth_state_t * state
) 

Parameters:

  • state Must 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.

int wfm_synth_get_nsps (
    const wfm_synth_state_t * state
) 

Parameters:

  • state Must be non-NULL.

Returns:

Samples per symbol (nsps >= 1).


function wfm_synth_get_state

void wfm_synth_get_state (
    const wfm_synth_state_t * state,
    void * blob
) 

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.

int wfm_synth_get_sym_pos (
    const wfm_synth_state_t * state
) 

Parameters:

  • state Must 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.

int wfm_synth_get_wtype (
    const wfm_synth_state_t * state
) 

Parameters:

  • state Must 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.

JM_FORCEINLINE uint64_t wfm_synth_mls_poly (
    uint32_t n
) 

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.

JM_FORCEINLINE float _Complex wfm_synth_next_symbol (
    wfm_synth_state_t * s
) 

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.

void wfm_synth_noise_steps (
    wfm_synth_state_t * state,
    float _Complex * output,
    size_t n
) 

Parameters:

  • state Synth state (may be NULL — no-op).
  • output n complex samples out.
  • n Sample 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.

void wfm_synth_reseed_noise (
    wfm_synth_state_t * state,
    uint32_t seed
) 

Parameters:

  • state Synth state (may be NULL).
  • seed New 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.

void wfm_synth_reset (
    wfm_synth_state_t * state
) 

Parameters:

  • state Must be non-NULL.
    >>> from doppler.wfm import _SynthEngine
    >>> import numpy as np
    >>> s = _SynthEngine(type="qpsk", sps=4, seed=1, snr=100.0)
    >>> a = s.steps(16).copy()
    >>> s.reset()
    >>> np.array_equal(a, s.steps(16))
    True
    

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:

  • state Must be non-NULL.
  • bits Array of n bytes, each 0 or 1.
  • n Number of bits (> 0).
  • modulation 0=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).

void wfm_synth_set_chirp_span (
    wfm_synth_state_t * state,
    size_t span
) 

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:

  • state Must be non-NULL.
  • span Sweep 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.

void wfm_synth_set_cur_im (
    wfm_synth_state_t * state,
    float val
) 

Parameters:

  • state Must be non-NULL.
  • val New 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.

void wfm_synth_set_cur_re (
    wfm_synth_state_t * state,
    float val
) 

Parameters:

  • state Must be non-NULL.
  • val New 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:

  • state Must be non-NULL.
  • acq_code Preamble code (0/1), length acq_len; NULL when acq_len*acq_reps == 0.
  • acq_len Preamble code length in chips.
  • acq_reps Preamble repetitions.
  • data_code Payload spreading code (0/1), length data_len.
  • data_len Chips per frame symbol (the spreading factor).
  • sync Frame-sync word bits (0/1); NULL for none.
  • sync_len Sync word length in bits.
  • payload Payload bits (0/1); NULL for a preamble-only burst.
  • payload_len Payload length in bits.
  • crc Non-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.

int wfm_synth_set_dsss_chips (
    wfm_synth_state_t * state,
    const uint8_t * chips,
    size_t n_chips
) 

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:

  • state Synth (no-op unless wtype == WFM_SYNTH_DSSS).
  • chips Burst chips, one per byte (0/1), BPSK-mapped by the synth.
  • n_chips Chip 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_BITSdata, 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:

  • state Synth (no-op unless wtype == WFM_SYNTH_DSSS).
  • code Spreading code chips (0/1), length code_len; copied.
  • code_len Spreading code length in chips (> 0) — the SF.
  • chips_per_symbol Chips per data symbol (>= 1), chip_rate / symbol_rate. Non-integer is the normal, asynchronous case.
  • data_mode WFM_DSSS_DATA_{NONE,BITS,PRBS}.
  • data Payload bits (0/1) for WFM_DSSS_DATA_BITS, length n_data; copied. Ignored (may be NULL) otherwise.
  • n_data Payload 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:

  • state Synth (no-op unless wtype == WFM_SYNTH_DSSS).
  • code_only_symbols Pure-code symbols opening each frame, at most frame_symbols. Equal to it means code only, for ever.
  • frame_symbols Frame 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.

void wfm_synth_set_nsps (
    wfm_synth_state_t * state,
    int val
) 

Parameters:

  • state Must be non-NULL.
  • val New nsps value (>= 1).

function wfm_synth_set_rrc

Enable RRC pulse shaping on a symbol synth (pn/bpsk/qpsk/bits).

int wfm_synth_set_rrc (
    wfm_synth_state_t * state,
    const float * taps,
    size_t ntaps
) 

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:

  • state Must be non-NULL.
  • taps Real FIR taps (copied).
  • ntaps Number of taps (> 0).

Returns:

0 on success; -1 on bad args / allocation failure.


function wfm_synth_set_state

int wfm_synth_set_state (
    wfm_synth_state_t * state,
    const void * blob
) 

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.

void wfm_synth_set_sym_pos (
    wfm_synth_state_t * state,
    int val
) 

Parameters:

  • state Must be non-NULL.
  • val New sym_pos value (0 <= val < nsps).

function wfm_synth_set_symbols

Attach a complex-symbol stream to a type=symbols synth (no-op else).

int wfm_synth_set_symbols (
    wfm_synth_state_t * state,
    const float _Complex * symbols,
    size_t n
) 

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:

  • state Must be non-NULL.
  • symbols Array of n complex symbols (copied).
  • n Number 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.

void wfm_synth_set_wtype (
    wfm_synth_state_t * state,
    int val
) 

Parameters:

  • state Must be non-NULL.
  • val New 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:

  • s Shaper-attached synth state (s->shaper != NULL).
  • out Output buffer, capacity >= m.
  • m Number of baseband samples to produce.
  • syms Caller 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.

JM_FORCEINLINE void wfm_synth_shaper_prime (
    wfm_synth_state_t * s
) 

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.

JM_FORCEINLINE double wfm_synth_snr_over_fs (
    int mode,
    int bps,
    double span,
    double snr
) 

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:

  • mode RESOLVED mode: 1 fs, 2 Eb/No, 3 Es/No. Never 0 (auto) — see below.
  • bps Bits per symbol, from wfm_synth_bps().
  • span Samples one symbol's energy is spread over.
  • snr The requested figure, in dB, in mode's reference.

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

size_t wfm_synth_state_bytes (
    const wfm_synth_state_t * state
) 

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.

JM_FORCEINLINE  JM_HOT float _Complex wfm_synth_step (
    wfm_synth_state_t * state
) 

Parameters:

  • state Must 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.

void wfm_synth_steps (
    wfm_synth_state_t * state,
    float _Complex * output,
    size_t n
) 

Parameters:

  • state Initialised Synth state returned by wfm_synth_create.
  • output Output buffer of at least n cf32 elements.
  • n Number of samples to generate.
    >>> 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.shape, x.dtype
    ((4,), dtype('complex64'))
    >>> x.tolist()
    [(1+0j), (1+0j), (1+0j), (1+0j)]
    

Macro Definition Documentation

define WFM_SYNTH_SNR_CLEAN

#define WFM_SYNTH_SNR_CLEAN `100.0`

define WFM_SYNTH_STATE_MAGIC

#define WFM_SYNTH_STATE_MAGIC `DP_FOURCC ('W','F','M','S')`

define WFM_SYNTH_STATE_VERSION

#define WFM_SYNTH_STATE_VERSION `2u /* v2: + continuous-DSSS chip/symbol clocks */`


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