Explorar el Código

Add basic RDS decoding placeholders

master
Jan Svabenik hace 3 días
padre
commit
d86d53b44f
Se han modificado 3 ficheros con 114 adiciones y 0 borrados
  1. +100
    -0
      internal/rds/rds.go
  2. +9
    -0
      internal/recorder/demod.go
  3. +5
    -0
      internal/recorder/rds.go

+ 100
- 0
internal/rds/rds.go Ver fichero

@@ -0,0 +1,100 @@
package rds

import (
"math"
)

// Decoder performs a simple RDS baseband decode (BPSK, 1187.5 bps).
type Decoder struct {
lastPS string
lastPI uint16
}

type Result struct {
PI uint16 `json:"pi"`
PS string `json:"ps"`
}

// Decode takes baseband samples at ~2400 Hz and attempts to extract PI/PS.
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
// carrier recovery simplified: assume baseband already mixed
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
// slice decision
v := base[i]
if v >= 0 {
bits = append(bits, 1)
} else {
bits = append(bits, 0)
}
}
}
// parse groups (very naive): look for 16-bit blocks and decode group type 0A for PS
// This is a placeholder: real RDS needs CRC and block sync.
if len(bits) < 104 {
return Result{PI: d.lastPI, PS: d.lastPS}
}
// best effort: just map first 16 bits to PI and next 8 chars from consecutive bytes
pi := bitsToU16(bits[0:16])
ps := decodePS(bits)
if pi != 0 {
d.lastPI = pi
}
if ps != "" {
d.lastPS = ps
}
return Result{PI: d.lastPI, PS: d.lastPS}
}

func bitsToU16(bits []int) uint16 {
var v uint16
for _, b := range bits {
v = (v << 1) | uint16(b&1)
}
return v
}

func decodePS(bits []int) string {
// naive: take next 64 bits as 8 ASCII chars
if len(bits) < 16+64 {
return ""
}
start := 16
out := make([]rune, 0, 8)
for i := 0; i < 8; i++ {
var c byte
for j := 0; j < 8; j++ {
c = (c << 1) | byte(bits[start+i*8+j]&1)
}
if c < 32 || c > 126 {
c = ' '
}
out = append(out, rune(c))
}
// trim
for len(out) > 0 && out[len(out)-1] == ' ' {
out = out[:len(out)-1]
}
return string(out)
}

// 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
}

+ 9
- 0
internal/recorder/demod.go Ver fichero

@@ -52,6 +52,15 @@ func (m *Manager) demodAndWrite(dir string, ev detector.Event, iq []complex64, f
_ = writeWAV(rdsPath, rds, 2400, 1)
files["rds_baseband"] = "rds.wav"
files["rds_sample_rate"] = 2400
// naive decode
dec := rdsdecoder{}
res := dec.Decode(rds, 2400)
if res.PI != 0 {
files["rds_pi"] = res.PI
}
if res.PS != "" {
files["rds_ps"] = res.PS
}
}
}
return nil


+ 5
- 0
internal/recorder/rds.go Ver fichero

@@ -0,0 +1,5 @@
package recorder

import "sdr-visual-suite/internal/rds"

type rdsdecoder struct{ rds.Decoder }

Cargando…
Cancelar
Guardar