Emitters

Note

Layer 3 shipped, Pass-1. The Layer 3 implementation (emitters, device-fingerprint, tx-impairments, propagation, rx-frontend) landed on branch rfgen-impl-2026-06-25-105955 (PR #94). The class names, Pydantic schemas, and Transformation enum members referenced below match the shipped surface; Pass-1 stubs (GNU Radio OOT emitters, cellular emitters, Sionna propagation backends) construct cleanly and raise an EmitterError or ChannelError tagged with stage="pass1_stub" until backend wiring lands. See Reference / rfgen.emitters and Reference / rfgen.channels for the shipped class roster.

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.

An emitter creates the clean signal a transmitter would have produced before the scene places it in time, frequency, and space. The signal is complex baseband in-phase/quadrature (IQ): samples are centered at zero hertz in the emitter’s own frame, while carrier placement stays in metadata for later scene and channel stages.

Everything downstream should be able to consume that output without knowing whether the source was LoRa (a low-power chirp-spread-spectrum radio family), frequency-modulated continuous-wave (FMCW) radar, Long-Term Evolution (LTE) cellular, a TorchSig benchmark modulation, or captured playback from recorded IQ.

rfgen delegates waveform synthesis to mature libraries instead of inventing radio-frequency (RF) generators. The per-family backend choices are grounded in Library Landscape: TorchSig signals and Sig53 for RF machine-learning benchmark modulations, Sionna physical-layer (PHY) primitives and 3rd Generation Partnership Project (3GPP) TS 38.211 for 5G New Radio (NR), srsRAN 4G for LTE, gr-lora_sdr for LoRa, RadarSimPy / SciPy chirp / Richards for radar, and SigMF for captured IQ playback.

The emitter contract is intentionally small: implement the waveform source, publish the supported labels and parameter schema, and return a Signal with SignalMetadata. Scenes, channels, labels, and storage handle placement, propagation, supervision, and persistence.

Minimal worked example

import torch
from rfgen.emitters import LoRaEmitter

emitter = LoRaEmitter()
signal = emitter.generate(
    # LoRa chirp spread spectrum, spreading factor 7, 125 kHz bandwidth,
    # coding rate 4/5. See Signal Catalog for the label taxonomy.
    class_label="iot.lora.css.sf7.bw125.cr-4-5",
    sample_rate=1_000_000,
    duration_s=0.020,
    f_offset_hz=0.0,
    rng=torch.Generator().manual_seed(0),
)
assert signal.metadata.family == "lora"
assert signal.metadata.bandwidth_hz == 125_000.0
assert signal.metadata.snr_db == float("inf")  # clean baseband; channel adds noise

For the browsable list of supported families with parameter ranges and example configs, see Signal Catalog.

Adjacent Concepts

Two emitter topics are important enough to have their own pages. Keeping them separate prevents this page from becoming a dependency catalog or a validation report.

Sub-concept

What it covers

See

Library landscape

Which mature tool or implementation strategy backs each family, and where optional dependencies are isolated.

Library Landscape

Coverage

What the catalog covers in practice, how coverage is measured, and where validation data is required.

Coverage

A reader writing a new emitter or scoping a sim2real study will want both. A reader trying to understand the emitter contract (this page) does not.

What an emitter does

An emitter (BaseEmitter subclass) returns a Signal wrapping the clean IQ and its SignalMetadata.

The metadata record stamps three things:

  • Identity: family, class_name, class_taxonomy, generator_name, and (when supplied) device_id. Tells downstream what was emitted, which implementation produced it, and which virtual TX device it belongs to.

  • Time-frequency extent: sample_rate_hz, bandwidth_hz, duration_samples. Tells downstream how the IQ sits on the time and frequency axes in the emitter’s own frame.

  • Clean-noise state: snr_db = +inf. Emitters never add propagation, receiver, or thermal noise; the channel layer is what makes SNR finite.

Source: the proposed BaseEmitter contract and Reference / Contract Tests require emitter outputs to be baseband, metadata-bearing, deterministic, and clean; Concepts / Channels owns propagation, RX capture, RX hardware, and receiver noise.

Channels, the scene composer, and the labeler fill in the rest of the record: scene-relative placement, channel realizations, receiver outputs, and derived annotations. Full field-by-field reference belongs on the SignalMetadata API page.

The emitter also honors the RNG contract: random payloads, jitter, burst timing, and protocol choices are drawn from the supplied generator. Re-running the same emitter with the same resolved configuration, seed, dependency versions, and device determinism settings should reproduce the same clean IQ. Full normative contract on the BaseEmitter API page.

What an emitter does not do

  • Center-frequency placement in a wideband scene → handled by the scene composer.

  • Channel propagation (reflections from multiple paths, signal weakening with distance, motion-related Doppler shifts, receive (RX) capture, and RX hardware effects including low-noise amplifier (LNA) thermal noise) → handled by channels.

  • Time placement in a scene → scene composer.

  • Label generation → the label layer reads component_signals after the scene composer assembles the scene.

The emitter is a pure waveform source. Everything that depends on receiver geometry, propagation, or downstream supervision lives elsewhere.

Two cross-layer subtleties matter:

  1. Emitters own waveform-intrinsic frequency behavior. Frequency-sweeping families such as LoRa chirp spread spectrum (CSS) and FMCW radar must not be reduced to “generate at direct current (DC), then blindly frequency-shift later” if that would break the waveform structure. The vocabulary for emitter-relative and scene-relative frequency lives in Coordinate Systems.

  2. Emitters can carry a device_id, which identifies transmitter hardware. The impairment math runs in the channel layer, but the identity travels with the emitted signal so scenes, channels, and labels can agree on the same virtual radio. See TX Impairments.

Source: gr-lora_sdr is the planned LoRa reference backend for chirp spread spectrum waveforms; SciPy documents frequency-swept chirps in scipy.signal.chirp, and Richards, Fundamentals of Radar Signal Processing, 2nd ed., is the cited radar waveform reference in Library Landscape. The RF-frame handoff is defined in Coordinate Systems § Frame Transitions.

Adding a custom emitter

This section is a contract sketch for readers who need to recognize the shape of an emitter plugin. Step-by-step implementation instructions belong in How-to / Add a custom emitter.

Subclass BaseEmitter, implement the waveform generation and schema methods, and register the family:

from rfgen.emitters.base import BaseEmitter
from rfgen.core.registry import register_emitter
from rfgen.core.types import Signal, SignalMetadata
from rfgen.enums import EmitterFamily

@register_emitter(family=EmitterFamily.COMMS)
class MyRadioEmitter(BaseEmitter):
    family = EmitterFamily.COMMS
    supported_classes = ("mode_a", "mode_b")

    def generate(self, *, class_label, sample_rate, duration_s, f_offset_hz,
                 rng, device_id=None, params=None):
        # synthesize IQ at baseband, apply f_offset_hz internally if needed
        ...
        return Signal(iq=iq, metadata=SignalMetadata(...))

    def schema(self):
        return MyRadioParams  # Pydantic validation model for emitter parameters

The contract reference is on the emitters API page, and LoRaEmitter is the canonical worked example.

See Also

Why ABC instead of duck-typed callables

The base class gives every emitter the same lifecycle: validate parameters, generate clean IQ, check metadata invariants, and expose a schema. That keeps family-specific synthesis local while scenes, channels, labels, and storage depend on one stable interface.

Why stateless?

Workers can instantiate emitters freely per shard, sample, or test without reset logic. State that should persist across samples, such as per-device fingerprint parameters, is keyed by device_id outside the emitter instance.

What we deliberately do not model
  • Receiver-side hardware effects, including low-noise amplifier (LNA) noise and analog-to-digital converter (ADC) quantization; those live in the channel layer, after propagation.

  • Protocol-stack-level effects (medium access, retransmissions, carrier sensing) are outside scope; we model PHY only.

  • Power-amplifier modeling beyond Rapp / Saleh: fine for fingerprinting, coarse for power-amp characterization research.

  • Cellular base-station scheduling: LTE/NR emitters produce single-cell downlink (DL) waveforms; multi-cell coordination is left to scene composition.

Source: RX capture and RX hardware define LNA noise and ADC as channel transformations; TX impairments cites the Rapp and Saleh PA models used by the proposed PA slot; Background / Sionna stack records that Sionna PHY targets NR physical-layer primitives, while heterogeneous multi-emitter scene composition remains an rfgen responsibility.