Python Source API — NCO / LO / AWGN¶
Three signal-source classes in the doppler.source module:
| Class | Output | Use when |
|---|---|---|
LO |
CF32 complex phasors via 2¹⁶-entry sin/cos LUT | Generate IQ tones, FM signals |
NCO |
uint32 raw phase accumulator | Drive polyphase clock, generate carries |
AWGN |
CF32 complex Gaussian noise | Noise injection, SNR testing, Monte Carlo |
Source:
src/doppler/source/__init__.py
LO — complex phasor generator¶
96 dBc SFDR from 16-bit phase truncation into the 65 536-entry LUT.
from doppler.source import LO
import numpy as np
lo = LO(0.25) # normalised frequency: 0.25 → Fs/4
# Batch generate
iq = lo.steps(1024) # complex64, length 1024
print(iq[:4])
# [ 1.+0.j 0.+1.j -1.+0.j 0.-1.j ]
# Scalar (one sample at a time)
s = lo.step()
# FM control port — per-sample frequency deviation
ctrl = (0.002 * np.sin(2 * np.pi * 0.01 * np.arange(1024))).astype(np.float32)
iq_fm = lo.steps_ctrl(ctrl)
# Retune without resetting phase
lo.norm_freq = 0.1
Phase continuity¶
NCO — raw phase accumulator¶
Useful for generating sample-clock events (overflow carries) that drive a polyphase resampler.
from doppler.source import NCO
import numpy as np
nco = NCO(0.25)
# Raw 32-bit phase values
ph = nco.steps_u32(16)
# Overflow carry — 1 at each wrap (every 4 samples for 0.25)
carry = nco.steps_u32_ovf(16)
# carry: [0, 0, 0, 1, 0, 0, 0, 1, ...]
# Scaled to [0, nmax) — fixed-point multiply, no division
nco2 = NCO(0.25, nmax=1000)
scaled = nco2.steps_u32_scaled(16) # values in [0, 1000)
LO
¶
Create an LO instance. Allocates state, sets phase to 0, and derives phase_inc from norm_freq. Initialises the shared 65536-entry float LUT on the first call (single-threaded concern: call lo_create() before spawning threads that share LO instances).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
norm_freq
|
float
|
norm_freq constructor parameter. |
0.0
|
Examples:
Create with defaults:
norm_freq
property
writable
¶
Normalised frequency (read/write). Setting norm_freq recomputes phase_inc = floor(frac(v) × 2^32) and takes effect on the next lo_steps call; phase is NOT reset.
phase
property
writable
¶
Current phase accumulator value (read/write). Returns the current integer phase in [0, 2^32). Writing overrides the accumulator directly for phase-coherent frequency switching.
phase_inc
property
¶
Per-sample phase increment (read-only). Derived from norm_freq as floor(frac(norm_freq) × 2^32). A freq of 0.25 gives phase_inc = 1073741824 (0x40000000).
reset
¶
steps
¶
Generate n CF32 phasors at the current norm_freq. Each sample is cos(θ) + j·sin(θ) where θ is the phase BEFORE the accumulator is advanced, giving a unit-magnitude complex sinusoid via the 65536-entry LUT. SFDR ≈ 96 dBc. Returns n.
Returns:
| Type | Description |
|---|---|
NDArray[complex64]
|
n (always). |
Examples:
steps_ctrl
¶
Generate CF32 phasors with per-sample FM deviation. For each sample i, ctrl[i]'s fractional part is converted to a delta phase-increment (delta = floor(frac(ctrl[i]) × 2^32)) that is added on top of the base phase_inc for that one step only. The base norm_freq and phase_inc are NOT modified; the deviation is transient per sample, making this the natural API for FM synthesis and frequency-hopping. Output length equals ctrl_len. Returns ctrl_len.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
ctrl
|
NDArray[float32]
|
Float32 array of per-sample normalised-frequency deviations. Only the fractional part of each element contributes. |
required |
Returns:
| Type | Description |
|---|---|
NDArray[complex64]
|
ctrl_len (always). |
Examples:
NCO
¶
Create an NCO instance. Allocates and initialises the phase accumulator to zero, converts norm_freq to the integer phase_inc = floor(frac(norm_freq) × 2^32), and stores nmax for scaled output. The NCO is immediately ready to call nco_steps_u32 / nco_steps_u32_scaled / nco_steps_u32_ovf.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
norm_freq
|
float
|
norm_freq constructor parameter. |
0.0
|
nmax
|
int
|
nmax constructor parameter. |
0
|
Examples:
Create with defaults:
norm_freq
property
writable
¶
Normalised frequency (read/write). Setting norm_freq recomputes phase_inc = floor(frac(v) × 2^32) and takes effect on the next nco_steps_* call; phase is NOT reset.
phase
property
writable
¶
Current phase accumulator value (read/write). Reading returns the current integer phase in [0, 2^32). Writing overrides the accumulator directly, allowing arbitrary phase offsets without re-creating the NCO.
phase_inc
property
¶
Per-sample phase increment (read-only). Derived from norm_freq as floor(frac(norm_freq) × 2^32). Updated automatically whenever norm_freq is written. A freq of 0.25 gives phase_inc = 1073741824 (0x40000000).
reset
¶
Zero the phase accumulator. Sets phase to 0 so the next nco_steps_u32 call starts from the beginning of the cycle. norm_freq, phase_inc, and nmax are unchanged; the NCO is ready to generate samples again immediately.
Examples:
steps_u32
¶
Advance n samples; write raw uint32 accumulator values. Each element is the phase value BEFORE the increment fires, so out[0] is the phase at the moment of the call. The accumulator wraps silently at 2^32, giving the full-resolution integer ramp that the scaled and carry variants derive from. Returns n.
Returns:
| Type | Description |
|---|---|
NDArray[uint32]
|
n (always). |
Examples:
steps_u32_scaled
¶
Advance n samples; values scaled to [0, nmax). Uses the branchless fixed-point identity out[i] = (uint64_t)phase * nmax >> 32 to map the full accumulator range uniformly onto [0, nmax) without a modulo operation. When nmax == 0 falls back to the raw accumulator (identical to nco_steps_u32). Useful for polyphase filter bank indexing and direct LUT addressing. Returns n.
Returns:
| Type | Description |
|---|---|
NDArray[uint32]
|
n (always). |
Examples:
steps_u32_ovf
¶
Advance n samples; write raw phase values and per-sample carry. Identical to nco_steps_u32 for the phase array, but simultaneously fills a parallel uint8 carry buffer: out1[i] is 1 if the add that produced out[i]'s post-increment phase wrapped past 2^32, else 0. The carry marks the exact boundary of one input period and is the primitive for polyphase sample-clock and rational resampling engines. Returns n.
Returns:
| Type | Description |
|---|---|
tuple[NDArray[uint32], NDArray[uint8]]
|
n (always). |
Examples:
AWGN — Additive White Gaussian Noise¶
xoshiro256++ RNG + Box-Muller transform. Per-component std dev = amplitude.
AVX-512 path runs 8 independent streams in parallel (~525 MSa/s).
from doppler.source import AWGN
import numpy as np
g = AWGN(seed=42, amplitude=1.0)
noise = g.generate(1024) # complex64, length 1024
# Amplitude can be changed without disturbing the RNG state
g.amplitude = 0.5
# Deterministic replay
g.reset()
same_noise = g.generate(1024)
# New seed
g.reseed(999)
AWGN
¶
Create an AWGN generator. Allocates state, seeds the xoshiro256++ RNG via SplitMix64, and sets up both the scalar and the AVX2 parallel streams. The initial seed is stored so awgn_reset() can reproduce the exact same stream.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int
|
seed constructor parameter. |
0
|
amplitude
|
float
|
amplitude constructor parameter. |
1.0
|
Examples:
Create with defaults:
amplitude
property
writable
¶
Return the current amplitude (per-component std dev).
reset
¶
Reset RNG to the seed supplied at create time. Re-runs the SplitMix64 seeding procedure with the original seed so the next awgn_generate() call produces exactly the same samples as the first call after awgn_create(). amplitude is not changed.
Examples:
generate
¶
Generate n complex CF32 AWGN samples. Uses Box-Muller with xoshiro256++ to fill out with independent complex Gaussians: Re and Im each have zero mean and standard deviation amplitude. Total complex power = 2 × amplitude². The AVX2 path processes 8 samples in parallel when available.
Returns:
| Type | Description |
|---|---|
NDArray[complex64]
|
n (always). |
Examples:
reseed
¶
Reseed the RNG and reset all xoshiro256++ state. Equivalent to calling awgn_destroy() and awgn_create(seed, amplitude) but reuses the existing allocation. amplitude is unchanged.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
seed
|
int
|
New 64-bit RNG seed. |
required |
Returns:
| Type | Description |
|---|---|
complex
|
Output. |
Examples: