Explorar el Código

rds: support explicit text clearing and symbol bootstrap

main
Jan hace 1 mes
padre
commit
b0964e71dc
Se han modificado 1 ficheros con 53 adiciones y 16 borrados
  1. +53
    -16
      internal/rds/encoder.go

+ 53
- 16
internal/rds/encoder.go Ver fichero

@@ -94,8 +94,17 @@ type Encoder struct {

// Live-updatable text — written by control API, read at group boundaries.
// Zero-contention: atomic swap, checked once per RDS group (~88ms at 228kHz).
livePS atomic.Value // string
liveRT atomic.Value // string
// pendingText.set distinguishes "no pending update" from "update to empty string"
// so that PS/RT can be explicitly cleared via UpdateText.
livePS atomic.Value // pendingText
liveRT atomic.Value // pendingText
}

// pendingText carries a pending text update for PS or RT.
// set=false means no update is pending; set=true means apply val (even if empty).
type pendingText struct {
val string
set bool
}

func NewEncoder(cfg RDSConfig) (*Encoder, error) {
@@ -163,16 +172,29 @@ func (e *Encoder) Reset() {

// UpdateText hot-swaps PS and/or RT. Thread-safe — called from HTTP handlers,
// applied at the next RDS group boundary by the DSP goroutine.
// Pass empty string to leave a field unchanged.
//
// Pass empty string to leave a field unchanged. To explicitly clear a field
// (set PS to 8 spaces, or RT to empty), use ClearPS/ClearRT instead.
func (e *Encoder) UpdateText(ps, rt string) {
if ps != "" {
e.livePS.Store(normalizePS(ps))
e.livePS.Store(pendingText{val: normalizePS(ps), set: true})
}
if rt != "" {
e.liveRT.Store(normalizeRT(rt))
e.liveRT.Store(pendingText{val: normalizeRT(rt), set: true})
}
}

// ClearPS resets the Program Service name to 8 spaces at the next group boundary.
func (e *Encoder) ClearPS() {
e.livePS.Store(pendingText{val: normalizePS(""), set: true})
}

// ClearRT resets RadioText to an empty string at the next group boundary.
// Per RDS spec, an empty RT causes receivers to clear their display.
func (e *Encoder) ClearRT() {
e.liveRT.Store(pendingText{val: "", set: true})
}

// NextSample returns the next RDS subcarrier sample at the configured rate.
// Uses the internal free-running 57 kHz carrier. Prefer NextSampleWithCarrier
// for phase-locked operation in a stereo MPX chain.
@@ -192,15 +214,15 @@ func (e *Encoder) NextSampleWithCarrier(carrier float64) float64 {
// Apply live text updates at group boundaries (~88ms at 228kHz).
// Atomics are consumed (cleared) after reading to prevent
// re-applying the same text every group and toggling A/B flag.
if ps, ok := e.livePS.Load().(string); ok && ps != "" {
e.scheduler.cfg.PS = ps
e.livePS.Store("") // consumed
if pt, ok := e.livePS.Load().(pendingText); ok && pt.set {
e.scheduler.cfg.PS = pt.val
e.livePS.Store(pendingText{}) // consumed
}
if rt, ok := e.liveRT.Load().(string); ok && rt != "" {
e.scheduler.cfg.RT = rt
if pt, ok := e.liveRT.Load().(pendingText); ok && pt.set {
e.scheduler.cfg.RT = pt.val
e.scheduler.rtIdx = 0 // restart RT transmission for new text
e.scheduler.rtABFlag = !e.scheduler.rtABFlag // toggle A/B per RDS spec
e.liveRT.Store("") // consumed
e.liveRT.Store(pendingText{}) // consumed
}
e.getRDSGroup()
e.bitPos = 0
@@ -240,12 +262,27 @@ func (e *Encoder) Generate(n int) []float64 {
out := make([]float64, n); for i := range out { out[i] = e.NextSample() }; return out
}
func (e *Encoder) Symbol() float64 {
if e.bitPos >= bitsPerGroup { return -1 }
sym := 1.0; if e.bitBuffer[e.bitPos] == 0 { sym = -1.0 }
// Populate the bit buffer on first call (bitPos starts at bitsPerGroup
// after NewEncoder/Reset, so the guard below would return -1 immediately
// without this bootstrap step).
if e.bitPos >= bitsPerGroup {
e.getRDSGroup()
e.bitPos = 0
}
sym := 1.0
if e.bitBuffer[e.bitPos] == 0 {
sym = -1.0
}
e.sampleCount++
if e.sampleCount >= e.spb { e.sampleCount = 0; e.bitPos++
if e.bitPos >= bitsPerGroup { e.getRDSGroup(); e.bitPos = 0 }
}; return sym
if e.sampleCount >= e.spb {
e.sampleCount = 0
e.bitPos++
if e.bitPos >= bitsPerGroup {
e.getRDSGroup()
e.bitPos = 0
}
}
return sym
}

func (e *Encoder) getRDSGroup() {


Cargando…
Cancelar
Guardar