Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Ви не можете вибрати більше 25 тем Теми мають розпочинатися з літери або цифри, можуть містити дефіси (-) і не повинні перевищувати 35 символів.
Jan Svabenik 88a1a7736a feat: add Linux PlutoSDR support and Orange Pi build tooling 1 місяць тому
..
API.md feat: add live audio ingest pipeline for on-air streaming 1 місяць тому
DSP-CHAIN.md feat: add BS.412 limiter and document the clip-filter-clip chain 1 місяць тому
NOTES.md docs: bootstrap fm rds transmitter project plan 1 місяць тому
README.md docs: add API reference for live TX control and hot reload 1 місяць тому
config.orangepi-pluto-soapy.json feat: add Linux PlutoSDR support and Orange Pi build tooling 1 місяць тому
config.plutosdr.json feat: add driver/uri/deviceArgs backend config plumbing 1 місяць тому
config.sample.json refactor: remove unused input sample rate config 1 місяць тому

README.md

fm-rds-tx docs

Build & Test

Root CLI

  • go test ./...
  • go run ./cmd/fmrtx -print-config
  • go run ./cmd/fmrtx -config docs/config.sample.json
  • go run ./cmd/fmrtx --dry-run --dry-output build/dryrun/frame.json
  • go run ./cmd/fmrtx --simulate-tx --simulate-output build/sim/simulated-soapy.iqf32 --simulate-duration 250ms
  • go run ./cmd/offline -duration 500ms -output build/offline/composite.iqf32

Audio source modes

Current no-hardware sources:

  • generated stereo tones via config
  • 16-bit PCM WAV file input via audio.inputPath (robust chunk-scanning loader)
  • linear-interpolation sample-rate conversion for WAV sources
  • transparent tone fallback if the configured WAV source cannot be loaded

Tone configuration

The current no-hardware source can be parameterized via config:

  • audio.toneLeftHz
  • audio.toneRightHz
  • audio.toneAmplitude

DSP chain

The full signal chain from audio input to IQ output:

  1. Audio ingest — tone generator or WAV file with linear-interpolation resampler
  2. Gain staging — configurable audio gain
  3. Pre-emphasis — first-order IIR high-shelf filter, configurable τ (50 µs EU / 75 µs US / 0 = off)
  4. Stereo encoder — L+R mono, L-R on stateful 38 kHz DSB-SC subcarrier, phase-coherent 19 kHz pilot
  5. RDS encoder — standards-grade group framing (0A/2A), CRC-10 per IEC 62106, differential encoding, 57 kHz BPSK subcarrier, group scheduler cycling PS and RadioText
  6. MPX combiner — configurable gains for mono, stereo, pilot, and RDS components
  7. Output drive — configurable output level scaling
  8. MPX limiter — smooth attack/release peak limiter with hard-clip safety net
  9. FM modulator — composite-to-IQ via phase integration, configurable ±75 kHz deviation, unit-magnitude IQ output

Pre-emphasis

FM broadcast requires pre-emphasis to boost high frequencies before transmission. The receiver applies complementary de-emphasis to restore flat response while reducing noise.

  • Europe/World: τ = 50 µs (preEmphasisUS: 50)
  • North America/South Korea: τ = 75 µs (preEmphasisUS: 75)
  • Disabled: preEmphasisUS: 0

FM modulation modes

  • fmModulationEnabled: true — output is baseband FM-modulated IQ (I² + Q² = 1). This is what SDR transmitters expect.
  • fmModulationEnabled: false — output is raw composite MPX (I = composite, Q = 0). Useful for analysis or composite exciters.

Split-rate mode (Pluto / HackRF)

When deviceSampleRateHz > compositeRateHz (e.g. Pluto at 2.28 MHz, composite at 228 kHz), the engine automatically activates split-rate mode:

  1. DSP chain (stereo, RDS, limiter) runs at compositeRateHz (228 kHz)
  2. FMUpsampler performs FM modulation + phase-domain interpolation to deviceSampleRateHz
  3. Hardware receives IQ at device rate

This halves CPU load compared to running all DSP at device rate. Log output confirms the active mode:

engine: split-rate mode — DSP@228000Hz → upsample@2280000Hz (ratio 10.00)

When rates are equal (e.g. LimeSDR at 228 kHz), same-rate mode is used:

engine: same-rate mode — DSP@228000Hz

Limiter

The MPX limiter prevents overmodulation by applying smooth gain reduction when the composite signal exceeds the configured ceiling. A hard clipper acts as a safety net after the limiter.

  • limiterEnabled: true/false
  • limiterCeiling: 1.0 (max composite level before FM modulation)

HTTP control surface

Full API documentation: docs/API.md

All major TX parameters are hot-reloadable via POST /config during live transmission — frequency, stereo/mono, RDS text, output drive, pilot/RDS levels, limiter. Changes take effect within 50–88ms without stopping the stream.

Available endpoints: /healthz, /status, /runtime, /config (GET/POST), /dry-run, /tx/start, /tx/stop

Internal DSP module

  • cd internal
  • go test ./...

Examples module

  • cd examples
  • go test ./...
  • go run ./soapy_simulated

Dry run

The dry-run mode generates a synthetic, hardware-free frame summary based on the current config. It reports the active source label, pre-emphasis setting, limiter state, and FM modulation mode.

The HTTP control plane also exposes GET /dry-run for quick inspection.

Simulated transmit

--simulate-tx runs the offline generator through the Soapy-oriented simulated backend path and writes an IQ-style artifact to disk.

Offline generation

cmd/offline generates a deterministic no-hardware IQ/composite file using the full DSP chain (pre-emphasis, stereo encoding, RDS, limiter, FM modulation).

Release posture

Current honest release posture: pre-v1. Recommended milestone tag: v0.4.0-pre. See CHANGELOG.md and RELEASE.md.