Skip to content

Python Quick Start

Using C?

This page is Python. To build a C program against the library, go to the C Quick Start: jbx get-doppler gets the library and headers in one command, and it builds with plain cc, CMake, or pkg-config.

Get it!

Note

Isolate your install from system python with a virtual environment! Debian and Ubuntu refuse a pip install outside one, and ship the module that makes one as a separate package:

sudo apt install python3-venv   # Debian / Ubuntu only
python3 -m venv .venv
. .venv/bin/activate
pip install doppler-dsp

The wheel bundles all native dependencies — no system libraries required. Everything under Signal processing and Streaming below runs against this install alone; the C transmitter example binary needs Build from source, called out at each point it applies.

Optional extras

pip install "doppler-dsp[specan]"      # terminal spectrum analyzer
pip install "doppler-dsp[specan-web]"  # live spectrum analyzer web UI
pip install "doppler-dsp[cli]"         # compose / Dopplerfile pipeline CLI

See Install → Python for the full extras table.

Or pull the container image

Every release publishes a ready-to-run image with the cli and specan-web extras pre-installed — doppler, doppler-fir, doppler-source, doppler-specan, and wfmgen are all on PATH:

docker pull ghcr.io/doppler-dsp/doppler:latest
docker run --rm ghcr.io/doppler-dsp/doppler wfmgen --help

Built for both linux/amd64 and linux/arm64. See Docker for details.


Signal processing

Every object is a thin, stateful wrapper over the C core: construct it once, then stream blocks through it. Each example below is self-contained — copy-paste any one of them on its own.

LO — generate a complex tone

from doppler.source import LO

lo = LO(0.25)          # normalised frequency: 0.25 → Fs/4
iq = lo.steps(8)
print(iq)
# [ 1.+0.j  0.+1.j -1.+0.j  0.-1.j ...]

For modulated waveforms, multi-segment scenes, and file/stream output, see Waveform Generator (wfmgen) below.

FFT

from doppler.spectral import FFT
import numpy as np

x = (np.random.randn(1024) + 1j * np.random.randn(1024)).astype(np.complex64)
fft = FFT(1024)
X = fft.execute_cf32(x)   # complex64 in → complex64 out (~2× faster than f64)
print(f"FFT: {X.shape[0]} complex64 bins")

FIR filter

from doppler.filter import FIR
from doppler.spectral import kaiser_window, kaiser_beta_for_sidelobe
import numpy as np

x = (np.random.randn(1024) + 1j * np.random.randn(1024)).astype(np.complex64)

n_taps, cutoff = 63, 0.05                    # cutoff: fraction of fs
m = np.arange(n_taps) - (n_taps - 1) / 2
taps = 2 * cutoff * np.sinc(2 * cutoff * m)  # ideal windowed-sinc lowpass
w = np.zeros(n_taps, dtype=np.float32)
kaiser_window(w, kaiser_beta_for_sidelobe(60.0))   # 60 dB sidelobe target
taps = (taps * w).astype(np.float32)

fir = FIR(taps)
y = fir.execute(x)
print(f"filtered {len(y)} samples through a {len(taps)}-tap FIR")

The walkthrough above spells out the windowed-sinc method by hand; for real use, doppler.filter.design_lowpass does the same design in one call — n_taps sized automatically from the requested band edges/attenuation, no scipy dependency (see Filter design helpers).

Resample

RateConverter picks the cheapest cascade (halfband / CIC / polyphase) for the rate you ask for — no filter design required:

from doppler.resample import RateConverter
import numpy as np

x = (np.random.randn(1024) + 1j * np.random.randn(1024)).astype(np.complex64)
rc = RateConverter(0.5)   # 2:1 decimation → auto-selects a halfband stage
y = rc.execute(x)         # 1024 → 512 samples
print(f"resampled {len(x)} -> {len(y)} samples")

Streaming

Doppler streams IQ data over NATS. Transmit and receive on the same machine or across a network — the API is identical. Either side needs a running NATS server (see Run a NATS broker) to connect to.

Publisher (Python)

from doppler.stream import Publisher, CF32
import numpy as np

pub = Publisher("nats://127.0.0.1:4222/iq", CF32)   # sample_type must match samples' dtype

samples = np.ones(1024, dtype=np.complex64)
pub.send(samples, sample_rate=1e6, center_freq=2.4e9)
print(f"sent {len(samples)} samples")

Subscriber (Python)

from doppler.stream import Subscriber

sub = Subscriber("nats://127.0.0.1:4222/iq")

samples, hdr = sub.recv()   # (ndarray, header dict) -- not an object with attributes
print(f"Received {len(samples)} samples @ {hdr['sample_rate']/1e6:.1f} MHz")

