Scientific validation: emitters/torchsig_fm

Verdict. Validated with documented limitations.


1. The component

TorchSigFMEmitter generates a clean complex-baseband waveform for analog frequency modulation (FM: a modulation scheme where information is encoded in the instantaneous frequency of a carrier, not its amplitude). The instantaneous frequency wanders around zero according to a band-limited Gaussian noise message; the output is exp(j·φ(t)), a unit-envelope complex tone (IQ signal: in-phase and quadrature components stacked as a (2, N) tensor).

Two class labels are supported: nbfm (NBFM: narrowband FM, used in channelised voice radio) and wbfm (WBFM: wideband FM, used in commercial broadcast). The labels are distinguished by per-label defaults and ceilings on the Carson 98%-containment bandwidth (Carson bandwidth: the spectral window that contains 98% of the signal’s average power, named after J. R. Carson’s 1922 rule), anchored on real-world channel standards.

Class signature

class TorchSigFMEmitter(BaseEmitter):
    family: ClassVar[EmitterFamily]         # EmitterFamily.COMMS
    supported_classes: ClassVar[tuple[str, ...]]  # ("nbfm", "wbfm")

    def __init__(self) -> None: ...

    def generate(
        self,
        *,
        class_label: str,          # "nbfm" or "wbfm"
        sample_rate: float,        # Hz; Nyquist rate for the complex-baseband record
        duration_s: float,         # seconds; record length
        f_offset_hz: float,        # Hz; digital baseband frequency shift
        rng: torch.Generator,      # seeded generator for reproducibility
        device_id: str | None = None,
        params: BaseModel | None = None,  # TorchSigFMParams
    ) -> Signal: ...

Parameters

Name

Type

Units

Default

Purpose

class_label

str

n/a

required

"nbfm" or "wbfm"; selects per-label bandwidth defaults and ceilings

sample_rate

float

Hz

required

Complex-baseband Nyquist rate; must satisfy bandwidth_hz <= sample_rate/2

duration_s

float

s

required

Record length; round(sample_rate * duration_s) samples are produced

f_offset_hz

float

Hz

required

Baseband frequency shift applied as iq * exp(j·2π·f·t)

rng

torch.Generator

n/a

required

Seeded random generator; same seed produces identical IQ

device_id

str &#124; None

n/a

None

Propagated to SignalMetadata.device_id

params

TorchSigFMParams &#124; None

n/a

None

Pydantic model with optional bandwidth_hz field

params.bandwidth_hz

float &#124; None

Hz

label default

Carson 98%-containment bandwidth; None uses the label’s standard default

Per-label defaults and ceilings:

Label

Default bandwidth_hz

Ceiling

Standard

nbfm

12,500 Hz

25,000 Hz

ETSI EN 300 086 land-mobile voice (12.5 / 20 / 25 kHz raster)

wbfm

150,000 Hz

200,000 Hz

FCC 47 CFR 73.317 commercial broadcast FM (200 kHz channel raster)

Worked example

import torch
from rfgen.emitters import TorchSigFMEmitter, TorchSigFMParams

em = TorchSigFMEmitter()
g = torch.Generator()
g.manual_seed(42)

sig = em.generate(
    class_label="wbfm",
    sample_rate=2_000_000.0,   # 2 MHz sample rate
    duration_s=0.1,             # 100 ms = 200,000 samples
    f_offset_hz=0.0,
    rng=g,
    params=TorchSigFMParams(bandwidth_hz=200_000.0),  # 200 kHz Carson bandwidth
)

print(sig.iq.shape)   # torch.Size([2, 200000])
print(sig.iq.dtype)   # torch.float32
print(sig.metadata.bandwidth_hz)    # 200000.0
print(sig.metadata.class_taxonomy)  # ('comms', 'fm', 'wbfm')

The emitter wraps torchsig.signals.builders.fm.fm_modulator. TorchSig internally draws a modulation index m ~ Uniform(1, 10), computes fdev = (bandwidth_hz/2) / (1 + 1/m) and fmax = fdev/m, then builds a Gaussian-noise message filtered to fmax and integrates the phase. The wrapper adds the Nyquist guard, per-label ceiling enforcement, and the f_offset_hz baseband shift.


