Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

131 lignes
3.8KB

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