Skip to content

Quick Start

Get it!

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. Just want the C library itself (headers + libdoppler.a/.so, no example binaries)? jbx get-doppler grabs a pre-built release tarball instead — see C Library.

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.


Use the C library

Building against the C library — plain cc, CMake, or pkg-config, all three CI-verified to produce identical binaries? One page: C Quick Start.

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 (e.g. nats-server -js) 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

Build the C examples once, then mix and match:

make          # builds ./build/examples/c/transmitter, receiver, etc.

# Terminal 1 (transmitter takes ci32 or cf64 — not cf32)
./build/examples/c/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 --from-file scenario.json -o scenario.cf32                             # a multi-segment scene
wfmgen --type qpsk --continuous --realtime --output nats://127.0.0.1:4222/iq  # stream to NATS

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: . <(curl -sSL https://just-buildit.github.io/get-jb.sh).

git clone 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 + Rust test suites

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 jb.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 cmake pkg-config python3-dev \
  python3-numpy
sudo pacman -S --needed base-devel cmake pkgconf python python-numpy
sudo dnf install gcc make cmake pkgconf-pkg-config python3-devel \
  python3-numpy
sudo zypper install gcc make cmake pkg-config python3-devel python3-numpy
brew install cmake pkg-config python numpy

doppler does not target Windows natively — build under WSL2, a VM, or a container and follow the Ubuntu / Debian steps.

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


Next steps