Skip to content

Python Spectrum Analyzer API

doppler.analyzer is a single CPython extension exposing Specan — a streaming spectrum analyzer driven by the instrument parameters an operator already knows (center, span, RBW, reference level) instead of the DSP knobs (window length, Kaiser beta, zero-pad) underneath them.

It is the C-first home for the natural-parameter → DSP mapping: a Specan composes the DDC tuner/decimator and the PSD averaging-PSD core, so the whole chain lives in C exactly once and the doppler.specan application is a thin display/transport shell over it.


Specan

import numpy as np
from doppler.analyzer import Specan
from doppler.spectral import find_peaks_f32

# 200 kHz span, 500 Hz RBW around DC of a 2.048 MHz cf32 stream
sa = Specan(fs=2.048e6, span=200e3, rbw=500.0, center=0.0)
sa.fs_out, sa.n, sa.nfft, sa.display_size   # derived DSP grid
sa.rbw, sa.beta                              # realised RBW + the Kaiser beta

rng = np.random.default_rng(0)                # example cf32 source
iq_stream = [(rng.standard_normal(1 << 16)
              + 1j * rng.standard_normal(1 << 16)).astype(np.complex64)
             for _ in range(4)]
for chunk in iq_stream:                       # any cf32 block size
    db = sa.execute(chunk.astype(np.complex64))
    if db is None:                            # not enough samples for a frame
        continue
    # db is a DC-centred dB display band, length sa.display_size.
    # bin i → center + (i − display_size/2)·fs_out/nfft  Hz
    peaks = find_peaks_f32(db, 5, -60.0)      # peaks compose on the trace

sa.retune(50e3)        # cheap, seamless LO retune (no rebuild)
sa.destroy()

The constructor maps the instrument parameters to the DSP grid: the span sets the decimation rate (fs_out = span·1.28), the RBW sets the window length (coarse) and the Kaiser beta (fine, solved so the window ENBW realises the requested RBW), and the display is cropped to the central ±span/2 band. Changing the center is a cheap retune (the same instance keeps its filter history); changing the span or RBW alters the decimation rate and window length, so build a new Specan.

fs, span and rbw are required — omitting them raises TypeError rather than constructing an unusable analyzer. The display reads in dBFS against the PSD core's 0-dBFS reference — set it with bits (an ADC depth → 2**(bits-1)) or full_scale, the same single-source knob the measurement analyzers use. offset_db is an additive offset applied on top (e.g. a dBm calibration the application computes from a reference level). navg averages that many segments per emitted frame (navg=1 is a responsive single periodogram, larger navg trades update rate for a smoother, lower-variance floor). Peaks are intentionally not computed in the core — compose find_peaks_f32 on the returned dB band.

Specan

Create a natural-parameter spectrum analyzer.

Parameters:

Name Type Description Default
fs float

Input sample rate (Hz). Must be > 0.

required
span float

Display span (Hz). Must be > 0.

required
rbw float

Resolution bandwidth (Hz). Must be > 0.

required
src_center float

Source center frequency (Hz); the input band is centred here, so the analyzer mixes (center − src_center) to DC.

0.0
center float

Desired display center frequency (Hz).

0.0
offset_db float

Additive dB offset on the display spectrum, applied on top of dBFS (e.g. a dBm calibration the application computes from a reference level).

0.0
full_scale float

Amplitude that reads 0 dBFS (> 0). Ignored if bits > 0.

1.0
bits int

ADC depth: bits>0 sets the 0-dBFS reference to 2^(bits-1) in the shared PSD core (the single source of truth for the dBFS reference).

0
window Literal['hann', 'kaiser']

Window index: 0 = Hann, 1 = Kaiser (RBW-trimmable).

"kaiser"
navg int

Segments averaged per emitted frame (>= 1).

1

Examples:

Create with defaults:

>>> from doppler.analyzer import Specan
>>> obj = Specan(fs=2.048e6, span=200e3, rbw=500.0, src_center=0.0, center=0.0, offset_db=0.0, full_scale=1.0, bits=0, window="kaiser", navg=1)

fs_out property

fs_out: float

Fs out.

span property

span: float

Span.

rbw property

rbw: float

Rbw.

center property

center: float

Center.

beta property

beta: float

Beta.

n property

n: int

N.

nfft property

nfft: int

Nfft.

navg property

navg: int

Navg.

display_size property

display_size: int

Display size.

execute

execute(x: NDArray[complex64], out: NDArray[float32] | None = ...) -> NDArray[np.float32]

Mix, decimate, average; return one DC-centred dB display frame, or None.

Feeds x through the Ddc, buffers the decimated output, and once n·navg decimated samples are available windows + FFTs + averages them into a fresh frame, crops the central ±span/2 band and writes it in dB (+ ref_db). Returns 0 (writing nothing) until a frame is ready — the binding maps that to Python None.

Without out=, the returned array is a view into a buffer reused on the next call (see execute_max_out() to size an out= buffer for an independent, alias-free result).

Parameters:

Name Type Description Default
x NDArray[complex64]

cf32 input block (C-only; the binding passes it).

required
out NDArray[float32]

Caller-provided output buffer, at least max(execute_max_out(), len(x)) elements.

...

Returns:

Type Description
NDArray[float32]

Display bins written (disp_n), or 0 if no frame is ready yet.

Examples:

>>> from doppler.analyzer import Specan
>>> import numpy as np
>>> sa = Specan(fs=2.048e6, span=200e3, rbw=500.0, navg=1)
>>> sa.execute(np.zeros(64, dtype=np.complex64)) is None  # too few samples
True
>>> frame = sa.execute(np.zeros(65536, dtype=np.complex64))
>>> frame.shape, frame.dtype
((801,), dtype('float32'))

execute_max_out

execute_max_out() -> int

Max output length execute() can produce for the current state. Use to size the out= buffer.

retune

retune(center: float) -> None

Move the display center frequency (seamless LO retune; no rebuild).

Updates the Ddc LO phase increment (seamless across blocks — no resampler or window reset) and drops pending samples so the next frame reflects only the new tuning. Changing the span or RBW requires a destroy + create (the decimation rate and window length change).

Parameters:

Name Type Description Default
center float

New display center frequency (Hz).

required

reset

reset() -> None

Drop pending samples and the running average; zero LO/filter history.

state_bytes

state_bytes() -> int

Serialized state size in bytes.

get_state

get_state() -> bytes

Serialize the engine's mutable state to bytes.

set_state

set_state(blob: bytes) -> None

Restore mutable state from a get_state() blob.

destroy

destroy() -> None

Release C resources immediately.


See also