# 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 ```text 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` ### Hardware / legal - validate RF output, deviation, filtering, and power with proper measurement equipment - use only within applicable legal and regulatory constraints ## Quick start ## Build ```powershell # 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 ```powershell # 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 ```powershell .\fmrtx.exe --list-devices ``` ### 2) Dry-run / config verification ```powershell .\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 ```powershell 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 ```powershell go run ./cmd/fmrtx --simulate-tx --simulate-output build/sim/simulated-soapy.iqf32 --simulate-duration 250ms ``` ### 5) Real TX with config file ```powershell # 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 ```powershell 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 ```powershell 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: ```powershell 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 ` - `--simulate-tx` - `--simulate-output ` - `--simulate-duration ` - `--config ` - `--print-config` - `--list-devices` - `--audio-stdin` - `--audio-rate ` - `--audio-http` ## `offline` Useful flags include: - `-duration ` - `-output ` - `-output-rate ` 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 ```text 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: ```powershell 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 ```text 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` ## Legal note 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.