Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.

215 řádky
6.3KB

  1. package rds
  2. import "math"
  3. const (
  4. defaultSubcarrierHz = 57000.0
  5. defaultBitRateHz = 1187.5
  6. bitsPerBlock = 26
  7. bitsPerGroup = 4 * bitsPerBlock
  8. )
  9. const crcPoly = 0x1B9
  10. var offsetWords = map[byte]uint16{
  11. 'A': 0x0FC, 'B': 0x198, 'C': 0x168, 'c': 0x350, 'D': 0x1B4,
  12. }
  13. func crc10(data uint16) uint16 {
  14. var reg uint32 = uint32(data) << 10
  15. for i := 15; i >= 0; i-- {
  16. if reg&(1<<(uint(i)+10)) != 0 { reg ^= uint32(crcPoly) << uint(i) }
  17. }
  18. return uint16(reg & 0x3FF)
  19. }
  20. func encodeBlock(data uint16, offset byte) uint32 {
  21. return (uint32(data) << 10) | uint32(crc10(data)^offsetWords[offset])
  22. }
  23. func buildGroup0A(pi uint16, pty uint8, tp, ta bool, segIdx int, ps string) [4]uint16 {
  24. ps = normalizePS(ps)
  25. var bB uint16
  26. if tp { bB |= 1 << 10 }
  27. bB |= uint16(pty&0x1F) << 5
  28. if ta { bB |= 1 << 4 }
  29. bB |= 1 << 3
  30. bB |= uint16(segIdx & 0x03)
  31. ci := segIdx * 2
  32. return [4]uint16{pi, bB, pi, (uint16(ps[ci]) << 8) | uint16(ps[ci+1])}
  33. }
  34. func buildGroup2A(pi uint16, pty uint8, tp bool, abFlag bool, segIdx int, rt string) [4]uint16 {
  35. rt = normalizeRT(rt)
  36. var bB uint16 = 2 << 12
  37. if tp { bB |= 1 << 10 }
  38. bB |= uint16(pty&0x1F) << 5
  39. if abFlag { bB |= 1 << 4 }
  40. bB |= uint16(segIdx & 0x0F)
  41. ci := segIdx * 4
  42. c0, c1, c2, c3 := padRT(rt, ci)
  43. return [4]uint16{pi, bB, (uint16(c0) << 8) | uint16(c1), (uint16(c2) << 8) | uint16(c3)}
  44. }
  45. func padRT(rt string, off int) (byte, byte, byte, byte) {
  46. g := func(i int) byte { if i < len(rt) { return rt[i] }; return ' ' }
  47. return g(off), g(off + 1), g(off + 2), g(off + 3)
  48. }
  49. type GroupScheduler struct {
  50. cfg RDSConfig; psIdx, rtIdx, phase int; rtABFlag bool
  51. }
  52. func newGroupScheduler(cfg RDSConfig) *GroupScheduler { return &GroupScheduler{cfg: cfg} }
  53. func (gs *GroupScheduler) NextGroup() [4]uint16 {
  54. if gs.phase < 4 {
  55. g := buildGroup0A(gs.cfg.PI, gs.cfg.PTY, gs.cfg.TP, gs.cfg.TA, gs.psIdx, gs.cfg.PS)
  56. gs.psIdx = (gs.psIdx + 1) % 4; gs.phase++; return g
  57. }
  58. g := buildGroup2A(gs.cfg.PI, gs.cfg.PTY, gs.cfg.TP, gs.rtABFlag, gs.rtIdx, gs.cfg.RT)
  59. gs.rtIdx++
  60. rtSegs := rtSegmentCount(gs.cfg.RT)
  61. if gs.rtIdx >= rtSegs { gs.rtIdx = 0; gs.rtABFlag = !gs.rtABFlag }
  62. gs.phase++
  63. if gs.phase >= 4+rtSegs { gs.phase = 0 }
  64. return g
  65. }
  66. func rtSegmentCount(rt string) int {
  67. rt = normalizeRT(rt); n := (len(rt) + 3) / 4
  68. if n == 0 { n = 1 }; if n > 16 { n = 16 }; return n
  69. }
  70. type diffEncoder struct{ prev uint8 }
  71. func (d *diffEncoder) encode(bit uint8) uint8 {
  72. out := d.prev ^ bit; d.prev = out; return out
  73. }
  74. // Encoder produces RDS biphase-coded BPSK data locked to the pilot tone.
  75. //
  76. // Per IEC 62106, RDS uses biphase coding: the differentially-encoded data
  77. // is modulo-2 added with a clock signal at the data rate. This means every
  78. // bit period has a guaranteed polarity transition at the midpoint.
  79. //
  80. // The bit clock runs at 1187.5 bps = pilot_freq / 16.
  81. // Each bit period spans 16 pilot cycles. At the 8th cycle (midpoint),
  82. // the biphase symbol inverts polarity.
  83. type Encoder struct {
  84. config RDSConfig
  85. scheduler *GroupScheduler
  86. diff diffEncoder
  87. bitBuf [bitsPerGroup]uint8
  88. bitLen int
  89. bitPos int
  90. // Pilot-derived timing
  91. pilotCycles int
  92. lastPilotPhase float64
  93. inSecondHalf bool // true when in 2nd half of biphase bit period
  94. // Internal oscillator for Generate() backward compat
  95. sampleRate float64
  96. subPhase float64
  97. bitPhase float64
  98. }
  99. func NewEncoder(cfg RDSConfig) (*Encoder, error) {
  100. if cfg.SampleRate <= 0 { cfg.SampleRate = 228000 }
  101. cfg.PS = normalizePS(cfg.PS); cfg.RT = normalizeRT(cfg.RT)
  102. enc := &Encoder{config: cfg, sampleRate: cfg.SampleRate, scheduler: newGroupScheduler(cfg)}
  103. enc.loadNextGroup()
  104. return enc, nil
  105. }
  106. func (e *Encoder) Reset() {
  107. e.bitPhase = 0; e.subPhase = 0; e.pilotCycles = 0; e.lastPilotPhase = 0
  108. e.inSecondHalf = false
  109. e.diff = diffEncoder{}; e.scheduler = newGroupScheduler(e.config)
  110. e.bitLen = 0; e.bitPos = 0; e.loadNextGroup()
  111. }
  112. // BiphaseSymbolForPilotPhase returns the biphase-coded BPSK symbol (+1/-1)
  113. // for the current sample, with timing locked to the pilot phase.
  114. //
  115. // Biphase coding: for each bit period (16 pilot cycles):
  116. // - First 8 cycles: symbol = data_symbol
  117. // - Last 8 cycles: symbol = -data_symbol (inverted)
  118. //
  119. // This guarantees a transition at every bit midpoint, which is how
  120. // hardware RDS decoders synchronize their clock.
  121. func (e *Encoder) BiphaseSymbolForPilotPhase(pilotPhase float64) float64 {
  122. // Detect pilot cycle completion: phase wraps from >0.5 to <0.5
  123. if e.lastPilotPhase > 0.5 && pilotPhase < 0.5 {
  124. e.pilotCycles++
  125. if e.pilotCycles == 8 {
  126. // Midpoint of bit period: enter second half (biphase inversion)
  127. e.inSecondHalf = true
  128. }
  129. if e.pilotCycles >= 16 {
  130. // End of bit period: advance to next bit
  131. e.pilotCycles = 0
  132. e.inSecondHalf = false
  133. e.bitPos++
  134. if e.bitPos >= e.bitLen { e.loadNextGroup() }
  135. }
  136. }
  137. e.lastPilotPhase = pilotPhase
  138. sym := e.currentSymbol()
  139. if e.inSecondHalf {
  140. sym = -sym // biphase: invert in second half
  141. }
  142. return sym
  143. }
  144. // Symbol returns current symbol with internal bit clock (for tests).
  145. func (e *Encoder) Symbol() float64 {
  146. sym := e.currentSymbol(); e.advanceInternal(); return sym
  147. }
  148. // NextSample returns complete RDS sample with internal 57 kHz (for tests).
  149. // Note: uses NRZ (not biphase) for backward compat with software decoder.
  150. func (e *Encoder) NextSample() float64 {
  151. sym := e.currentSymbol()
  152. v := sym * math.Sin(2*math.Pi*e.subPhase)
  153. e.subPhase += defaultSubcarrierHz / e.sampleRate
  154. if e.subPhase >= 1 { e.subPhase -= math.Floor(e.subPhase) }
  155. e.advanceInternal()
  156. return v
  157. }
  158. // Generate produces n samples with internal oscillator (for tests).
  159. func (e *Encoder) Generate(n int) []float64 {
  160. out := make([]float64, n)
  161. for i := range out { out[i] = e.NextSample() }
  162. return out
  163. }
  164. func (e *Encoder) advanceInternal() {
  165. e.bitPhase += defaultBitRateHz / e.sampleRate
  166. if e.bitPhase >= 1 {
  167. s := int(e.bitPhase); e.bitPhase -= float64(s); e.bitPos += s
  168. if e.bitPos >= e.bitLen { e.loadNextGroup() }
  169. }
  170. }
  171. func (e *Encoder) loadNextGroup() {
  172. group := e.scheduler.NextGroup()
  173. e.bitLen = 0
  174. for blk, off := range [4]byte{'A', 'B', 'C', 'D'} {
  175. enc := encodeBlock(group[blk], off)
  176. for bit := bitsPerBlock - 1; bit >= 0; bit-- {
  177. e.bitBuf[e.bitLen] = e.diff.encode(uint8((enc >> uint(bit)) & 1))
  178. e.bitLen++
  179. }
  180. }
  181. e.bitPos = 0
  182. }
  183. func (e *Encoder) currentSymbol() float64 {
  184. if e.bitLen == 0 || e.bitBuf[e.bitPos] == 0 { return -1 }
  185. return 1
  186. }