C transmitter → Python subscriber

The C examples are not in the pip wheel: they are built from a clone of the repository, once (all it needs is a C compiler — see Build from source):

git clone --branch v0.81.0 https://github.com/doppler-dsp/doppler
cd doppler
make          # builds ./build/native/examples/transmitter, receiver, etc.

Then mix and match, from that clone:

# Terminal 1 (transmitter takes ci32 or cf64 — not cf32)
./build/native/examples/transmitter nats://127.0.0.1:4222/iq cf64

# Terminal 2 (Python)
python - <<'EOF'
from doppler.stream import Subscriber
sub = Subscriber("nats://127.0.0.1:4222/iq")
while True:
    samples, hdr = sub.recv()
    print(f"seq={hdr['sequence']}  samples={len(samples)}")
EOF

Waveform Generator (wfmgen)

No extra required

wfmgen ships in the base pip install doppler-dsp wheel — no optional extra needed.

One engine generates a single waveform, a multi-segment JSON scene, or a live stream — the CLI and the Python API produce byte-identical output:

wfmgen --type qpsk --snr 12 --count 100000 -o capture.cf32                    # a single waveform
wfmgen json-template scenario.json                                            # write an example scene to edit
wfmgen --from-file scenario.json -o scenario.cf32                             # play the multi-segment scene
wfmgen --type qpsk --continuous --realtime --output nats://127.0.0.1:4222/iq  # stream to NATS

A scene is a JSON file of segments; json-template writes a working one (a tone, a QPSK burst, and two sources summed) for you to edit.

The same waveform from Python:

from doppler.wfm import Synth

synth = Synth(type="qpsk", fs=1e6, snr=12.0, snr_mode="esno", sps=8, seed=1)
iq = synth.steps(4096)   # complex64 ndarray
print(f"generated {len(iq)} QPSK samples")

See Waveform Generator (wfmgen) for scenes, BLUE/SigMF, streaming, and the Plan bit-exact sweep cache.


Spectrum analyzer

doppler-specan opens a live FFT display in your terminal or browser.

Requires the specan or specan-web extra

pip install "doppler-dsp[specan]"      # terminal
pip install "doppler-dsp[specan-web]"  # browser

Demo mode (no hardware needed):

doppler-specan --source demo

Browser UI:

doppler-specan --source demo --web

The web UI is served at http://127.0.0.1:8765 by default. See Spectrum Analyzer for configuration options.


Pipeline CLI

Requires the cli extra

pip install "doppler-dsp[cli]"

doppler compose wires blocks into a processing pipeline defined in a YAML file.

doppler compose init tone fir specan --name my_pipeline --out my_pipeline.yaml
doppler compose up my_pipeline.yaml
doppler ps
doppler logs my_pipeline

See CLI & Pipelines and Dopplerfile for writing custom blocks.


Build from source

Only need the C library itself (headers + libdoppler.a/.so, no examples, no Rust FFI, no toolchain)? jbx get-doppler — see Get it! above — is faster. This section is for the examples, the Rust FFI bindings, running the test suite, or contributing.

Don't have jbx yet?

make install-deps bootstraps it for you (installs system build dependencies too). Or by hand: Get jbx.

git clone --branch v0.81.0 https://github.com/doppler-dsp/doppler
cd doppler
make install-deps  # bootstrap jbx (if needed) + install system deps
make               # C library + examples
make pyext         # Python extensions
make test-all      # C, Python and example test suites (Rust: make test-rust)

You'll need a C compiler — your system's default one is enough, no C++ toolchain is required anywhere in the build (the core library and the optional stream component, which vendors nats.c, are both pure C99).

Installing system deps by hand instead

make install-deps reads bootstrap.toml, the single source of truth for doppler's system deps, so it stays in sync automatically. To install them yourself instead:

sudo apt-get install build-essential ccache cmake pkg-config python3-dev \
  python3-numpy
sudo pacman -S --needed base-devel ccache cmake pkgconf python python-numpy
sudo dnf install gcc make ccache cmake pkgconf-pkg-config python3-devel \
  python3-numpy
sudo zypper install gcc make ccache cmake pkg-config python3-devel \
  python3-numpy
brew install cmake ccache pkg-config python numpy

The C library builds natively with clang or clang-cl (Visual Studio's C++ Clang tools for Windows): see Build from Source → Windows for the commands and what the Windows build leaves out, or download the pre-built zip: Install the C library → Windows. For Python, pip install doppler-dsp installs the Windows wheel, the same package as on Linux and macOS.

See Build from Source for CMake options, Docker, and platform-specific notes.


Next steps