2. What we validated

This validation establishes 7 load-bearing claims. Each is restated and supported by evidence in §3.

  1. Output shape and metadata contract (§3.1): the emitter produces a (2, N) float32 IQ tensor and correct SignalMetadata.

  2. Carson 98%-containment bandwidth (§3.2): the Carson bandwidth identity holds at algebraic precision; measured containment equals 1.0000 across all tested operating points.

  3. Constant-envelope invariant (§3.3): FM is phase-only modulation; |I + jQ| stays at unity for both WBFM and NBFM.

  4. WBFM fits the FCC 47 CFR 73.317 commercial broadcast mask (§3.4): at bandwidth_hz = 200 kHz, full power fits inside the 200 kHz channel.

  5. NBFM and WBFM per-label defaults produce distinguishable IQ (§3.5): per-label bandwidth defaults route the two labels through different spectral regimes.

  6. Baseband frequency shift is exact (§3.6): f_offset_hz migrates the spectral peak by the requested amount.

  7. Boundary and error enforcement (§3.7): per-label ceilings, Nyquist guards, and non-finite input rejection all raise the correct error types.

Limits and scope-bounded items appear in §4; full citations are in §5.


3. Evidence per claim

3.1 Output shape and metadata contract

The emitter returns a Signal whose iq field is a (2, N) torch.float32 tensor with N = round(sample_rate * duration_s). SignalMetadata carries the realised bandwidth, sample rate, carrier offset, class taxonomy, and generator name.

sig.iq.shape  == (2, N)
sig.iq.dtype  == torch.float32
sig.metadata.bandwidth_hz         == bandwidth_hz (as resolved from params or label default)
sig.metadata.realized_carrier_hz  == f_offset_hz
sig.metadata.class_taxonomy       == ("comms", "fm", class_label)
sig.metadata.snr_db               == float("inf")   # clean baseband

Tests: test_iq_shape_dtype (3 (fs, dur_s, expected_n) parameter combinations), test_metadata_carries_request, test_family_and_supported_classes in tests/validation/emitters/torchsig_fm/test_experiment_contract.py. All pass.

3.2 Carson 98%-containment bandwidth

The claim. The bandwidth_hz parameter is the Carson 98%-containment bandwidth: the spectral window [-bandwidth_hz/2, +bandwidth_hz/2] that contains 98% (or more) of the signal’s average power. This is the standard definition from Haykin, Communication Systems, 5th ed., Ch. 5 (Carson’s rule: B_carson = 2·(fdev + fmax)).

Mathematical basis. TorchSig picks m ~ Uniform(1, 10) and sets fdev = (bandwidth_hz/2) / (1 + 1/m) and fmax = fdev/m. Then:

2·(fdev + fmax) = 2·fdev·(1 + 1/m) = 2·(bandwidth_hz/2 / (1 + 1/m))·(1 + 1/m) = bandwidth_hz

This is an algebraic identity that holds for every draw of m, not a statistical approximation. The iterative-design LPF (low-pass filter: a digital filter that passes frequencies below a cutoff and attenuates those above) inside TorchSig hard-band-limits the message to fmax, so all signal energy sits inside ±bandwidth_hz/2.

Measured result. Containment fraction = 1.0000 (±0.0001 Welch leakage floor) across four (label, bandwidth_hz) pairs: (wbfm, 200 kHz), (wbfm, 180 kHz), (nbfm, 12.5 kHz), (nbfm, 25 kHz).

Figure 1 shows the measured containment fraction across the full valid bandwidth sweep for both labels, confirming the Carson rule holds at every tested point with a margin of approximately 30 standard deviations above the 0.98 lower bound.

Figure 1: Carson 98%-containment fraction across the valid bandwidth sweep. Circles are nbfm operating points (5, 12.5, 25 kHz); squares are wbfm operating points (50, 100, 150, 200 kHz). Red dashed line marks the 0.98 lower bound from Carson's rule. All 12 seeds per point show containment ≥ 0.9999, well above the bound. Supports the Carson bandwidth claim.

