Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Jan b7dffa8d26 test(stereo): cover mono delay compensation пре 1 месец
aoiprxkit fix RTP payload aliasing in jitter buffer пре 1 месец
cmd fix(tx): decouple watermark from evaluation jingle пре 1 месец
docs fix(ui): clarify limiter behavior and active radio text пре 1 месец
examples Add output backend abstractions пре 1 месец
internal test(stereo): cover mono delay compensation пре 1 месец
libiio feat: add hardware TX mode with PlutoSDR and SoapySDR drivers пре 1 месец
scripts feat: add Linux PlutoSDR support and Orange Pi build tooling пре 1 месец
.gitignore feat: add driver/uri/deviceArgs backend config plumbing пре 1 месец
CHANGELOG.md docs: mark current hardware baseline as v0.7.0-pre пре 1 месец
PROJECT_PLAN.md docs: switch plan to windows-first and soapy-first пре 1 месец
README.md docs(ingest): align phase-1 status and decoder fallback semantics пре 1 месец
RELEASE.md docs: mark current hardware baseline as v0.7.0-pre пре 1 месец
fm-rds-tx_pro_runtime_hardening_concept.json docs: add pro runtime hardening concept пре 1 месец
go.mod ingest: add srt source support via aoiprxkit пре 1 месец
go.sum ingest: add native ogg vorbis decoder пре 1 месец
stream_tx.bat feat(rds2): add LPS, eRT and RDS2 controls пре 1 месец

README.md

fm-rds-tx

Go-based FM stereo transmitter with RDS. Supports ADALM-Pluto (PlutoSDR) and SoapySDR-compatible TX devices.

Status

Current status: v0.9.0 — runtime hardening milestone

What is already in place:

  • complete DSP chain: audio -> pre-emphasis -> stereo encoding -> RDS -> MPX -> limiter -> FM modulation
  • real hardware TX paths for PlutoSDR / SoapySDR backends
  • continuous TX engine with runtime telemetry
  • dry-run, offline generation, and simulated TX modes
  • HTTP control plane with live config patching and runtime/status endpoints
  • browser UI on /
  • ingest runtime in front of TX stream sink, plus shared source/runtime stats
  • ingest source factory for stdin, http-raw, and icecast
  • Icecast source adapter with reconnect and decoder selection (auto/native/ffmpeg)
  • decoder layer with explicit ffmpeg fallback path

Current engineering focus:

  • merge/release stabilization after runtime hardening
  • deferred hardware-in-the-loop / RF validation work
  • deferred device-aware capability / calibration work
  • deferred signal self-monitoring work
  • finish native Icecast decoder wiring (mp3/oggvorbis/aac are placeholders; ffmpeg fallback is the currently functional decode path)

For the active runtime-hardening track, see:

  • docs/pro-runtime-hardening-workboard.md

Signal path

Audio Source -> PreEmphasis(50us/75us/off) -> StereoEncoder(19k + 38k DSB-SC)
-> RDS(57k BPSK) -> MPX Combiner -> Limiter -> FM Modulator(+/-75kHz)
-> optional split-rate FM upsampling -> SDR backend -> RF output

For deeper DSP details, see:

  • docs/DSP-CHAIN.md

Prerequisites

Go

  • Go version from go.mod (currently Go 1.22)

Native SDR dependencies

Depending on backend, native libraries are required:

  • SoapySDR backend

    • build with -tags soapy
    • requires SoapySDR native library (SoapySDR.dll / libSoapySDR.so / libSoapySDR.dylib)
    • on Windows, PothosSDR is the expected setup
  • Pluto backend

    • uses native libiio
    • Windows expects libiio.dll
    • Linux build/runtime expects pkg-config + libiio
  • validate RF output, deviation, filtering, and power with proper measurement equipment
  • use only within applicable legal and regulatory constraints

Quick start

Build

# Build CLI tools without hardware-specific build tags:
go build ./cmd/fmrtx
go build ./cmd/offline

# Build fmrtx with SoapySDR support:
go build -tags soapy ./cmd/fmrtx

Quick verification

# Print effective config
go run ./cmd/fmrtx -print-config

# Run tests
go test ./...

# Basic dry-run summary
go run ./cmd/fmrtx --dry-run --dry-output build/dryrun/frame.json

For additional build/test commands, see:

  • docs/README.md

Common usage flows

1) List available SDR devices

.\fmrtx.exe --list-devices

2) Dry-run / config verification

.\fmrtx.exe --dry-run --dry-output build/dryrun/frame.json

# Write dry-run JSON to stdout
.\fmrtx.exe --dry-run --dry-output -

3) Offline IQ/composite generation

go run ./cmd/offline -duration 2s -output build/offline/composite.iqf32

# Optional output rate override
go run ./cmd/offline -duration 500ms -output build/offline/composite.iqf32 -output-rate 228000

4) Simulated transmit path

go run ./cmd/fmrtx --simulate-tx --simulate-output build/sim/simulated-soapy.iqf32 --simulate-duration 250ms

5) Real TX with config file

# Start TX service with manual start over HTTP
.\fmrtx.exe --tx --config docs/config.plutosdr.json

# Start and begin transmitting immediately
.\fmrtx.exe --tx --tx-auto-start --config docs/config.plutosdr.json

6) Live audio via stdin

ffmpeg -i "http://svabi.ch:8443/stream" -f s16le -ar 44100 -ac 2 - | .\fmrtx.exe --tx --tx-auto-start --audio-stdin --config docs/config.plutosdr.json

7) Custom audio input rate

