rfgen.emitters

The catalog of waveform generators. Every concrete class subclasses BaseEmitter and produces a baseband SignalMetadata-tagged IQ tensor.

Scientific validation

The emitter backends have been scientifically validated against published references. See the per-backend reports:

Each report covers construct validity, mathematical correctness against cited equations, empirical comparison to published reference numbers, literature grounding, experimental methodology, operating envelope, and documented limitations.

Module summary

import torch
import rfgen.emitters as emitters

emitter = emitters.ChirpRadarEmitter()  # always available (scipy is a runtime dep)
signal = emitter.generate(
    class_label="lfm_chirp",
    sample_rate=10_000_000,
    duration_s=0.001,
    f_offset_hz=0.0,
    rng=torch.Generator().manual_seed(0),
)

Every emitter has the same surface (the generate keyword-only signature, the family and supported_classes ClassVars, schema() returning a Pydantic model). What differs is the synthesis math, the parameter schema, and the dependency tier.

Importing rfgen.emitters does NOT force-import Sionna, srsRAN, RadarSimPy, gr-lora_sdr, or any other optional dependency; missing-extra emitters raise BackendUnavailableError only when the class is actually instantiated.

Class index

Class

Family

supported_classes

Backend / extra

Notes

BaseEmitter

abstract

abstract

abc

ABC; subclass to add a custom emitter

AdsBEmitter

adsb

("adsb_extended_squitter",)

pyModeS; rfgen[adsb]

1090 MHz Mode-S; Pass-1 stub (generate() raises until Pass-N)

BLEEmitter

iot

("ble",)

GNU Radio OOT; rfgen[ble]

BLE; Pass-1 stub

ChirpRadarEmitter

radar

("lfm_chirp",)

scipy.signal.chirp (always available)

LFM chirp; fully implemented

LTEEmitter

cellular

("lte_pusch", "lte_pdsch")

srsRAN over ZMQ; rfgen[lte]

LTE; Pass-1 stub

LoRaPHYEmitter

iot

("lora",)

loraphy pure-Python; rfgen[lora-phy]

LoRa fallback backend; Pass-1 stub

LoRaSdrEmitter

iot

("lora",)

gr-lora_sdr GNU Radio OOT; rfgen[lora-sdr]

Default LoRa backend; Pass-1 stub

NRPuschEmitter

cellular

("nr_pusch",)

sionna.phy.nr; rfgen[sionna]

5G NR PUSCH; Pass-1 stub

PulsedRadarEmitter

radar

("pulsed",)

radarsimpy; rfgen[radarsim]

Pulsed radar; Pass-1 stub

TorchSigAMEmitter

comms

TorchSig AM signal builders

torchsig; rfgen[torchsig]

AM-family (DSB-FC, DSB-SC, SSB-USB, SSB-LSB)

TorchSigChirpEmitter

comms

TorchSig chirp signal builders

torchsig; rfgen[torchsig]

LoRa-style chirp spread spectrum

TorchSigCommsEmitter

comms

TorchSig constellation builders

torchsig; rfgen[torchsig]

PSK / QAM / OOK / ASK constellations

TorchSigFMEmitter

comms

TorchSig FM builders

torchsig; rfgen[torchsig]

NB-FM / WB-FM

TorchSigFSKEmitter

comms

TorchSig FSK builders

torchsig; rfgen[torchsig]

FSK / GFSK / MSK / GMSK

TorchSigOFDMEmitter

comms

TorchSig OFDM builders

torchsig; rfgen[torchsig]

OFDM-{64…2048} sizes

TorchSigToneEmitter

comms

TorchSig tone builder

torchsig; rfgen[torchsig]

CW tone

WifiEmitter

iot

("wifi",)

GNU Radio OOT; rfgen[wifi]

802.11 a/g/p; Pass-1 stub

ZigbeeEmitter

iot

("zigbee",)

GNU Radio OOT; rfgen[zigbee]

802.15.4 OQPSK; Pass-1 stub

Each emitter ships a matching *Params Pydantic v2 model (e.g. ChirpRadarParams, LoRaParams, LTEParams) returned from schema() and re-exported from rfgen.emitters.

For the conceptual map of supported families and roadmap items deferred to a future emitter expansion (drone family, GSM, captured playback, NR downlink, extended radar waveforms), see Signal Catalog. For synthesis algorithms, see Reference / Algorithms.

LoRa: dual backend

The LoRa family ships two backends behind one shared schema. Both accept ("lora",) as their only class label and share LoRaParams for the per-emission parameters; selection happens at scene-config time via the LoRaBackend enum ("gr-lora-sdr" or "lora-phy").

  • LoRaSdrEmitter is the default, backed by gr-lora_sdr (GNU Radio OOT; typically installed via conda or a system package manager, not PyPI). Behind rfgen[lora-sdr].

  • LoRaPHYEmitter is the pure-Python fallback, backed by the loraphy library. Behind rfgen[lora-phy].

Both subclass an internal _LoRaBase ABC that fixes family = EmitterFamily.IOT and supported_classes = ("lora",); per-emission knobs (spreading_factor, bandwidth_hz, coding_rate, payload_bytes) live in the shared LoRaParams model. Backend selection happens at scene-config time via the LoRaBackend enum ("gr-lora-sdr" or "lora-phy").

