Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

137 linhas
4.1KB

  1. package classifier
  2. import "math"
  3. type PLLResult struct {
  4. ExactHz float64 `json:"exact_hz"`
  5. OffsetHz float64 `json:"offset_hz"`
  6. Locked bool `json:"locked"`
  7. Method string `json:"method"`
  8. PrecisionHz float64 `json:"precision_hz"`
  9. Stereo bool `json:"stereo,omitempty"`
  10. RDSStation string `json:"rds_station,omitempty"`
  11. }
  12. func EstimateExactFrequency(iq []complex64, sampleRate int, detectedHz float64, modType SignalClass) PLLResult {
  13. if len(iq) < 256 {
  14. return PLLResult{ExactHz: detectedHz}
  15. }
  16. switch modType {
  17. case ClassWFM:
  18. return estimateWFMPilot(iq, sampleRate, detectedHz)
  19. case ClassAM:
  20. return estimateAMCarrier(iq, sampleRate, detectedHz)
  21. case ClassNFM:
  22. return estimateNFMCarrier(iq, sampleRate, detectedHz)
  23. case ClassCW:
  24. return estimateCWTone(iq, sampleRate, detectedHz)
  25. default:
  26. return PLLResult{ExactHz: detectedHz, Method: "none"}
  27. }
  28. }
  29. func estimateWFMPilot(iq []complex64, sampleRate int, detectedHz float64) PLLResult {
  30. if sampleRate < 40000 {
  31. return PLLResult{ExactHz: detectedHz, Method: "pilot", Locked: false}
  32. }
  33. demod := fmDemod(iq)
  34. if len(demod) == 0 {
  35. return PLLResult{ExactHz: detectedHz, Method: "pilot"}
  36. }
  37. pilotFreq := 19000.0
  38. bestFreq := pilotFreq
  39. bestMag := goertzelMagnitude(demod, pilotFreq, sampleRate)
  40. for offset := -50.0; offset <= 50.0; offset += 1.0 {
  41. mag := goertzelMagnitude(demod, pilotFreq+offset, sampleRate)
  42. if mag > bestMag {
  43. bestMag = mag
  44. bestFreq = pilotFreq + offset
  45. }
  46. }
  47. freqError := bestFreq - 19000.0
  48. noiseMag := goertzelMagnitude(demod, 17500, sampleRate)
  49. locked := bestMag > noiseMag*5
  50. if !locked {
  51. return PLLResult{ExactHz: detectedHz, Method: "pilot", Locked: false}
  52. }
  53. return PLLResult{ExactHz: detectedHz - freqError, OffsetHz: -freqError, Locked: true, Method: "pilot", PrecisionHz: 1.0, Stereo: true}
  54. }
  55. func estimateAMCarrier(iq []complex64, sampleRate int, detectedHz float64) PLLResult {
  56. offset := meanInstFreqHz(iq, sampleRate)
  57. locked := math.Abs(offset) < 5000 // Only lock if offset is plausible (<5 kHz)
  58. return PLLResult{ExactHz: detectedHz + offset, OffsetHz: offset, Locked: locked, Method: "carrier", PrecisionHz: 5.0}
  59. }
  60. func estimateNFMCarrier(iq []complex64, sampleRate int, detectedHz float64) PLLResult {
  61. offset := meanInstFreqHz(iq, sampleRate)
  62. return PLLResult{ExactHz: detectedHz + offset, OffsetHz: offset, Locked: math.Abs(offset) < 5000, Method: "fm_dc", PrecisionHz: 20.0}
  63. }
  64. func estimateCWTone(iq []complex64, sampleRate int, detectedHz float64) PLLResult {
  65. demod := fmDemod(iq)
  66. if len(demod) == 0 {
  67. return PLLResult{ExactHz: detectedHz, Method: "tone"}
  68. }
  69. bestFreq := 700.0
  70. bestMag := 0.0
  71. for f := 300.0; f <= 1200.0; f += 1.0 {
  72. mag := goertzelMagnitude(demod, f, sampleRate)
  73. if mag > bestMag {
  74. bestMag = mag
  75. bestFreq = f
  76. }
  77. }
  78. bfoHz := 700.0
  79. toneOffset := bestFreq - bfoHz
  80. return PLLResult{ExactHz: detectedHz + toneOffset, OffsetHz: toneOffset, Locked: bestMag > 0, Method: "tone", PrecisionHz: 2.0}
  81. }
  82. func fmDemod(iq []complex64) []float64 {
  83. if len(iq) < 2 {
  84. return nil
  85. }
  86. out := make([]float64, len(iq)-1)
  87. for i := 1; i < len(iq); i++ {
  88. p := iq[i-1]
  89. c := iq[i]
  90. num := float64(real(p))*float64(imag(c)) - float64(imag(p))*float64(real(c))
  91. den := float64(real(p))*float64(real(c)) + float64(imag(p))*float64(imag(c))
  92. out[i-1] = math.Atan2(num, den)
  93. }
  94. return out
  95. }
  96. func goertzelMagnitude(samples []float64, targetHz float64, sampleRate int) float64 {
  97. n := len(samples)
  98. if n == 0 {
  99. return 0
  100. }
  101. k := targetHz / (float64(sampleRate) / float64(n))
  102. w := 2.0 * math.Pi * k / float64(n)
  103. coeff := 2.0 * math.Cos(w)
  104. s1, s2 := 0.0, 0.0
  105. for _, v := range samples {
  106. s0 := v + coeff*s1 - s2
  107. s2 = s1
  108. s1 = s0
  109. }
  110. return math.Sqrt(s1*s1 + s2*s2 - coeff*s1*s2)
  111. }
  112. func meanInstFreqHz(iq []complex64, sampleRate int) float64 {
  113. if len(iq) < 2 {
  114. return 0
  115. }
  116. var sum float64
  117. for i := 1; i < len(iq); i++ {
  118. p := iq[i-1]
  119. c := iq[i]
  120. num := float64(real(p))*float64(imag(c)) - float64(imag(p))*float64(real(c))
  121. den := float64(real(p))*float64(real(c)) + float64(imag(p))*float64(imag(c))
  122. sum += math.Atan2(num, den)
  123. }
  124. meanRad := sum / float64(len(iq)-1)
  125. return meanRad * float64(sampleRate) / (2.0 * math.Pi)
  126. }