Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

267 行
6.6KB

  1. package rds
  2. import (
  3. "math"
  4. "os"
  5. )
  6. // RDS2 subcarrier frequencies (IEC 62106-1:2018, Figure 3).
  7. // All are integer multiples of 19 kHz / 2.
  8. const (
  9. RDS2Stream1Freq = 66500.0 // 3.5 × 19 kHz
  10. RDS2Stream2Freq = 71250.0 // 3.75 × 19 kHz
  11. RDS2Stream3Freq = 76000.0 // 4 × 19 kHz
  12. )
  13. // RDS2Encoder generates the three additional RDS2 subcarrier signals.
  14. // Each stream carries Group Type C data at 1187.5 bps.
  15. // The output is added to the composite MPX signal.
  16. type RDS2Encoder struct {
  17. streams [3]*streamEncoder
  18. sampleRate float64
  19. enabled bool
  20. // RFT state: file segments to transmit
  21. rftFile *RFTFile
  22. rftSegIdx int
  23. }
  24. // streamEncoder handles one RDS2 subcarrier stream.
  25. type streamEncoder struct {
  26. sampleRate float64
  27. carrierFreq float64
  28. carrierPhase float64
  29. carrierStep float64
  30. // Biphase encoding (same as Stream 0)
  31. spb int // samples per bit
  32. waveform []float64 // resampled PiFmRds biphase waveform
  33. wfLen int
  34. ring []float64
  35. ringSize int
  36. bitBuffer [bitsPerGroup]int
  37. bitPos int
  38. prevOutput int
  39. curOutput int
  40. sampleCount int
  41. inSampleIdx int
  42. outSampleIdx int
  43. // Group C queue
  44. groups []GroupC
  45. groupIdx int
  46. }
  47. // NewRDS2Encoder creates an RDS2 encoder for the given sample rate.
  48. // Call LoadLogo() to set up the station logo for RFT transmission.
  49. func NewRDS2Encoder(sampleRate float64) *RDS2Encoder {
  50. freqs := [3]float64{RDS2Stream1Freq, RDS2Stream2Freq, RDS2Stream3Freq}
  51. e := &RDS2Encoder{sampleRate: sampleRate}
  52. for i := 0; i < 3; i++ {
  53. e.streams[i] = newStreamEncoder(sampleRate, freqs[i])
  54. }
  55. return e
  56. }
  57. func newStreamEncoder(sampleRate, carrierFreq float64) *streamEncoder {
  58. spb := int(math.Round(sampleRate / rdsBitRate))
  59. ratio := sampleRate / refRate
  60. // Resample PiFmRds waveform (same as main RDS encoder)
  61. wfLen := int(math.Round(float64(refFilterSize) * ratio))
  62. waveform := make([]float64, wfLen)
  63. for i := range waveform {
  64. srcPos := float64(i) / ratio
  65. idx := int(srcPos)
  66. frac := srcPos - float64(idx)
  67. if idx+1 < refFilterSize {
  68. waveform[i] = refWaveform[idx]*(1-frac) + refWaveform[idx+1]*frac
  69. } else if idx < refFilterSize {
  70. waveform[i] = refWaveform[idx]
  71. }
  72. }
  73. // Normalize to peak=1.0
  74. var peak float64
  75. for _, v := range waveform {
  76. if a := math.Abs(v); a > peak {
  77. peak = a
  78. }
  79. }
  80. if peak > 0 {
  81. for i := range waveform {
  82. waveform[i] /= peak
  83. }
  84. }
  85. ringSize := spb + wfLen
  86. return &streamEncoder{
  87. sampleRate: sampleRate,
  88. carrierFreq: carrierFreq,
  89. carrierPhase: 0,
  90. carrierStep: carrierFreq / sampleRate,
  91. spb: spb,
  92. waveform: waveform,
  93. wfLen: wfLen,
  94. ring: make([]float64, ringSize),
  95. ringSize: ringSize,
  96. bitPos: bitsPerGroup,
  97. sampleCount: spb,
  98. outSampleIdx: ringSize - 1,
  99. }
  100. }
  101. // Enable activates or deactivates RDS2 output.
  102. func (e *RDS2Encoder) Enable(on bool) {
  103. e.enabled = on
  104. }
  105. // Enabled returns whether RDS2 is active.
  106. func (e *RDS2Encoder) Enabled() bool {
  107. return e.enabled
  108. }
  109. // LoadLogo reads a logo file and segments it for RFT transmission on Stream 1.
  110. func (e *RDS2Encoder) LoadLogo(path string) error {
  111. data, err := os.ReadFile(path)
  112. if err != nil {
  113. return err
  114. }
  115. // Detect file type
  116. ft := uint8(RFTFileTypePNG)
  117. if len(data) >= 3 && data[0] == 0xFF && data[1] == 0xD8 && data[2] == 0xFF {
  118. ft = RFTFileTypeJPEG
  119. }
  120. e.rftFile = SegmentFile(data, 0, ft) // fileID=0 = station logo
  121. e.rftSegIdx = 0
  122. return nil
  123. }
  124. // NextSample returns the combined RDS2 subcarrier sample for all 3 streams.
  125. // Add this to the composite MPX signal with appropriate injection level.
  126. func (e *RDS2Encoder) NextSample() float64 {
  127. if !e.enabled {
  128. return 0
  129. }
  130. var sum float64
  131. for i := 0; i < 3; i++ {
  132. sum += e.streams[i].nextSample()
  133. }
  134. return sum
  135. }
  136. // NextSampleWithPilot returns the RDS2 sample using pilot-locked carriers.
  137. // pilotPhase is the current pilot oscillator phase (0-1 range).
  138. func (e *RDS2Encoder) NextSampleWithPilot(pilotPhase float64) float64 {
  139. if !e.enabled {
  140. return 0
  141. }
  142. // RDS2 carriers are at 3.5×, 3.75×, 4× pilot frequency
  143. // sin(2π × N × pilotPhase) where N = 3.5, 3.75, 4.0
  144. multipliers := [3]float64{3.5, 3.75, 4.0}
  145. var sum float64
  146. for i := 0; i < 3; i++ {
  147. carrier := math.Sin(2 * math.Pi * multipliers[i] * pilotPhase)
  148. sum += e.streams[i].nextSampleWithCarrier(carrier)
  149. }
  150. return sum
  151. }
  152. // FeedGroups distributes Group C data to the stream encoders.
  153. // Call periodically (e.g. once per frame) to keep streams fed.
  154. func (e *RDS2Encoder) FeedGroups() {
  155. if !e.enabled {
  156. return
  157. }
  158. // Stream 1: Station logo via RFT (if loaded)
  159. if e.rftFile != nil && len(e.rftFile.Segments) > 0 {
  160. seg := e.rftFile.Segments[e.rftSegIdx]
  161. gc := GroupC{FH: seg.FH, Data: seg.Payload}
  162. e.streams[0].pushGroup(gc)
  163. e.rftSegIdx = (e.rftSegIdx + 1) % len(e.rftFile.Segments)
  164. }
  165. // Streams 2 & 3: available for future ODAs
  166. // For now, send idle groups (FH=0, all zeros)
  167. }
  168. // --- streamEncoder methods ---
  169. func (se *streamEncoder) pushGroup(gc GroupC) {
  170. se.groups = append(se.groups, gc)
  171. }
  172. func (se *streamEncoder) nextSample() float64 {
  173. carrier := math.Sin(2 * math.Pi * se.carrierPhase)
  174. se.carrierPhase += se.carrierStep
  175. if se.carrierPhase >= 1.0 {
  176. se.carrierPhase -= 1.0
  177. }
  178. return se.nextSampleWithCarrier(carrier)
  179. }
  180. func (se *streamEncoder) nextSampleWithCarrier(carrier float64) float64 {
  181. if se.sampleCount >= se.spb {
  182. if se.bitPos >= bitsPerGroup {
  183. se.getNextGroup()
  184. se.bitPos = 0
  185. }
  186. curBit := se.bitBuffer[se.bitPos]
  187. se.prevOutput = se.curOutput
  188. se.curOutput = se.prevOutput ^ curBit
  189. inverting := (se.curOutput == 1)
  190. idx := se.inSampleIdx
  191. for j := 0; j < se.wfLen; j++ {
  192. val := se.waveform[j]
  193. if inverting {
  194. val = -val
  195. }
  196. se.ring[idx] += val
  197. idx++
  198. if idx >= se.ringSize {
  199. idx = 0
  200. }
  201. }
  202. se.inSampleIdx += se.spb
  203. if se.inSampleIdx >= se.ringSize {
  204. se.inSampleIdx -= se.ringSize
  205. }
  206. se.bitPos++
  207. se.sampleCount = 0
  208. }
  209. envelope := se.ring[se.outSampleIdx]
  210. se.ring[se.outSampleIdx] = 0
  211. se.outSampleIdx++
  212. if se.outSampleIdx >= se.ringSize {
  213. se.outSampleIdx = 0
  214. }
  215. se.sampleCount++
  216. return envelope * carrier
  217. }
  218. func (se *streamEncoder) getNextGroup() {
  219. var group [4]uint16
  220. if se.groupIdx < len(se.groups) {
  221. gc := se.groups[se.groupIdx]
  222. group = buildGroupC(gc)
  223. se.groupIdx++
  224. if se.groupIdx >= len(se.groups) {
  225. se.groupIdx = 0 // loop
  226. }
  227. }
  228. // else: idle group (all zeros)
  229. // Encode group into bit buffer using same CRC/offset as Stream 0
  230. pos := 0
  231. for blk, off := range [4]byte{'A', 'B', 'C', 'D'} {
  232. encoded := encodeBlock(group[blk], off)
  233. for bit := bitsPerBlock - 1; bit >= 0; bit-- {
  234. se.bitBuffer[pos] = int((encoded >> uint(bit)) & 1)
  235. pos++
  236. }
  237. }
  238. }