|
- package rds
-
- // RDS encoder following the PiFmRds reference implementation.
- // Generates shaped biphase BPSK at 228 kHz (4× 57 kHz).
- //
- // Architecture:
- // raw bits → CRC+offset → differential encoding → shaped biphase waveform
- // → 57 kHz modulation via {0,+1,0,-1} at 228 kHz
- //
- // The shaped biphase waveform is a pre-computed RRC-filtered Manchester pulse.
- // Successive symbols overlap-add in a ring buffer, eliminating ISI.
- // 57 kHz carrier is exact at 228 kHz (period = 4 samples), no sin() needed.
- //
- // Call NextSample228k() at exactly 228 kHz to get the modulated RDS subcarrier.
-
- const (
- rdsRate = 228000.0 // internal sample rate, must be 4×57000
- rdsBitRate = 1187.5 // bits per second
- samplesPerBit = 192 // rdsRate / rdsBitRate = 228000/1187.5 = 192
- bitsPerBlock = 26
- bitsPerGroup = 4 * bitsPerBlock // 104
- )
-
- const crcPoly = 0x1B9
-
- var offsetWords = map[byte]uint16{
- 'A': 0x0FC, 'B': 0x198, 'C': 0x168, 'c': 0x350, 'D': 0x1B4,
- }
-
- func crc10(data uint16) uint16 {
- var reg uint32 = uint32(data) << 10
- for i := 15; i >= 0; i-- {
- if reg&(1<<(uint(i)+10)) != 0 {
- reg ^= uint32(crcPoly) << uint(i)
- }
- }
- return uint16(reg & 0x3FF)
- }
-
- func encodeBlock(data uint16, offset byte) uint32 {
- return (uint32(data) << 10) | uint32(crc10(data)^offsetWords[offset])
- }
-
- // --- Group building (unchanged) ---
-
- func buildGroup0A(pi uint16, pty uint8, tp, ta bool, segIdx int, ps string) [4]uint16 {
- ps = normalizePS(ps)
- var bB uint16
- if tp {
- bB |= 1 << 10
- }
- bB |= uint16(pty&0x1F) << 5
- if ta {
- bB |= 1 << 4
- }
- bB |= 1 << 3
- bB |= uint16(segIdx & 0x03)
- ci := segIdx * 2
- return [4]uint16{pi, bB, pi, (uint16(ps[ci]) << 8) | uint16(ps[ci+1])}
- }
-
- func buildGroup2A(pi uint16, pty uint8, tp bool, abFlag bool, segIdx int, rt string) [4]uint16 {
- rt = normalizeRT(rt)
- var bB uint16 = 2 << 12
- if tp {
- bB |= 1 << 10
- }
- bB |= uint16(pty&0x1F) << 5
- if abFlag {
- bB |= 1 << 4
- }
- bB |= uint16(segIdx & 0x0F)
- ci := segIdx * 4
- c0, c1, c2, c3 := padRT(rt, ci)
- return [4]uint16{pi, bB, (uint16(c0) << 8) | uint16(c1), (uint16(c2) << 8) | uint16(c3)}
- }
-
- func padRT(rt string, off int) (byte, byte, byte, byte) {
- g := func(i int) byte {
- if i < len(rt) {
- return rt[i]
- }
- return ' '
- }
- return g(off), g(off + 1), g(off + 2), g(off + 3)
- }
-
- // --- Group scheduler (unchanged) ---
-
- type GroupScheduler struct {
- cfg RDSConfig
- psIdx int
- rtIdx int
- phase int
- rtABFlag bool
- }
-
- func newGroupScheduler(cfg RDSConfig) *GroupScheduler {
- return &GroupScheduler{cfg: cfg}
- }
-
- func (gs *GroupScheduler) NextGroup() [4]uint16 {
- if gs.phase < 4 {
- g := buildGroup0A(gs.cfg.PI, gs.cfg.PTY, gs.cfg.TP, gs.cfg.TA, gs.psIdx, gs.cfg.PS)
- gs.psIdx = (gs.psIdx + 1) % 4
- gs.phase++
- return g
- }
- g := buildGroup2A(gs.cfg.PI, gs.cfg.PTY, gs.cfg.TP, gs.rtABFlag, gs.rtIdx, gs.cfg.RT)
- gs.rtIdx++
- rtSegs := rtSegmentCount(gs.cfg.RT)
- if gs.rtIdx >= rtSegs {
- gs.rtIdx = 0
- gs.rtABFlag = !gs.rtABFlag
- }
- gs.phase++
- if gs.phase >= 4+rtSegs {
- gs.phase = 0
- }
- return g
- }
-
- func rtSegmentCount(rt string) int {
- rt = normalizeRT(rt)
- n := (len(rt) + 3) / 4
- if n == 0 {
- n = 1
- }
- if n > 16 {
- n = 16
- }
- return n
- }
-
- // --- Differential encoder ---
-
- type diffEncoder struct{ prev uint8 }
-
- func (d *diffEncoder) encode(bit uint8) uint8 {
- out := d.prev ^ bit
- d.prev = out
- return out
- }
-
- // --- Biphase waveform ---
- // A biphase symbol at 228 kHz / 1187.5 bps = 192 samples per bit.
- // First half (96 samples): +1, second half (96 samples): -1.
- // This is the "rectangular biphase pulse". PiFmRds uses an RRC-shaped
- // version, but even rectangular works for most receivers. We apply a
- // simple raised-cosine taper at the transitions to reduce spectral splatter.
- //
- // The waveform is 192 samples long. It gets overlap-added into the ring
- // buffer, so adjacent symbols blend at boundaries.
-
- const waveformLen = samplesPerBit // 192
- const taperLen = 8 // smooth transitions over 8 samples
-
- var biphaseWaveform [waveformLen]float32
-
- func init() {
- // Generate biphase pulse: +1 for first half, -1 for second half,
- // with raised-cosine tapered transitions.
- half := waveformLen / 2
- for i := 0; i < waveformLen; i++ {
- if i < half {
- biphaseWaveform[i] = 1.0
- } else {
- biphaseWaveform[i] = -1.0
- }
- }
- // Taper at start (0 → +1)
- for i := 0; i < taperLen; i++ {
- t := float32(i) / float32(taperLen)
- biphaseWaveform[i] *= t
- }
- // Taper at midpoint (+1 → -1): ramp over taperLen samples centered at half
- for i := 0; i < taperLen; i++ {
- t := float32(i) / float32(taperLen)
- // Crossfade from +1 to -1
- idx := half - taperLen/2 + i
- if idx >= 0 && idx < waveformLen {
- biphaseWaveform[idx] = 1.0 - 2.0*t
- }
- }
- // Taper at end (-1 → 0)
- for i := 0; i < taperLen; i++ {
- t := float32(i) / float32(taperLen)
- biphaseWaveform[waveformLen-1-i] *= t
- }
- }
-
- // --- Encoder ---
-
- const ringSize = samplesPerBit + waveformLen // overlap region
-
- // Encoder generates RDS subcarrier samples at 228 kHz.
- // Call NextSample228k() at 228 kHz rate to get modulated output.
- type Encoder struct {
- config RDSConfig
- scheduler *GroupScheduler
- diff diffEncoder
-
- // Bit stream
- bitBuf [bitsPerGroup]int
- bitLen int
- bitPos int
-
- // Ring buffer for overlap-add shaped biphase
- ring [ringSize]float32
- ringWrite int // next write position for new symbol
- ringRead int // current read position
-
- // Sample counter within current bit
- sampleCount int
-
- // 57 kHz carrier phase: cycles through 0,1,2,3 at 228 kHz
- carrierPhase int
-
- // For backward-compat Generate() used in tests
- SampleRate float64
- }
-
- func NewEncoder(cfg RDSConfig) (*Encoder, error) {
- if cfg.SampleRate <= 0 {
- cfg.SampleRate = rdsRate
- }
- cfg.PS = normalizePS(cfg.PS)
- cfg.RT = normalizeRT(cfg.RT)
-
- enc := &Encoder{
- config: cfg,
- SampleRate: cfg.SampleRate,
- scheduler: newGroupScheduler(cfg),
- }
- enc.loadNextGroup()
- // Pre-load first symbol into ring buffer
- enc.emitSymbol()
- return enc, nil
- }
-
- func (e *Encoder) Reset() {
- e.diff = diffEncoder{}
- e.scheduler = newGroupScheduler(e.config)
- e.bitLen = 0
- e.bitPos = 0
- e.sampleCount = 0
- e.carrierPhase = 0
- e.ringWrite = 0
- e.ringRead = 0
- for i := range e.ring {
- e.ring[i] = 0
- }
- e.loadNextGroup()
- e.emitSymbol()
- }
-
- // NextSample228k returns the next RDS subcarrier sample.
- // MUST be called at exactly 228000 Hz.
- // Output is the shaped biphase envelope modulated onto 57 kHz.
- func (e *Encoder) NextSample228k() float64 {
- // Read shaped envelope from ring buffer
- envelope := float64(e.ring[e.ringRead])
- e.ring[e.ringRead] = 0 // clear after reading
- e.ringRead++
- if e.ringRead >= ringSize {
- e.ringRead = 0
- }
-
- // Advance sample counter; emit next symbol when bit period ends
- e.sampleCount++
- if e.sampleCount >= samplesPerBit {
- e.sampleCount = 0
- e.bitPos++
- if e.bitPos >= e.bitLen {
- e.loadNextGroup()
- }
- e.emitSymbol()
- }
-
- // 57 kHz modulation: at 228 kHz, period = 4 samples.
- // Phase pattern: {0, +1, 0, -1} → multiply envelope by carrier
- var carrier float64
- switch e.carrierPhase {
- case 0:
- carrier = 0
- case 1:
- carrier = 1
- case 2:
- carrier = 0
- case 3:
- carrier = -1
- }
- e.carrierPhase++
- if e.carrierPhase >= 4 {
- e.carrierPhase = 0
- }
-
- return envelope * carrier
- }
-
- // emitSymbol writes the shaped biphase waveform for the current bit
- // into the ring buffer using overlap-add.
- func (e *Encoder) emitSymbol() {
- // Differential encoding output determines polarity
- diffBit := e.bitBuf[e.bitPos]
- sign := float32(1.0)
- if diffBit == 1 {
- sign = -1.0
- }
-
- // Overlap-add the biphase waveform into the ring buffer
- idx := e.ringWrite
- for i := 0; i < waveformLen; i++ {
- e.ring[idx] += biphaseWaveform[i] * sign
- idx++
- if idx >= ringSize {
- idx = 0
- }
- }
- e.ringWrite += samplesPerBit
- if e.ringWrite >= ringSize {
- e.ringWrite -= ringSize
- }
- }
-
- func (e *Encoder) loadNextGroup() {
- group := e.scheduler.NextGroup()
- e.bitLen = 0
- for blk, off := range [4]byte{'A', 'B', 'C', 'D'} {
- enc := encodeBlock(group[blk], off)
- for bit := bitsPerBlock - 1; bit >= 0; bit-- {
- raw := uint8((enc >> uint(bit)) & 1)
- e.bitBuf[e.bitLen] = int(e.diff.encode(raw))
- e.bitLen++
- }
- }
- e.bitPos = 0
- }
-
- // --- Backward-compatible API for tests ---
-
- // NextSample returns a single RDS sample at 228 kHz. Same as NextSample228k.
- func (e *Encoder) NextSample() float64 {
- return e.NextSample228k()
- }
-
- // Generate produces n RDS samples at 228 kHz.
- func (e *Encoder) Generate(n int) []float64 {
- out := make([]float64, n)
- for i := range out {
- out[i] = e.NextSample228k()
- }
- return out
- }
-
- // Symbol returns +1/-1 for the current differential bit (NRZ, no biphase).
- // Only for the software decoder test path.
- func (e *Encoder) Symbol() float64 {
- if e.bitLen == 0 {
- return -1
- }
- sym := float64(1)
- if e.bitBuf[e.bitPos] == 0 {
- sym = -1
- }
- // Advance bit clock at 228 kHz rate
- e.sampleCount++
- if e.sampleCount >= samplesPerBit {
- e.sampleCount = 0
- e.bitPos++
- if e.bitPos >= e.bitLen {
- e.loadNextGroup()
- }
- }
- return sym
- }
|