| @@ -1,101 +1,290 @@ | |||
| # fm-rds-tx | |||
| Go-based FM stereo transmitter with RDS. Supports ADALM-Pluto (PlutoSDR) and any SoapySDR-compatible TX device. | |||
| Go-based FM stereo transmitter with RDS. Supports ADALM-Pluto (PlutoSDR) and SoapySDR-compatible TX devices. | |||
| ## Status: v0.7.0-pre — hardware bring-up milestone | |||
| ## Status | |||
| ### What works | |||
| - Complete DSP chain: pre-emphasis → stereo encoding → RDS (IEC 62106) → MPX → limiter → FM modulation | |||
| - Real hardware TX via SoapySDR CGO binding (PlutoSDR tested) | |||
| - Continuous TX engine with Start/Stop/Stats | |||
| - IQ resampling (composite rate → device rate) | |||
| - HTTP control plane with /tx/start, /tx/stop, /runtime | |||
| - 82 passing tests including spectral verification | |||
| **Current status:** `v0.7.0-pre` — hardware bring-up milestone | |||
| ### Signal path | |||
| ``` | |||
| Audio Source → PreEmphasis(50µs) → StereoEncoder(19k+38k) → RDS(57k) | |||
| → MPX Combiner → Limiter → FM Modulator(±75kHz) | |||
| → IQ Resample(228k→528k) → SoapySDR → PlutoSDR RF | |||
| 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 `/` | |||
| - live audio ingestion via stdin or HTTP stream input | |||
| ## 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 | |||
| # Without hardware (simulation/offline only): | |||
| # Build CLI tools without hardware-specific build tags: | |||
| go build ./cmd/fmrtx | |||
| go build ./cmd/offline | |||
| # With SoapySDR hardware support (requires PothosSDR installed): | |||
| # Build fmrtx with SoapySDR support: | |||
| go build -tags soapy ./cmd/fmrtx | |||
| ``` | |||
| ## Usage | |||
| ## 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 | |||
| ### List available SDR devices | |||
| ```powershell | |||
| .\fmrtx.exe --list-devices | |||
| ``` | |||
| ### Offline IQ file generation | |||
| ### 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 | |||
| ``` | |||
| ### Real TX (PlutoSDR) | |||
| ### 5) Real TX with config file | |||
| ```powershell | |||
| # Start with manual TX control via HTTP: | |||
| # Start TX service with manual start over HTTP | |||
| .\fmrtx.exe --tx --config docs/config.plutosdr.json | |||
| # Start with auto-TX on launch: | |||
| # Start and begin transmitting immediately | |||
| .\fmrtx.exe --tx --tx-auto-start --config docs/config.plutosdr.json | |||
| ``` | |||
| ### HTTP control | |||
| ### 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 | |||
| ``` | |||
| ## 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>` | |||
| ## `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`) | |||
| ### 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 | |||
| ``` | |||
| POST http://localhost:8088/tx/start → start transmission | |||
| POST http://localhost:8088/tx/stop → stop transmission | |||
| GET http://localhost:8088/runtime → engine + driver telemetry | |||
| GET http://localhost:8088/status → config status | |||
| GET http://localhost:8088/config → full config | |||
| POST http://localhost:8088/config → patch config (freq, RDS, etc.) | |||
| GET http://localhost:8088/dry-run → dry-run summary | |||
| GET http://localhost:8088/healthz → health check | |||
| ### 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 | |||
| ### 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.*` | |||
| 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 | |||
| ``` | |||
| ## PlutoSDR notes | |||
| See also: | |||
| - `docs/README.md` | |||
| - Device rate: 528 kHz (PlutoSDR minimum ~521 kHz) | |||
| - IQ format: CF32 (float32 interleaved I/Q) | |||
| - Gain range: 0–89 dB (`outputDrive` 0..1 maps to 0..89 dB) | |||
| - SoapySDR driver name: `plutosdr` | |||
| - Requires: PothosSDR or SoapySDR + SoapyPlutoSDR plugin installed | |||
| ## 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 (--tx, --dry-run, --simulate-tx, --list-devices) | |||
| offline/ offline IQ file generator | |||
| fmrtx/ main CLI | |||
| offline/ offline generator | |||
| internal/ | |||
| app/ TX engine (continuous chunk loop) + simulated transmit | |||
| audio/ sample types, WAV loader, resampler, tone generator | |||
| config/ config schema, validation, PI parsing | |||
| control/ HTTP control plane (/tx/start, /tx/stop, /runtime) | |||
| dryrun/ JSON dry-run summaries | |||
| dsp/ oscillator, pre-emphasis, FM modulator, limiter, Goertzel, IQ resampler | |||
| mpx/ MPX combiner | |||
| offline/ offline composite generation (full DSP chain) | |||
| output/ backend abstractions (file, dummy) | |||
| platform/ SoapyDriver interface, SoapyBackend, SimulatedDriver | |||
| platform/soapysdr/ CGO SoapySDR native binding (build tag: soapy) | |||
| rds/ RDS encoder (IEC 62106, CRC, differential, group scheduler) | |||
| stereo/ stereo encoder (19 kHz pilot, 38 kHz DSB-SC) | |||
| 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/ | |||
| config.sample.json default config | |||
| config.plutosdr.json PlutoSDR-specific config | |||
| pro-runtime-hardening-workboard.md living workboard for pro runtime hardening | |||
| 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/ | |||
| ``` | |||
| @@ -103,10 +292,23 @@ examples/ | |||
| ## Planning / workboard | |||
| For the current pro-runtime-hardening track, see: | |||
| - `docs/pro-runtime-hardening-workboard.md` | |||
| This document is the detailed working board for status tracking, confirmed findings, open decisions, verification notes, and implementation progress. | |||
| 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` | |||
| ## Legal note | |||