Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

5.5KB

fm-rds-tx HTTP Control API

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


Endpoints

GET /healthz

Health check.

Response:

{"ok": true}

GET /status

Current transmitter status (read-only snapshot).

Response:

{
  "service": "fm-rds-tx",
  "backend": "pluto",
  "frequencyMHz": 100.0,
  "stereoEnabled": true,
  "rdsEnabled": true,
  "preEmphasisTauUS": 50,
  "limiterEnabled": true,
  "fmModulationEnabled": true
}

GET /runtime

Live engine and driver telemetry. Only populated when TX is active.

Response:

{
  "engine": {
    "state": "running",
    "chunksProduced": 12345,
    "totalSamples": 1408950000,
    "underruns": 0,
    "lastError": "",
    "uptimeSeconds": 3614.2
  },
  "driver": {
    "txEnabled": true,
    "streamActive": true,
    "framesWritten": 12345,
    "samplesWritten": 1408950000,
    "underruns": 0,
    "effectiveSampleRateHz": 2280000
  }
}

GET /config

Full current configuration (all fields, including non-patchable).

Response: Complete Config JSON object.


POST /config

Live parameter update. Changes are applied to the running TX engine immediately — no restart required. Only include fields you want to change (PATCH semantics).

Request body: JSON with any subset of patchable fields.

Response:

{"ok": true, "live": true}

"live": true = changes were forwarded to the running engine.
"live": false = engine not active, changes saved for next start.

Patchable fields — DSP (applied within ~50ms)

Field Type Range Description
frequencyMHz float 65–110 TX center frequency. Tunes hardware LO live.
outputDrive float 0–3 Composite output level multiplier.
stereoEnabled bool Enable/disable stereo (pilot + 38kHz subcarrier).
pilotLevel float 0–0.2 19 kHz pilot injection level.
rdsInjection float 0–0.15 57 kHz RDS subcarrier injection level.
rdsEnabled bool Enable/disable RDS subcarrier.
limiterEnabled bool Enable/disable MPX peak limiter.
limiterCeiling float 0–2 Limiter ceiling (max composite amplitude).

Patchable fields — RDS text (applied within ~88ms)

Field Type Max length Description
ps string 8 chars Program Service name (station name on receiver display).
radioText string 64 chars RadioText message (scrolling text on receiver).

When radioText is updated, the RDS A/B flag toggles automatically per spec, signaling receivers to refresh their display.

Patchable fields — other (saved, not live-applied)

Field Type Description
toneLeftHz float Left tone frequency (test generator).
toneRightHz float Right tone frequency (test generator).
toneAmplitude float Test tone amplitude (0–1).
preEmphasisTauUS float Pre-emphasis time constant. Requires restart.

Examples

# Tune to 99.5 MHz
curl -X POST localhost:8088/config -d '{"frequencyMHz": 99.5}'

# Switch to mono
curl -X POST localhost:8088/config -d '{"stereoEnabled": false}'

# Update now-playing text
curl -X POST localhost:8088/config \
  -d '{"ps": "MYRADIO", "radioText": "Artist - Song Title"}'

# Reduce power + disable limiter
curl -X POST localhost:8088/config \
  -d '{"outputDrive": 0.8, "limiterEnabled": false}'

# Full update
curl -X POST localhost:8088/config -d '{
  "frequencyMHz": 101.3,
  "outputDrive": 2.2,
  "stereoEnabled": true,
  "pilotLevel": 0.041,
  "rdsInjection": 0.021,
  "rdsEnabled": true,
  "limiterEnabled": true,
  "limiterCeiling": 1.0,
  "ps": "PIRATE",
  "radioText": "Broadcasting from the attic"
}'

Error handling

Invalid values return 400 Bad Request with a descriptive message:

curl -X POST localhost:8088/config -d '{"frequencyMHz": 200}'
# → 400: frequencyMHz out of range (65-110)

POST /tx/start

Start transmission. Requires --tx mode with hardware.

Response:

{"ok": true, "action": "started"}

Errors:

  • 405 if not POST
  • 503 if no TX controller (not in --tx mode)
  • 409 if already running

POST /tx/stop

Stop transmission.

Response:

{"ok": true, "action": "stopped"}

GET /dry-run

Generate a synthetic frame summary without hardware. Useful for config verification.

Response: FrameSummary JSON with mode, rates, source info, preview samples.


Live update architecture

All live updates are lock-free in the DSP path:

What Mechanism Latency
DSP params atomic.Pointer[LiveParams] loaded once per chunk ≤ 50ms
RDS text atomic.Value in encoder, read at group boundary ≤ 88ms
TX frequency atomic.Pointer in engine, driver.Tune() between chunks ≤ 50ms

No mutex, no channel, no allocation in the real-time path. The HTTP goroutine writes atomics, the DSP goroutine reads them.

Parameters that require restart

These cannot be hot-reloaded (they affect DSP pipeline structure):

  • compositeRateHz — changes sample rate of entire DSP chain
  • deviceSampleRateHz — changes hardware rate / upsampler ratio
  • maxDeviationHz — changes FM modulator scaling
  • preEmphasisTauUS — changes filter coefficients
  • rds.pi / rds.pty — rarely change, baked into encoder init
  • audio.inputPath — audio source selection
  • backend.kind / backend.device — hardware selection