Skip to content

API taxonomy: the DSP building-block hierarchy and its naming axis

Status: draft / for discussion Scope: distill doppler's implicit organizing vision for the DSP building blocks in objects/*.toml into an explicit hierarchy, so that naming a new class — or renaming an existing one — follows from where it sits, not from whatever felt right at the time. Concrete rename proposals are included; none have landed yet except the one that motivated this doc (ChannelDespreader).


1. Why this doc exists

Renaming track.Channel (a GPS-flavored name — its own docstring said "GPS-style tracking channel," and its nav_period constructor param is GPS/GNSS jargon for "code periods per data bit") surfaced a real question: what should it be called instead? Answering that for one class exposed that doppler has never written down the horizontal structure its API is organized around — only the vertical one.

architecture.md states the vertical stack clearly: DSP library → transport → pipeline CLI → apps. That tells you nothing about how the ~50 classes inside the DSP library relate to each other, which is exactly the context naming needs. That horizontal structure turns out to already exist, just scattered across three places that mostly agree:

  • The gallery nav groups examples into informal categories (Sources & Waveforms, Filters & Resampling, Detection & Acquisition, Synchronization Loops, Constellations & Receivers, Measurement, Quantization & Fixed-Point, Gain Control).
  • dsss-use-cases.md states a real design principle explicitly for one slice of the library: DSSS receivers come in exactly two flavors — UC1, "GPS-like always on" (continuous), and UC2, "Burst Transmission" (latency-bound) — built from the same acquisition primitives but composed differently downstream. It even says the burst path "hands off... to the shipped Despreader" — so dsss.Despreader was already conceived, in the design's own language, as the burst payload tracker.
  • The naming survey run while researching this doc, which checked all ~50 classes against the Channel/nav_period pattern.

This doc distills those three into one explicit hierarchy, states the naming axis each layer should follow, and proposes renames where a class doesn't fit its layer's axis.


2. The hierarchy