Earlier drafts described a LoRaEmitter class with a multi-class supported_classes taxonomy of dotted strings (e.g. "iot.lora.css.sf7.bw125.cr-4-5"); the shipped framework keeps the SF/BW/CR knobs on LoRaParams and uses the single "lora" label so the scene composer treats LoRa as a single family with backend-selectable internals.


class rfgen.emitters.BaseEmitter

Produces IQ for one emitter on a shared time/frequency grid. Stateless across calls; all randomness comes from the supplied rng.

from abc import ABC, abstractmethod
from typing import ClassVar
import torch
from pydantic import BaseModel

class BaseEmitter(ABC):
    """One emitter family. Implementations are stateless; call producing IQ."""

    family: ClassVar[EmitterFamily]
    supported_classes: ClassVar[tuple[str, ...]]

    @abstractmethod
    def generate(
        self,
        *,
        class_label: str,
        sample_rate: float,
        duration_s: float,
        f_offset_hz: float,
        rng: torch.Generator,
        device_id: str | None = None,
        params: BaseModel | None = None,
    ) -> Signal: ...

    @abstractmethod
    def schema(self) -> type[BaseModel]: ...

Contract

  • Output IQ shape MUST be (2, int(sample_rate * duration_s)).

  • Output IQ MUST be at baseband; f_offset_hz is the desired offset from baseband DC, applied by the emitter (not the caller). Some emitters (LoRa CSS, FMCW radar) sweep across frequency intrinsically, so post-hoc mixing would alias the chirp slope.

  • Returned Signal.metadata MUST be self-consistent: start_sample within [0, int(sample_rate * duration_s)), frequency-extent fields within [-sample_rate/2, +sample_rate/2].

  • Implementations MUST consume randomness only from rng for determinism under shard re-runs.

  • params is a backend-specific Pydantic model (e.g. ChirpRadarParams); validated by the caller against the emitter’s schema() before calling.

  • device_id, when set, is used by the fingerprint module to pick a deterministic CFO/IQ-imb/phase-noise profile per virtual device.

Class attributes

Attribute

Type

Description

family

ClassVar[EmitterFamily]

Top-level family tag, copied into every emitted SignalMetadata.family

supported_classes

ClassVar[tuple[str, ...]]

Whitelist of class_label values this emitter accepts; _check_class_label() raises EmitterError if a call uses an unknown label

Method: generate

Abstract method on BaseEmitter. Produces one emitter’s IQ plus its ground-truth metadata. See the full normative contract above.

Method: schema

Abstract method on BaseEmitter. Returns the Pydantic v2 model describing valid params for this emitter, used by the config validator and by rfgen inspect emitters --schema <family>.

Why stateless?

Spark workers spin emitters up per shard; statelessness lets us instantiate freely without worrying about reset between calls. State that should persist across emitters (per-device fingerprint parameters) lives in rfgen.device_fingerprint.DeviceRegistry, keyed by device_id.

Why is f_offset applied by the emitter, not the scene composer?

Some emitters (LoRa CSS, FMCW radar) sweep across frequency intrinsically; mixing them up after the fact would break their structure. Letting each emitter own its baseband-to-target translation keeps the composition layer agnostic.


Stub class anchors

The catalog rows above each follow the same template as ChirpRadarEmitter (the fully-implemented reference example). Most concrete emitters in this Layer 3 release ship as Pass-1 stubs: the constructor validates the lazy-import gate and pins the class attributes, but generate() raises EmitterError until the backend wiring lands in a future Pass-N. The TorchSig comms backends (TorchSigCommsEmitter, TorchSigFSKEmitter, TorchSigOFDMEmitter, TorchSigAMEmitter, TorchSigFMEmitter, TorchSigChirpEmitter, TorchSigToneEmitter) and ChirpRadarEmitter are fully implemented.

The anchor labels below give every shipped class a stable cross-reference target so concept pages, glossary entries, and how-to guides can use {ref} links per the STYLE.md code-span linking rule.

Per-protocol algorithm references:

Anchor aliases (cross-reference compatibility)

The anchors below preserve cross-reference targets from earlier design drafts that named classes the shipped framework does not export. Each resolves to the class index above; readers looking for a class name that no longer exists will land on the index and see what shipped instead.

Earlier drafts described LoRaEmitter (now LoRaSdrEmitter + LoRaPHYEmitter, see LoRa: dual backend) and LTEDownlinkEmitter (the shipped class is LTEEmitter with supported_classes = ("lte_pusch", "lte_pdsch")). The drone family (OcuSyncShellEmitter, LightbridgeShellEmitter, FPVAnalogEmitter, MAVLinkTelemetryEmitter, CrossfireELRSEmitter), the extended radar waveforms (FMCWRadarEmitter, FrequencyHoppingRadarEmitter), GSMEmitter, NRDownlinkEmitter, and CapturedPlaybackEmitter are planned for a future emitter expansion and not yet implemented; see the Deferred for future emitter expansion section of the signal catalog for the canonical deferral list.