Skip to content

doppler — signal, shifted

Practical, portable, performant digital signal processing.

CI Docs PyPI Python License: MIT

C99 Rust uv Ruff

doppler is a C99 DSP library — NCO, FIR, FFT, polyphase resampling, DDC, AGC and more — with file, buffer, and NATS-based streaming, and a scenario-driven waveform generator (wfmgen) with byte-identical CLI/Python/C parity. Python and Rust wrap the same C core — no second implementation, no divergence, full SIMD throughput from any language.

New here? Start with Start Here — a one-page map from "what are you trying to do" to the right doc.

NavigateQuick Start · Architecture · Gallery · Examples · Guides · Waveform Generator · Design · Contributing

API ReferenceFull Python + C API index


Why it's built this way

Every algorithm lives in C exactly once. The Python layer is type conversion and lifetime bridging — a few hundred lines of glue, not a reimplementation. Bugs get fixed once, benchmarks reflect real hardware, and a C transmitter talks to a Python subscriber without surprises.

Performance

On a Ryzen 7 AI 350 (-O2): NCO raw accumulator ~15 GSa/s, LO CF32 ~1.8 GSa/s, FIR CF32 ~900 MSa/s. The full generated table lives in Benchmarks; run make bench to measure on your hardware.

Quick start

See Quick Start for the full walkthrough.

Python

Install

Note

Isolate your install from system python with a virtual environment!

python3 -m venv .venv
. .venv/bin/activate
pip install doppler-dsp

Compute FFT

from doppler.spectral import FFT
import numpy as np

x = np.random.randn(1024).astype(np.complex64)
X = FFT(1024).execute_cf32(x)
print(f"FFT: {len(x)} samples in -> {X.shape[0]} complex64 bins out")

Create a Waveform

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")

C

Don't have jbx yet?

. <(curl -sSL https://just-buildit.github.io/get-jb.sh)

Install

Get libdoppler.a/libdoppler.so plus headers, ready to link, in one command:

jbx get-doppler

Compute FFT

/* example.c */

#include <complex.h>
#include <stdio.h>
#include <fft/fft_core.h>

int main(void)
{
  float complex in[1024]  = { 0 };   /* fill with your samples */
  float complex out[1024];

  fft_state_t *fft = fft_create(1024, -1, 1);  /* n, sign, nthreads */
  fft_execute_cf32(fft, in, 1024, out, 1024);  /* in,out: float complex[1024] */
  fft_destroy(fft);
  printf("FFT: 1024 samples in -> 1024 complex bins out\n");
  return 0;
}

Compile and run

cc example.c -I "$HOME/.local/doppler/include" "$HOME/.local/doppler/lib/libdoppler.a" -lm -o example
./example

Other install methods

Prefer a custom prefix or no jbx? Grab a pre-built release tarball by hand — no toolchain, no building doppler itself — and extract it to $PREFIX; you get the same libdoppler.a/libdoppler.so plus headers. See C Library for find_package/pkg-config integration and building from source.

Build

Building from source gets you the C library, examples, and Rust FFI bindings — see Build from source if you just want the C library without cloning the repo.

git clone https://github.com/doppler-dsp/doppler
cd doppler
make install-deps       # bootstrap jbx (if needed) + install system deps
make                    # C library
make pyext              # + Python bindings
make test               # CTest suite
make bench              # benchmarks

Docs

Full docs: doppler-dsp.github.io/doppler

Licensing

MIT. The core C library is pure C99 and links only -lm. Its FFT uses the vendored pocketfft (BSD-3-Clause) for double precision and arbitrary sizes, and the vendored PFFFT (Pommier/FFTPACK, BSD) for the native single-precision SIMD path. The optional NATS stream component (libdoppler_stream) vendors nats.c (Apache-2.0) — it too is pure C99, so no C++ toolchain is needed anywhere in the build.