Every class is a DSP building block at one of eight conceptual layers, independent of which Python module it currently lives in (module boundaries are a packaging/build concern — see repository-map.md — not a naming one, though closer alignment between the two is a nice side effect where it's cheap).

# Layer What it does Current members
1 Sources produce samples from nothing LO, NCO, PN, AWGN, the wfm_compose family
2 Filtering & rate conversion reshape a stream's spectrum/rate FIR, CIC, Resampler, RateConverter, HalfbandDecimator, HBDecimQ15, Farrow, DDC
3 Detection & acquisition find presence/timing/frequency once, no persistent feedback Corr, Corr2D, Detector, Detector2D, Acquisition, PolyPhaseEstimator
4 Tracking & synchronization loops continuously refine an estimate via feedback, sample-by-sample LoopFilter, Costas, Dll, CarrierMpsk, CarrierNda, SymbolSync, MpskReceiver
5 DSSS composite receivers combine layers 3+4 into one PN-aware receiver, in exactly two flavors continuous: track.Channel (today); burst: dsss.Despreader, BurstDemod
6 Measurement & analysis characterize signal quality PSD, ToneMeasure, NPRMeasure, IMDMeasure, Specan
7 Quantization & fixed-point model/convert numeric representations ADC, the cvt family, Q15/UQ15
8 Support gain control, accumulation, plumbing AGC, AccF32/AccCf64/AccQ15/AccQ8/AccTrace, Buffer, DelayCf64

3. The naming axis per layer

Most layers already hold one naming axis consistently — the trouble is concentrated in layers 3–5, which never had one written down:

Layer Axis Holds today?
1 Sources mechanism (NCO, PN) yes
2 Filtering mechanism (FIR, CIC, Corr-adjacent) yes, modulo §4.2 below
3 Detection should be mechanism — currently mixed with a too-generic bare noun no — see §4.4
4 Tracking loops should be one of {mechanism, target-signal-type} — currently mixes both no — see §4.6
5 DSSS composites framing (continuous/burst) + role (despread/demod) now yes, once §4.1 lands
6 Measurement what it measures (ToneMeasure) yes
7 Quantization representation (Q15, UQ15) yes
8 Support role yes

4. Rename proposals

4.1 track.Channeldsss.Despreader (continuous), dsss.Despreaderdsss.BurstDespreader

The rename that started this doc. Per §1, the design doc's own language already treats the burst path's target as "the shipped Despreader" — so the existing dsss.Despreader keeps the base name and picks up the Burst prefix that BurstDemod already establishes, and track.Channel — a Costas+DLL continuous tracker with no burst-specific features (no preamble-aided acquisition) — becomes the plain, continuous-flavored Despreader, moved into dsss to sit next to its burst sibling.

From To Module
track.Channel Despreader trackdsss
dsss.Despreader BurstDespreader dsss (unchanged)
Channel's nav_period param periods_per_bit (moves with the class)

nav_period is GPS/GNSS jargon for "code periods per data bit" — the same smell as the class name it lived on. periods_per_bit says exactly that, generically.

Not yet decided: whether BurstDemod (frame-sync + CRC on top of despreading) should also move to sit next to both, or stays separate as a higher-scope object built on BurstDespreader. Flagging, not proposing.

4.2 dsss.PolyPhaseEstimator (ppe) → PolynomialPhaseEstimator or ChirpEstimator

Does polynomial-phase (frequency + chirp-rate) estimation via a 2-D matched-filter/dechirp search — nothing to do with polyphase filter-bank structures. This is worse than an external-domain leak: it collides with the library's own vocabulary. "Polyphase" is used consistently elsewhere (Resampler, RateConverter, HalfbandDecimator, DDC, NCO, Farrow) to mean the classic decimation/interpolation branch structure — layer 2's axis. Compressing "polynomial-phase" into the visually/verbally identical "PolyPhase" breaks that axis for anyone skimming the API.

From To
PolyPhaseEstimator PolynomialPhaseEstimator or ChirpEstimator

No preference stated yet between the two — ChirpEstimator is shorter and matches how the algorithm is usually described in prose; PolynomialPhaseEstimator is more literal and avoids implying a specific chirp waveform rather than the general polynomial-phase model.

4.3 filter.HBDecimQ15resample.HalfbandDecimatorQ15

Same halfband 2:1 decimator algorithm as resample.HalfbandDecimator, differing only by dtype (Q15 fixed-point vs. CF32) — but one name is abbreviated, the other spelled out, and they live in different modules with no naming cue that they're siblings.

From To Module
filter.HBDecimQ15 HalfbandDecimatorQ15 filterresample

4.4 spectral.Detector / Detector2DCorrDetector / CorrDetector2D

Too generic for what it actually is — an FFT-correlation + CFAR-threshold detector, built directly on Corr/Corr2D but with no naming relationship to either. "Detector" alone could mean an edge detector, onset detector, envelope detector, preamble detector, etc., in different DSP subfields.

From To
Detector CorrDetector
Detector2D CorrDetector2D

4.5 track.MpskReceiver's auto_handover param → acq_to_track or auto_mode_switch

Cellular-network jargon ("handover" = transferring a call between base stations) describing what's purely an internal loop-mode transition: swap from the NDA acquisition-mode carrier loop to a lower-jitter decision-directed tracking-mode loop once locked. Same flavor of leak as nav_period.

From To
auto_handover acq_to_track or auto_mode_switch

4.6 Flagged, no rename proposed yet

  • track.Costas vs. track.CarrierMpskCarrierMpsk's own docstring states "at m=2 this is exactly the BPSK Costas loop." Same overlap pattern as Channel/Despreader, but retiring or aliasing Costas is a bigger design call than a naming fix — flagging for the same kind of reorg conversation, not proposing a rename here.
  • Layer 4's mixed axisCostas/Dll/LoopFilter name by mechanism; CarrierMpsk/CarrierNda name by target-signal-type. Worth picking one axis deliberately once the Costas/CarrierMpsk overlap above is resolved, since the axis choice and the overlap are the same underlying question.

5. Lower-confidence / not actioned

Found during the survey, judged borderline or low-priority — noted for completeness, not proposed for action:

  • Acquisition's cn0_dbhz param — GNSS term, but also standard general RF link-budget vocabulary; borderline rather than a clear leak.
  • dwell across the Corr/Corr2D/Detector/Detector2D family — reads as radar/EW jargon outside that subfield, but used consistently across the whole family, so low priority.
  • CarrierNda — "Nda" (non-data-aided) is synchronization-theory terminology, less immediately recognizable than sibling CarrierMpsk's modulation-based name — this is the layer-4 axis inconsistency in §4.6, not a standalone issue.
  • BurstDemod — legitimately a bigger-scope object (dechirp + despread + frame-sync + CRC) than a despreader. Not itself a naming defect; worth double-checking it doesn't get confused with the incoming BurstDespreader once §4.1 lands.

Everything else checked (FFT/FFT2D, FIR, CIC, AWGN, DDC, the cvt and Acc* families, the Resampler/RateConverter/HalfbandDecimator family, Specan, SymbolSync, the measurement suite, DelayCf64) held up against its layer's axis with no changes suggested.

Out of scope for this survey (not class_name-declared objects in objects/*.toml): the wfm_compose classes (Synth, Segment, Timeline, Composer, Plan) and Ddcr (the ddc_fn handle module) — worth a follow-up pass if this taxonomy should extend there too.

6. See also