Figure 2 shows the average Welch PSD (power spectral density: signal energy plotted against frequency) for WBFM and NBFM at their respective default bandwidths, confirming the two labels produce distinct emission widths.

Figure 2: Average Welch PSD over 20 seeds for WBFM (left, bandwidth_hz = 200 kHz) and NBFM (right, bandwidth_hz = 12.5 kHz). Red dashed lines mark the Carson half-bandwidth. The WBFM PSD fills the 200 kHz window; the NBFM PSD is confined to the 12.5 kHz window (approximately 16x narrower). Supports the distinguishable-IQ and Carson claims.

Tests: test_carson_98_percent_containment (4 parametrized cases), test_carson_holds_across_bandwidth_sweep (6 cases across both labels), test_carson_holds_across_sample_rate_sweep (4 sample rates) in tests/validation/emitters/torchsig_fm/test_empirical_known_results.py and test_robustness_envelope.py.

Note on -3 dB bandwidth. A reader expecting bandwidth_hz to equal the -3 dB bandwidth will be surprised. For m ~ Uniform(1, 10) the -3 dB point sits near fmax = bandwidth_hz / (2·(m+1)), approximately an order of magnitude below bandwidth_hz. The Carson 98%-containment interpretation is the one this parameter controls; the module docstring states this explicitly.

3.3 Constant-envelope invariant

The claim. FM is phase-only modulation: s(t) = exp(j·φ(t)), which has |s(t)| = 1 at every sample by construction. The wrapper deliberately does NOT subtract the empirical complex-baseband mean (unlike the digital-comms wrapper). For narrow bandwidths the phase trajectory walks slowly enough that the empirical mean magnitude can reach ~0.4 over short records; subtracting it would collapse the unit envelope into a ~30%-RMS (root-mean-square: the quadratic average of a signal’s amplitude) amplitude variation, converting a textbook constant-envelope FM signal into an AM-contaminated artefact.

Measured result. Envelope mean = 1.000, std < 0.005 for WBFM at 200 kHz bandwidth; envelope mean = 1.000, std < 0.05 for NBFM at 12.5 kHz bandwidth (float32 cast introduces small rounding ripple at narrow bandwidths).

Figure 3 shows the IQ envelope over time for both WBFM and NBFM realisations, confirming the unit-envelope property holds for both labels.

Figure 3: FM envelope |I + jQ| vs. time for WBFM (top, bandwidth_hz = 200 kHz) and NBFM (bottom, bandwidth_hz = 12.5 kHz), seed = 7. Both panels show mean ≈ 1.000 with small ripple. Dotted line marks unit envelope. Supports the constant-envelope claim.

Figure 4 shows the instantaneous frequency trajectory (how fast the signal’s frequency is changing at each moment) for both labels, confirming the deviation stays within the Carson half-bandwidth.

Figure 4: Instantaneous frequency (time-derivative of the unwrapped phase, in Hz) vs. time for WBFM (top) and NBFM (bottom), seed = 11. Red dashed lines mark the Carson half-bandwidth (fdev + fmax = bandwidth_hz/2). The frequency excursion stays within the bounds, confirming the LPF hard-limits the message. Supports the constant-envelope and Carson claims.

Tests: test_envelope_approximately_unit_wbfm, test_envelope_approximately_unit_nbfm, test_constant_envelope_holds in tests/validation/emitters/torchsig_fm/test_empirical_known_results.py.

3.4 WBFM fits the FCC 47 CFR 73.317 broadcast mask

The claim. At bandwidth_hz = 200 kHz the emitter produces a WBFM signal whose full power lies inside the FCC 47 CFR 73.317 commercial broadcast channel (±100 kHz around the carrier). The FCC requires ≥99% in-channel power at 200 kHz channel spacing.

Measured result. Power fraction inside ±100 kHz = 1.0000 (measured at 2 MHz sample rate, 0.1 s capture, Welch nperseg = 16384). The fraction exceeds the FCC 99% requirement by a large margin because TorchSig’s iterative-design LPF hard-limits the message; there is negligible spectral leakage beyond the Carson boundary.

