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 <math.h>

Classes

Type Name
struct wfm_synth_state_t
Synth state.

Public Types

Type Name
enum wfm__synth__core_8h_1adc29c2ff13d900c2f185ee95427fb06c

Public Functions

Type Name
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)
Maximal-length-sequence (MLS) primitive polynomial for an LFSR of the given register length n, in pn_core's right-shift Galois convention. Returns 0 for lengths outside 2..64 (caller errors). Generated from verified primitive polynomials (period 2^n-1); the n=2..16 values are unchanged.
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.
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.
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 1u

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_1adc29c2ff13d900c2f185ee95427fb06c

enum wfm__synth__core_8h_1adc29c2ff13d900c2f185ee95427fb06c {
    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
};

Waveform type discriminant (the type create argument / type choice).


Public Functions Documentation

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. The Python binding accepts strings "tone"|"noise"|"pn"|"bpsk"|"qpsk"|"chirp"|"bits"|"symbols". For "bits" attach the pattern with wfm_synth_set_bits(); for "symbols" attach the complex stream with wfm_synth_set_symbols() 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

Maximal-length-sequence (MLS) primitive polynomial for an LFSR of the given register length n, in pn_core's right-shift Galois convention. Returns 0 for lengths outside 2..64 (caller errors). Generated from verified primitive polynomials (period 2^n-1); the n=2..16 values are unchanged.

JM_FORCEINLINE uint64_t wfm_synth_mls_poly (
    uint32_t n
) 


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_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_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 `1u`


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