Quick Start¶
Get it!¶
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
Demo mode (no hardware needed):
Browser UI:
The web UI is served at http://127.0.0.1:8765 by default.
See Spectrum Analyzer for configuration options.
Pipeline 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:
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¶
- Architecture — design overview and layer diagram
- Examples: C · Streaming
- API reference — full C and Python API docs
- Waveform Generator (wfmgen) — scenes, BLUE/SigMF, NATS streaming, and the Plan sweep cache
- Spectrum Analyzer — specan configuration
- CLI & Pipelines — compose and Dopplerfile