Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

180 строки
4.6KB

  1. package demod
  2. import (
  3. "math"
  4. "sdr-wideband-suite/internal/dsp"
  5. "sdr-wideband-suite/internal/logging"
  6. )
  7. type NFM struct{}
  8. type WFM struct{}
  9. type WFMStereo struct{}
  10. func (NFM) Name() string { return "NFM" }
  11. func (WFM) Name() string { return "WFM" }
  12. func (WFMStereo) Name() string { return "WFM_STEREO" }
  13. func (NFM) OutputSampleRate() int { return 48000 }
  14. func (WFM) OutputSampleRate() int { return 192000 }
  15. func (WFMStereo) OutputSampleRate() int {
  16. return 192000
  17. }
  18. func (NFM) Channels() int { return 1 }
  19. func (WFM) Channels() int { return 1 }
  20. func (WFMStereo) Channels() int { return 2 }
  21. func wfmMonoBase(iq []complex64) []float32 {
  22. return fmDiscrim(iq)
  23. }
  24. func (NFM) Demod(iq []complex64, sampleRate int) []float32 {
  25. return fmDiscrim(iq)
  26. }
  27. func (WFM) Demod(iq []complex64, sampleRate int) []float32 {
  28. return wfmMonoBase(iq)
  29. }
  30. func (WFMStereo) Demod(iq []complex64, sampleRate int) []float32 {
  31. return wfmStereo(iq, sampleRate)
  32. }
  33. func fmDiscrim(iq []complex64) []float32 {
  34. if len(iq) < 2 {
  35. return nil
  36. }
  37. out := make([]float32, len(iq)-1)
  38. maxAbs := 0.0
  39. maxIdx := 0
  40. largeSteps := 0
  41. minMag := math.MaxFloat64
  42. maxMag := 0.0
  43. for i := 1; i < len(iq); i++ {
  44. p := iq[i-1]
  45. c := iq[i]
  46. pmag := math.Hypot(float64(real(p)), float64(imag(p)))
  47. cmag := math.Hypot(float64(real(c)), float64(imag(c)))
  48. if pmag < minMag {
  49. minMag = pmag
  50. }
  51. if cmag < minMag {
  52. minMag = cmag
  53. }
  54. if pmag > maxMag {
  55. maxMag = pmag
  56. }
  57. if cmag > maxMag {
  58. maxMag = cmag
  59. }
  60. num := float64(real(p))*float64(imag(c)) - float64(imag(p))*float64(real(c))
  61. den := float64(real(p))*float64(real(c)) + float64(imag(p))*float64(imag(c))
  62. step := math.Atan2(num, den)
  63. if a := math.Abs(step); a > maxAbs {
  64. maxAbs = a
  65. maxIdx = i - 1
  66. }
  67. if math.Abs(step) > 1.5 {
  68. largeSteps++
  69. }
  70. out[i-1] = float32(step)
  71. }
  72. if logging.EnabledCategory("discrim") {
  73. logging.Debug("discrim", "fm_meter", "iq_len", len(iq), "audio_len", len(out), "min_mag", minMag, "max_mag", maxMag, "max_abs_step", maxAbs, "max_idx", maxIdx, "large_steps", largeSteps)
  74. if largeSteps > 0 {
  75. logging.Warn("discrim", "fm_large_steps", "iq_len", len(iq), "large_steps", largeSteps, "max_abs_step", maxAbs, "max_idx", maxIdx, "min_mag", minMag, "max_mag", maxMag)
  76. }
  77. }
  78. return out
  79. }
  80. func wfmStereo(iq []complex64, sampleRate int) []float32 {
  81. base := fmDiscrim(iq)
  82. if len(base) == 0 {
  83. return nil
  84. }
  85. lp := dsp.LowpassFIR(15000, sampleRate, 101)
  86. lpr := dsp.ApplyFIRReal(base, lp)
  87. bpHi := dsp.LowpassFIR(53000, sampleRate, 101)
  88. bpLo := dsp.LowpassFIR(23000, sampleRate, 101)
  89. hi := dsp.ApplyFIRReal(base, bpHi)
  90. lo := dsp.ApplyFIRReal(base, bpLo)
  91. bpf := make([]float32, len(base))
  92. for i := range base {
  93. bpf[i] = hi[i] - lo[i]
  94. }
  95. lr := make([]float32, len(base))
  96. phase := 0.0
  97. inc := 2 * math.Pi * 38000 / float64(sampleRate)
  98. for i := range bpf {
  99. phase += inc
  100. lr[i] = bpf[i] * float32(2*math.Cos(phase))
  101. }
  102. lr = dsp.ApplyFIRReal(lr, lp)
  103. out := make([]float32, len(lpr)*2)
  104. for i := range lpr {
  105. l := 0.5 * (lpr[i] + lr[i])
  106. r := 0.5 * (lpr[i] - lr[i])
  107. out[i*2] = l
  108. out[i*2+1] = r
  109. }
  110. return out
  111. }
  112. type RDSBasebandResult struct {
  113. Samples []float32
  114. SampleRate int
  115. }
  116. // RDSBaseband returns a rough 57k baseband (not decoded).
  117. func RDSBaseband(iq []complex64, sampleRate int) []float32 {
  118. return RDSBasebandDecimated(iq, sampleRate).Samples
  119. }
  120. // RDSComplexResult holds complex baseband samples for the Costas loop RDS decoder.
  121. type RDSComplexResult struct {
  122. Samples []complex64
  123. SampleRate int
  124. }
  125. // RDSBasebandComplex extracts the RDS subcarrier as complex samples.
  126. // The Costas loop in the RDS decoder needs both I and Q to lock.
  127. func RDSBasebandComplex(iq []complex64, sampleRate int) RDSComplexResult {
  128. base := wfmMonoBase(iq)
  129. if len(base) == 0 || sampleRate <= 0 {
  130. return RDSComplexResult{}
  131. }
  132. cplx := make([]complex64, len(base))
  133. for i, v := range base {
  134. cplx[i] = complex(v, 0)
  135. }
  136. cplx = dsp.FreqShift(cplx, sampleRate, -57000)
  137. lpTaps := dsp.LowpassFIR(7500, sampleRate, 101)
  138. cplx = dsp.ApplyFIR(cplx, lpTaps)
  139. targetRate := 19000
  140. decim := sampleRate / targetRate
  141. if decim < 1 {
  142. decim = 1
  143. }
  144. cplx = dsp.Decimate(cplx, decim)
  145. actualRate := sampleRate / decim
  146. return RDSComplexResult{Samples: cplx, SampleRate: actualRate}
  147. }
  148. // RDSBasebandDecimated returns float32 baseband for WAV writing / recorder.
  149. func RDSBasebandDecimated(iq []complex64, sampleRate int) RDSBasebandResult {
  150. res := RDSBasebandComplex(iq, sampleRate)
  151. out := make([]float32, len(res.Samples))
  152. for i, c := range res.Samples {
  153. out[i] = real(c)
  154. }
  155. return RDSBasebandResult{Samples: out, SampleRate: res.SampleRate}
  156. }
  157. func init() {
  158. Register(NFM{})
  159. Register(WFM{})
  160. Register(WFMStereo{})
  161. }