ffmpeg -i source.wav -f s16le -ar 48000 -ac 2 - | .\fmrtx.exe --tx --tx-auto-start --audio-stdin --audio-rate 48000 --config docs/config.plutosdr.json

8) HTTP audio ingest

Start the control plane with --audio-http to accept raw PCM pushes on /audio/stream and feed them into the live encoder:

Set Content-Type to application/octet-stream (or audio/L16) when posting audio data:

ffmpeg -i music.mp3 -f s16le -ar 44100 -ac 2 - | curl -X POST -H "Content-Type: application/octet-stream" --data-binary @- http://localhost:8088/audio/stream

9) Icecast ingest via config

Use ingest.kind = "icecast" and set ingest.icecast.url in config.

Decoder semantics in Phase 1:

  • ingest.icecast.decoder = "auto": try native by content-type, fallback to ffmpeg on unsupported paths
  • ingest.icecast.decoder = "native": native only, no fallback
  • ingest.icecast.decoder = "ffmpeg" (or fallback): ffmpeg only

Current implementation note: native codec packages exist but are placeholders; practical decode today is ffmpeg fallback.

CLI overview

fmrtx

Important runtime modes and flags include:

  • --tx
  • --tx-auto-start
  • --dry-run
  • --dry-output <path|- >
  • --simulate-tx
  • --simulate-output <path>
  • --simulate-duration <duration>
  • --config <path>
  • --print-config
  • --list-devices
  • --audio-stdin
  • --audio-rate <hz>
  • --audio-http

offline

Useful flags include:

  • -duration <duration>
  • -output <path>
  • -output-rate <hz>

If the README is too high-level for the exact CLI surface, check:

  • cmd/fmrtx/main.go
  • cmd/offline/main.go

HTTP control plane

Base URL: http://{listenAddress} (default typically 127.0.0.1:8088)

Security note:

  • keep the control plane bound locally unless you intentionally place it behind a trusted and hardened access layer

Main endpoints

GET  /              browser UI
GET  /healthz       health check
GET  /status        current config/status snapshot
GET  /runtime       live engine / driver / audio telemetry
GET  /config        full config
POST /config        patch config / live updates
GET  /dry-run       synthetic frame summary
POST /tx/start      start transmission
POST /tx/stop       stop transmission
POST /audio/stream  push raw S16LE stereo PCM into live stream buffer (Content-Type: application/octet-stream or audio/L16 required)

What the control plane covers

  • TX start / stop
  • runtime status and driver telemetry
  • config inspection
  • live patching of selected parameters
  • dry-run inspection
  • browser-accessible control UI
  • optional HTTP audio ingest (enable with --audio-http)

Live config notes

POST /config supports live updates for selected fields such as:

  • frequency
  • stereo enable/disable
  • pilot / RDS injection levels
  • RDS enable/disable
  • limiter settings
  • PS / RadioText

Some parameters are saved but not live-applied and require restart.

For the full API contract, examples, live-patch semantics, and /audio/stream details, see:

  • docs/API.md

Configuration

Sample configs:

  • docs/config.sample.json
  • docs/config.plutosdr.json
  • docs/config.orangepi-pluto-soapy.json

Important config areas include:

  • fm.*
  • rds.*
  • audio.*
  • backend.*
  • control.*
  • ingest.*

Examples of relevant fields you may want to inspect:

  • fm.outputDrive
  • fm.mpxGain
  • fm.bs412Enabled
  • fm.bs412ThresholdDBr
  • fm.fmModulationEnabled
  • backend.kind
  • backend.driver
  • backend.deviceArgs
  • backend.uri
  • backend.deviceSampleRateHz
  • backend.outputPath
  • control.listenAddress

For deeper config/API behavior, refer to:

  • internal/config/config.go
  • docs/API.md
  • docs/config.sample.json

Development and testing

Useful commands:

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

See also:

  • docs/README.md

PlutoSDR / backend notes

  • PlutoSDR commonly runs with a device-side sample rate above composite rate, so split-rate mode may be used automatically
  • SoapySDR backend is suitable for Soapy-compatible TX hardware
  • backend/device settings are selected through config rather than hardcoded paths
  • runtime telemetry should be used to inspect effective TX state during operation

Repository layout

cmd/
  fmrtx/                     main CLI
  offline/                   offline generator
internal/
  app/                       TX engine + runtime state
  audio/                     audio input, resampling, tone generation, stream buffering
  config/                    config schema and validation
  control/                   HTTP control plane + browser UI
  dryrun/                    dry-run JSON summaries
  dsp/                       DSP primitives
  mpx/                       MPX combiner
  offline/                   full offline composite generation
  output/                    output/backend abstractions
  platform/                  backend abstractions and device/runtime stats
  platform/soapysdr/         CGO SoapySDR binding
  platform/plutosdr/         Pluto/libiio backend code
  rds/                       RDS encoder
  stereo/                    stereo encoder
docs/
  API.md
  DSP-CHAIN.md
  README.md
  config.sample.json
  config.plutosdr.json
  config.orangepi-pluto-soapy.json
  pro-runtime-hardening-workboard.md
scripts/
examples/

Planning / workboard

For the current runtime-hardening / professionalization track, see:

  • docs/pro-runtime-hardening-workboard.md

This is the living workboard for:

  • status tracking
  • confirmed findings
  • open technical decisions
  • verification notes
  • implementation progress

Release / project docs

Additional project docs:

  • CHANGELOG.md
  • RELEASE.md
  • docs/README.md
  • docs/API.md
  • docs/DSP-CHAIN.md
  • docs/NOTES.md

This project is intended only for lawful use within relevant license and regulatory constraints. RF output, deviation, filtering, and transmitted power must be validated with proper measurement equipment.