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

107 строки
2.7KB

  1. package recorder
  2. import (
  3. "bytes"
  4. "errors"
  5. "log"
  6. "math"
  7. "time"
  8. "sdr-visual-suite/internal/demod"
  9. "sdr-visual-suite/internal/demod/gpudemod"
  10. "sdr-visual-suite/internal/dsp"
  11. )
  12. // DemodLive demodulates a recent window and returns WAV bytes.
  13. func (m *Manager) DemodLive(centerHz float64, bw float64, mode string, seconds int) ([]byte, int, error) {
  14. if m == nil || m.ring == nil {
  15. return nil, 0, errors.New("recorder not ready")
  16. }
  17. if seconds <= 0 {
  18. seconds = 2
  19. }
  20. end := time.Now()
  21. start := end.Add(-time.Duration(seconds) * time.Second)
  22. segment := m.ring.Slice(start, end)
  23. if len(segment) == 0 {
  24. return nil, 0, errors.New("no iq in ring")
  25. }
  26. name := mode
  27. if name == "" {
  28. name = "NFM"
  29. }
  30. switch name {
  31. case "AM", "NFM", "WFM", "WFM_STEREO", "USB", "LSB", "CW":
  32. default:
  33. name = "NFM"
  34. }
  35. d := demod.Get(name)
  36. if d == nil {
  37. return nil, 0, errors.New("demodulator not found")
  38. }
  39. offset := centerHz - m.centerHz
  40. if bw <= 0 {
  41. bw = 12000
  42. }
  43. var audio []float32
  44. var inputRate int
  45. gpu := m.gpuEngine()
  46. if gpu != nil {
  47. var gpuMode gpudemod.DemodType
  48. var useGPU bool
  49. switch name {
  50. case "NFM":
  51. gpuMode, useGPU = gpudemod.DemodNFM, true
  52. case "WFM":
  53. gpuMode, useGPU = gpudemod.DemodWFM, true
  54. case "AM":
  55. gpuMode, useGPU = gpudemod.DemodAM, true
  56. case "USB":
  57. gpuMode, useGPU = gpudemod.DemodUSB, true
  58. case "LSB":
  59. gpuMode, useGPU = gpudemod.DemodLSB, true
  60. case "CW":
  61. gpuMode, useGPU = gpudemod.DemodCW, true
  62. }
  63. if useGPU {
  64. if gpuAudio, gpuRate, err := gpu.DemodFused(segment, offset, bw, gpuMode); err == nil {
  65. audio = gpuAudio
  66. inputRate = gpuRate
  67. log.Printf("gpudemod: fused GPU live demod used (%s)", name)
  68. } else {
  69. log.Printf("gpudemod: fused GPU live demod failed (%s): %v", name, err)
  70. if gpuAudio, gpuRate, err := gpu.Demod(segment, offset, bw, gpuMode); err == nil {
  71. audio = gpuAudio
  72. inputRate = gpuRate
  73. log.Printf("gpudemod: legacy GPU live demod used (%s)", name)
  74. } else {
  75. log.Printf("gpudemod: legacy GPU live demod failed (%s): %v", name, err)
  76. }
  77. }
  78. }
  79. }
  80. if audio == nil {
  81. log.Printf("gpudemod: CPU live demod fallback used (%s)", name)
  82. shifted := dsp.FreqShift(segment, m.sampleRate, offset)
  83. cutoff := bw / 2
  84. if cutoff < 200 {
  85. cutoff = 200
  86. }
  87. taps := dsp.LowpassFIR(cutoff, m.sampleRate, 101)
  88. filtered := dsp.ApplyFIR(shifted, taps)
  89. decim := int(math.Round(float64(m.sampleRate) / float64(d.OutputSampleRate())))
  90. if decim < 1 {
  91. decim = 1
  92. }
  93. dec := dsp.Decimate(filtered, decim)
  94. inputRate = m.sampleRate / decim
  95. audio = d.Demod(dec, inputRate)
  96. }
  97. buf := &bytes.Buffer{}
  98. if err := writeWAVTo(buf, audio, inputRate, d.Channels()); err != nil {
  99. return nil, 0, err
  100. }
  101. return buf.Bytes(), inputRate, nil
  102. }