# fm-rds-tx — DSP Signal Chain & Konfiguration ## Übersicht fm-rds-tx ist ein broadcast-konformer FM-Stereo-MPX-Encoder mit RDS für PlutoSDR/SoapySDR. Die DSP-Kette folgt dem Industriestandard (Omnia, Orban, Stereo Tool) mit Clip-Filter-Clip- Architektur und ITU-R BS.412 MPX Power Limiting. --- ## Signalkette ``` ┌─────────────────────────────────────────────────────────────────┐ │ AUDIO INPUT │ │ S16LE Stereo PCM via stdin (ffmpeg) oder interner Tongenerator │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 1: Pre-Emphasis + Band-Limiting (pro Kanal L/R) │ │ │ │ Audio × gain ──→ Pre-Emphasis (50µs EU / 75µs US) │ │ ──→ 15kHz LPF (8th-order Chebyshev Type I, 0.5dB Ripple) │ │ ──→ 19kHz Notch (Q=15, double-cascade) │ │ │ │ Frequenzantwort (verifiziert): │ │ 10kHz: +0.1dB (flat) 15kHz: -0.2dB │ │ 17kHz: -21dB 18.5kHz: -40dB │ │ 19kHz: -155dB (tot) 22kHz: -51dB │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 2: Drive + Kompression + Clip₁ │ │ │ │ × OutputDrive │ │ ──→ StereoLimiter (5ms Attack / 200ms Release, ceiling) │ │ ──→ HardClip₁ (ceiling) │ │ │ │ Der Limiter komprimiert die Dynamik (bringt Average hoch). │ │ Der Clip fängt Peaks die der Limiter's Attack verpasst. │ │ "Slow-to-fast Progression" — Broadcast-Standard. │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 3: Cleanup LPF + Clip₂ (Overshoot-Kompensator) │ │ │ │ ──→ 15kHz LPF (8th-order Chebyshev, identisch zu Stage 1) │ │ ──→ HardClip₂ (ceiling) │ │ │ │ Der zweite LPF-Pass entfernt Clip₁-Harmonische. │ │ Clip₂ fängt die LPF-Overshoots (IIR-Filter erzeugen diese). │ │ Doppelter LPF-Pass verdoppelt die Guard-Band-Dämpfung. │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 4: Stereo-Encode │ │ │ │ L/R ──→ Mono: (L+R)/2 (0–15kHz Baseband) │ │ ──→ Stereo: (L-R)/2 × sin(38kHz) (23–53kHz DSB-SC) │ │ ──→ Pilot: sin(19kHz) (phase-locked, kohärent) │ │ ──→ RDS Carrier: sin(57kHz) (3× Pilot-Phase, kohärent) │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 5: Composite Clip + Schutzfilter │ │ │ │ Audio-MPX (Mono + Stereo-Sub) │ │ ──→ HardClip₃ (ceiling) — finale Deviations-Kontrolle │ │ ──→ 19kHz Notch (Q=10, double) — Clip-Harmonische bei Pilot │ │ ──→ 57kHz Notch (Q=10, double) — Clip-Harmonische bei RDS │ │ │ │ Guard Bands (total, 2× LPF + Notches): │ │ 19kHz: >-80dB broadband, >-90dB exakt │ │ 57kHz: >-100dB │ │ (Omnia 11 Spezifikation: >80dB — wir sind on par) │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 6: BS.412 MPX Power Limiter (optional) │ │ │ │ ──→ × BS412 Gain │ │ │ │ Rolling 60-Sekunden RMS-Messung auf dem Audio-Composite. │ │ Langsamer Gain-Regler (2s Attack / 5s Release). │ │ Zieht Pilot+RDS-Power automatisch vom Budget ab. │ │ Pflicht in CH, DE, NL, FR für lizenzierte FM-Sender. │ │ ~5dB Lautheitsverlust bei 0 dBr Threshold. │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 7: Pilot + RDS Injection (fixe Amplitude) │ │ │ │ composite = audioMPX │ │ + pilotLevel × sin(19kHz) — IMMER 9% │ │ + rdsInjection × rdsWaveform — IMMER 4% │ │ │ │ Pilot und RDS werden NIE geclippt, NIE gefiltert, NIE vom │ │ BS.412-Limiter berührt. Konstante Amplitude, immer. │ │ │ │ Peak Composite = ceiling + pilotLevel + rdsInjection ≈ 113% │ │ (Standard-Broadcast-Praxis — Pilot/RDS werden von den meisten │ │ Regulierungsbehörden aus dem Modulationslimit ausgenommen) │ └──────────────────────────┬──────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────┐ │ STAGE 8: FM-Modulation │ │ │ │ Split-Rate: Composite @ 228kHz ──→ FMUpsampler ──→ IQ @ 2.28MHz│ │ maxDeviation × mpxGain = effektive Deviation │ │ composite=1.0 → ±75kHz Deviation (bei mpxGain=1.0) │ │ │ │ Ausgabe: IQ-Samples (float32) an PlutoSDR via libiio │ └──────────────────────────────────────────────────────────────────┘ ``` --- ## Konfiguration ### Audio | Parameter | Typ | Default | Beschreibung | |---|---|---|---| | `audio.gain` | float | 1.0 | Eingangsverstärkung vor Pre-Emphasis. 1.0 = unity. | | `audio.inputPath` | string | "" | WAV-Datei als Quelle (leer = stdin oder Tongenerator) | **Empfehlung:** `gain: 1.0`. Pegel-Kontrolle über `outputDrive`. ### FM — Audio-Processing | Parameter | Typ | Default | Bereich | Beschreibung | |---|---|---|---|---| | `outputDrive` | float | 0.5 | 0–10 | Eingangsverstärkung vor Limiter/Clip. Bestimmt wie aggressiv die Kompression arbeitet. | | `limiterEnabled` | bool | true | — | Aktiviert den StereoLimiter (5ms/200ms). | | `limiterCeiling` | float | 1.0 | 0–2 | Maximum-Amplitude für Audio L/R und Composite. 1.0 = ±75kHz. | | `preEmphasisTauUS` | float | 50 | 0/50/75 | Pre-Emphasis Zeitkonstante. 50µs = Europa/CH, 75µs = USA, 0 = aus. | **outputDrive im Detail:** Der Drive bestimmt den *Klangcharakter*, nicht die Lautstärke (wenn BS.412 aktiv ist): | Drive | Effekt | Einsatz | |---|---|---| | 1–2 | Wenig Kompression, dynamisch, sauber | Klassik, Jazz, Wortbeiträge | | 3–4 | Moderate Kompression, ausgewogen | **Empfohlen für die meisten Formate** | | 5–7 | Aggressive Kompression, dichter Sound | Pop/Rock-Formatradio | | 8–10 | Maximale Dichte, hörbare Clip-Artefakte | Nicht empfohlen | **Empfehlung:** `outputDrive: 3.0` für sauberen, broadcast-fähigen Sound. ### FM — Pilot & RDS | Parameter | Typ | Default | Bereich | Beschreibung | |---|---|---|---|---| | `pilotLevel` | float | 0.09 | 0–0.2 | 19kHz Pilot-Amplitude. **Direkte Prozentangabe von ±75kHz.** 0.09 = 9% = ITU-Standard. | | `rdsInjection` | float | 0.04 | 0–0.15 | RDS-Amplitude. **Direkte Prozentangabe.** 0.04 = 4%. Waveform ist unity-normalisiert. | | `stereoEnabled` | bool | true | — | Stereo-Encode an/aus. Aus = nur Mono (L+R)/2, kein Pilot. | **Empfehlung:** `pilotLevel: 0.09`, `rdsInjection: 0.04`. Nicht ändern ausser es gibt einen guten Grund. Pilot und RDS sind **unabhängig vom OutputDrive** — sie bleiben immer bei der konfigurierten Amplitude, egal wie hart das Audio komprimiert wird. ### FM — BS.412 (ITU-R MPX Power Limiter) | Parameter | Typ | Default | Beschreibung | |---|---|---|---| | `bs412Enabled` | bool | false | Aktiviert den BS.412 MPX Power Limiter. **Pflicht in CH, DE, NL, FR.** | | `bs412ThresholdDBr` | float | 0 | Power-Limit in dBr. 0 = Standard. +3 = relaxiert. | **Was BS.412 macht:** Begrenzt die durchschnittliche MPX-Leistung über ein rollendes 60-Sekunden-Fenster. Reduziert die Audio-Amplitude langsam wenn die Power den Threshold überschreitet. Pilot und RDS werden automatisch vom Power-Budget abgezogen. **Effekt auf den Sound:** - ~5dB Lautheitsverlust bei 0 dBr mit aggressiver Kompression - Weniger Verlust bei dynamischerem Material - OutputDrive beeinflusst bei aktivem BS.412 nur den Klangcharakter, nicht die Lautheit **Empfehlung:** `bs412Enabled: true`, `bs412ThresholdDBr: 0` für BAKOM-Compliance. ### FM — Hardware-Kalibrierung | Parameter | Typ | Default | Bereich | Beschreibung | |---|---|---|---|---| | `mpxGain` | float | 1.0 | 0.1–5 | Skaliert die FM-Deviation (nicht den Composite!). Kompensiert DAC/SDR-Hardware-Faktoren. | | `maxDeviationHz` | float | 75000 | 0–150000 | Maximale FM-Deviation in Hz. 75000 = Standard. | | `compositeRateHz` | int | 228000 | — | Interne DSP-Sample-Rate. 228000 = 12×19kHz (optimal für Pilot-Kohärenz). | **MpxTool-Kalibrierung:** 1. `mpxGain: 1.0` setzen (keine Skalierung) 2. MpxTool Ref Level so einstellen dass **Pilot Level = 9.0%** anzeigt 3. Für PlutoSDR typisch: Ref Level ca. **-7.5 dBFS** 4. Einmal kalibrieren, nie wieder anfassen **Empfehlung:** `mpxGain: 1.0`, `maxDeviationHz: 75000`. Kalibrierung über MpxTool Ref Level. ### RDS | Parameter | Typ | Default | Beschreibung | |---|---|---|---| | `rds.enabled` | bool | true | RDS an/aus | | `rds.pi` | string | "1234" | Programme Identification (4-stellig hex). Muss mit BAKOM-Zuteilung übereinstimmen. | | `rds.ps` | string | "FMRTX" | Programme Service Name (max 8 Zeichen). Stationsname auf dem Display. | | `rds.radioText` | string | "" | Radio Text (max 64 Zeichen). Scrolltext auf dem Display. | | `rds.pty` | int | 0 | Programme Type. 0=undefined, 1=News, 3=Info, 10=Pop, 15=Other Music, etc. | ### Backend | Parameter | Typ | Default | Beschreibung | |---|---|---|---| | `backend.kind` | string | "file" | `"pluto"` für PlutoSDR, `"soapy"` für SoapySDR, `"file"` für Dateiausgabe | | `backend.device` | string | "" | Device-String. PlutoSDR: `"usb:"` oder `"ip:192.168.2.1"` | | `backend.deviceSampleRateHz` | float | 0 | SDR-Device-Rate. 2280000 = 10× compositeRate (optimal). | --- ## Referenz-Konfiguration (BAKOM-konform, PlutoSDR) ```json { "audio": { "gain": 1.0 }, "rds": { "enabled": true, "pi": "BEEF", "ps": "RADIO-ZH", "radioText": "Ihr Zürcher Kurzradio", "pty": 0 }, "fm": { "frequencyMHz": 100.0, "stereoEnabled": true, "pilotLevel": 0.09, "rdsInjection": 0.04, "preEmphasisTauUS": 50, "outputDrive": 3.0, "limiterEnabled": true, "limiterCeiling": 1.0, "bs412Enabled": true, "bs412ThresholdDBr": 0, "mpxGain": 1.0, "compositeRateHz": 228000, "maxDeviationHz": 75000, "fmModulationEnabled": true }, "backend": { "kind": "pluto", "device": "usb:", "deviceSampleRateHz": 2280000 }, "control": { "listenAddress": "127.0.0.1:8088" } } ``` --- ## Audio-Streaming (Produktionsbetrieb) ```bash ffmpeg -i http://stream-url/stream -f s16le -ar 44100 -ac 2 - | fmrtx.exe --tx --tx-auto-start --audio-stdin --audio-rate 44100 --config config.json ``` **Hinweis:** Unter Windows `cmd.exe` verwenden, nicht PowerShell (korrumpiert die Binary-Pipe). --- ## Verifizierte Messwerte (MpxTool, PlutoSDR @ 100MHz) | Parameter | Messung | Soll | |---|---|---| | Pilot Level | 9.0% | 9% ✓ | | RDS Injection | 3.4% | 4% (≈, BPSK-Mittelung) | | MPX Peak | 105–110% | 100–113% ✓ | | Guard Band 19kHz | >-80dB | >-80dB (Omnia 11: >80dB) ✓ | | Audio Bandwidth | flat bis 15kHz | 15kHz ✓ |