Tests: test_wbfm_200khz_channel_fits_fcc_mask in tests/validation/emitters/torchsig_fm/test_empirical_known_results.py.

Out-of-band floor. Beyond twice the Carson half-bandwidth, the average PSD is below -50 dB relative to the in-band peak. Test: test_oob_floor_below_minus_50_db.

3.5 NBFM and WBFM per-label defaults produce distinguishable IQ

The claim. Calling the emitter with class_label = "nbfm" (no explicit bandwidth_hz) routes through the ETSI EN 300 086 default of 12.5 kHz; calling with class_label = "wbfm" routes through the FCC 47 CFR 73.317 default of 150 kHz. The two labels cannot silently produce the same IQ under any seed.

Figure 5 shows the realised bandwidth_hz from SignalMetadata across 10 seeds for both labels, confirming the defaults are constant and differ by a factor of 12.

Figure 5: Realised bandwidth_hz from metadata across 10 seeds for nbfm (circles, 12.5 kHz) and wbfm (squares, 150 kHz). Each seed shows the correct per-label default, confirming the two labels route through distinct spectral regimes. Supports the distinguishable-IQ claim.

Tested. test_nbfm_wbfm_default_bandwidth_differs in tests/validation/emitters/torchsig_fm/test_experiment_contract.py confirms both the bit-difference and the metadata bandwidth values.

3.6 Baseband frequency shift is exact

The claim. Setting f_offset_hz = f applies the complex multiply iq * exp(j·2π·f·t), which shifts the baseband spectrum by exactly f Hz without distorting the waveform.

Measured result. At f_offset_hz = 200 kHz, the spectral peak migrates from 0 to 200 kHz within ±30 kHz tolerance (approximately half the empirical -3 dB bandwidth of the FM waveform at bandwidth_hz = 100 kHz).

Figure 6 shows the Welch PSD before and after the frequency shift for three offset values, confirming the spectral envelope translates cleanly.

Figure 6: Welch PSD for f_offset_hz in {-300, 0, +300} kHz (blue, green, orange). The spectral envelope translates by the requested offset without shape distortion. bandwidth_hz = 100 kHz, sample_rate = 2 MHz, seed = 2. Supports the baseband-shift claim.

Tests: test_f_offset_shifts_spectrum in tests/validation/emitters/torchsig_fm/test_experiment_contract.py.

Determinism. Same seed produces bit-identical IQ; different seeds produce different IQ. Tests: test_same_seed_same_iq, test_different_seed_different_iq.

3.7 Boundary and error enforcement

The emitter enforces all boundary conditions before calling TorchSig, so every error surfaces as a typed rfgen exception rather than a raw backend error.

Boundary

Expected error

Test

bandwidth_hz > per-label ceiling

EmitterError naming the standard

test_nbfm_bandwidth_above_ceiling_raises, test_wbfm_bandwidth_above_ceiling_raises

bandwidth_hz > sample_rate/2

EmitterError with Nyquist context

test_bandwidth_above_fs_over_two_raises_emitter_error

bandwidth_hz + 2·&#124;f_offset_hz&#124;sample_rate

EmitterError with shifted-extent context

test_offset_plus_bw_exceeds_budget_raises, test_offset_exactly_at_budget_raises

bandwidth_hz < 1 Hz

pydantic.ValidationError from ge=1.0

test_tiny_bandwidth_rejected_by_schema

Non-finite f_offset_hz, sample_rate, duration_s

EmitterError with field name

test_nan_offset_rejected_with_emitter_error, test_nan_sample_rate_rejected_with_emitter_error, test_nan_duration_rejected_with_emitter_error, test_inf_offset_rejected_by_budget

round(sample_rate * duration_s) == 0

EmitterError

test_zero_samples_raises

TorchSig backend absent

BackendUnavailableError

test_backend_missing_surfaces_actionable_error

Unsupported class_label

EmitterError listing supported labels

test_unsupported_class_raises

All tests in tests/validation/emitters/torchsig_fm/test_robustness_envelope.py and test_experiment_contract.py pass.


4. Limits and what’s not validated

