| @@ -1,101 +1,290 @@ | |||||
| # fm-rds-tx | # 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 | ## Build | ||||
| ```powershell | ```powershell | ||||
| # Without hardware (simulation/offline only): | |||||
| # Build CLI tools without hardware-specific build tags: | |||||
| go build ./cmd/fmrtx | go build ./cmd/fmrtx | ||||
| go build ./cmd/offline | go build ./cmd/offline | ||||
| # With SoapySDR hardware support (requires PothosSDR installed): | |||||
| # Build fmrtx with SoapySDR support: | |||||
| go build -tags soapy ./cmd/fmrtx | 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 | ```powershell | ||||
| .\fmrtx.exe --list-devices | .\fmrtx.exe --list-devices | ||||
| ``` | ``` | ||||
| ### Offline IQ file generation | |||||
| ### 2) Dry-run / config verification | |||||
| ```powershell | ```powershell | ||||
| .\fmrtx.exe --dry-run --dry-output build/dryrun/frame.json | .\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 | 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 | ```powershell | ||||
| # Start with manual TX control via HTTP: | |||||
| # Start TX service with manual start over HTTP | |||||
| .\fmrtx.exe --tx --config docs/config.plutosdr.json | .\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 | .\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 | ## Repository layout | ||||
| ```text | ```text | ||||
| cmd/ | cmd/ | ||||
| fmrtx/ main CLI (--tx, --dry-run, --simulate-tx, --list-devices) | |||||
| offline/ offline IQ file generator | |||||
| fmrtx/ main CLI | |||||
| offline/ offline generator | |||||
| internal/ | 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/ | 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/ | scripts/ | ||||
| examples/ | examples/ | ||||
| ``` | ``` | ||||
| @@ -103,10 +292,23 @@ examples/ | |||||
| ## Planning / workboard | ## Planning / workboard | ||||
| For the current pro-runtime-hardening track, see: | For the current pro-runtime-hardening track, see: | ||||
| - `docs/pro-runtime-hardening-workboard.md` | - `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 | ## Legal note | ||||