|
- package rds
-
- import "math"
-
- // Decoder performs a simple RDS baseband decode (BPSK, 1187.5 bps).
- type Decoder struct {
- ps [8]rune
- rt [64]rune
- lastPI uint16
- }
-
- type Result struct {
- PI uint16 `json:"pi"`
- PS string `json:"ps"`
- RT string `json:"rt"`
- }
-
- // Decode takes baseband samples at ~2400 Hz and attempts to extract PI/PS/RT.
- // NOTE: lightweight decoder with CRC+block sync; not a full RDS implementation.
- func (d *Decoder) Decode(base []float32, sampleRate int) Result {
- if len(base) == 0 || sampleRate <= 0 {
- return Result{}
- }
- // crude clock: 1187.5 bps
- baud := 1187.5
- spb := float64(sampleRate) / baud
- bits := make([]int, 0, int(float64(len(base))/spb))
- phase := 0.0
- for i := 0; i < len(base); i++ {
- phase += 1.0
- if phase >= spb {
- phase -= spb
- if base[i] >= 0 {
- bits = append(bits, 1)
- } else {
- bits = append(bits, 0)
- }
- }
- }
- if len(bits) < 26*4 {
- return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
- }
- // search for block sync
- for i := 0; i+26*4 <= len(bits); i++ {
- bA, okA := decodeBlock(bits[i : i+26])
- bB, okB := decodeBlock(bits[i+26 : i+52])
- bC, okC := decodeBlock(bits[i+52 : i+78])
- bD, okD := decodeBlock(bits[i+78 : i+104])
- if !(okA && okB && okC && okD) {
- continue
- }
- if bA.offset != offA || bB.offset != offB || bC.offset != offC || bD.offset != offD {
- continue
- }
- pi := bA.data
- if pi != 0 {
- d.lastPI = pi
- }
- groupType := (bB.data >> 12) & 0xF
- versionA := ((bB.data >> 11) & 0x1) == 0
- if groupType == 0 && versionA {
- addr := bB.data & 0x3
- chars := []byte{byte(bD.data >> 8), byte(bD.data & 0xFF)}
- idx := int(addr) * 2
- if idx+1 < len(d.ps) {
- d.ps[idx] = sanitizeRune(chars[0])
- d.ps[idx+1] = sanitizeRune(chars[1])
- }
- }
- if groupType == 2 && versionA {
- addr := bB.data & 0xF
- chars := []byte{byte(bC.data >> 8), byte(bC.data & 0xFF), byte(bD.data >> 8), byte(bD.data & 0xFF)}
- idx := int(addr) * 4
- for j := 0; j < 4 && idx+j < len(d.rt); j++ {
- d.rt[idx+j] = sanitizeRune(chars[j])
- }
- }
- break
- }
- return Result{PI: d.lastPI, PS: d.psString(), RT: d.rtString()}
- }
-
- type block struct {
- data uint16
- offset uint16
- }
-
- const (
- offA uint16 = 0x0FC
- offB uint16 = 0x198
- offC uint16 = 0x168
- offD uint16 = 0x1B4
- )
-
- func decodeBlock(bits []int) (block, bool) {
- if len(bits) != 26 {
- return block{}, false
- }
- var raw uint32
- for _, b := range bits {
- raw = (raw << 1) | uint32(b&1)
- }
- data := uint16(raw >> 10)
- synd := crcSyndrome(raw)
- switch synd {
- case offA, offB, offC, offD:
- return block{data: data, offset: uint16(synd)}, true
- default:
- return block{}, false
- }
- }
-
- func crcSyndrome(raw uint32) uint16 {
- // polynomial 0x1B9 (10-bit)
- var reg uint32 = raw
- poly := uint32(0x1B9)
- for i := 25; i >= 10; i-- {
- if (reg>>uint(i))&1 == 1 {
- reg ^= poly << uint(i-10)
- }
- }
- return uint16(reg & 0x3FF)
- }
-
- func sanitizeRune(b byte) rune {
- if b < 32 || b > 126 {
- return ' '
- }
- return rune(b)
- }
-
- func (d *Decoder) psString() string {
- out := make([]rune, 0, len(d.ps))
- for _, r := range d.ps {
- if r == 0 {
- r = ' '
- }
- out = append(out, r)
- }
- return trimRight(out)
- }
-
- func (d *Decoder) rtString() string {
- out := make([]rune, 0, len(d.rt))
- for _, r := range d.rt {
- if r == 0 {
- r = ' '
- }
- out = append(out, r)
- }
- return trimRight(out)
- }
-
- func trimRight(in []rune) string {
- end := len(in)
- for end > 0 && in[end-1] == ' ' {
- end--
- }
- return string(in[:end])
- }
-
- // BPSKCostas returns a simple carrier-locked version of baseband (placeholder).
- func BPSKCostas(in []float32) []float32 {
- out := make([]float32, len(in))
- var phase float64
- for i, v := range in {
- phase += 0.0001 * float64(v) * math.Sin(phase)
- out[i] = float32(float64(v) * math.Cos(phase))
- }
- return out
- }
|