No audio source. The message is white Gaussian noise band-limited to fmax. Real FM transmissions carry voice, music, or signalling tones with distinctive spectral and temporal structure absent here.

No pre-emphasis. Real FM broadcast applies 75 µs (US) or 50 µs (Europe) pre-emphasis (a high-frequency boost applied to the audio before modulation) before modulation. The emitter omits this because pre-emphasis is part of an audio pipeline outside the framework’s scope.

No stereo pilot or sub-carriers. Commercial FM stereo (19 kHz pilot, 38 kHz L-R subcarrier) and RDS (57 kHz subcarrier) are not present. These belong to a higher-layer broadcast emitter.

No CTCSS sub-audible tones. Narrowband FM voice channels use CTCSS (Continuous Tone-Coded Squelch System: a sub-audible tone that activates a receiver’s squelch circuit) for squelch control. Out of scope at the modulation-class abstraction level.

No transmitter impairments. No PA (power amplifier) nonlinearity, IQ imbalance, oscillator phase noise, or carrier-frequency drift. These are deferred to the channel-pipeline layer per the rfgen architecture.

No frame structure. Continuous modulation only; no PTT (push-to-talk) on/off envelope, no preamble, no inter-burst silence.

Modulation index not user-controllable. m ~ Uniform(1, 10) is drawn per call inside TorchSig and cannot be fixed. Standards typically pin m (e.g. broadcast FM has effective m in [1, 5] depending on programme content; GSM uses m = 0.5). Exposing m would require patching TorchSig or switching to its lower-level signal-generator API.

No upper bound on n_samples. Very large sample_rate * duration_s products can exhaust memory. Resource caps belong in the pipeline-configuration layer.


5. References

5.1 Published works

Citation

Role

J. R. Carson, “Notes on the Theory of Modulation,” Proc. IRE, vol. 10, no. 1, pp. 57–64, Feb. 1922

Original derivation of Carson’s rule; the bandwidth_hz parameter is its Carson 98%-containment bandwidth

S. Haykin, Communication Systems, 5th ed., Wiley, 2009, Ch. 5

Canonical FM textbook covering Carson’s rule, Bessel sidebands, NBFM vs. WBFM, and pre-emphasis; cited in the module docstring

L. W. Couch, Digital and Analog Communication Systems, 8th ed., Pearson, 2013, eq. 5-94

Carson’s rule restatement and modulation index definition

FCC 47 CFR §73.317, FM transmission system requirements (current)

US commercial FM mask: 75 kHz peak deviation, 15 kHz audio bandwidth, 200 kHz channel raster; anchors the wbfm ceiling

ETSI EN 300 086, Land Mobile Service; Radio equipment with an internal or external RF connector intended primarily for analogue speech

NBFM channel raster 12.5 / 20 / 25 kHz; anchors the nbfm ceiling

ITU-R BS.450-3, Transmission standards for FM sound broadcasting at VHF (2001)

International FM broadcast values match the FCC mask numerically

M. Boegner, J. West, et al., “Large Scale Radio Frequency Signal Classification,” arXiv:2207.09918 (2022), doi:10.48550/arXiv.2207.09918

TorchSig library and dataset citation

5.2 Libraries

PyPI distribution

Installed version

Documentation

Role in this validation

torchsig

2.1.1

https://github.com/TorchDSP/torchsig

Provides fm_modulator, the iterative-design LPF, and phase integration; the primary backend under test

torch

2.12.1

https://pytorch.org/docs/stable/

torch.Generator for reproducible seeding; torch.from_numpy for IQ tensor construction

scipy

1.18.0

https://docs.scipy.org/doc/scipy/

scipy.signal.welch (Welch PSD estimator) used for all spectral containment and OOB (out-of-band: signal energy falling outside the intended frequency channel) floor measurements

numpy

2.4.6

https://numpy.org/doc/stable/

Complex arithmetic, np.unwrap for instantaneous frequency, np.random.default_rng for the per-call numpy seed

matplotlib

3.11.0

https://matplotlib.org/stable/

Figure generation in tests/validation/emitters/torchsig_fm/generate_figures.py