Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

111 строки
2.8KB

  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. if name == "WFM_STEREO" {
  82. log.Printf("gpudemod: WFM_STEREO live path using CPU stereo/RDS post-process")
  83. } else {
  84. log.Printf("gpudemod: CPU live demod fallback used (%s)", name)
  85. }
  86. shifted := dsp.FreqShift(segment, m.sampleRate, offset)
  87. cutoff := bw / 2
  88. if cutoff < 200 {
  89. cutoff = 200
  90. }
  91. taps := dsp.LowpassFIR(cutoff, m.sampleRate, 101)
  92. filtered := dsp.ApplyFIR(shifted, taps)
  93. decim := int(math.Round(float64(m.sampleRate) / float64(d.OutputSampleRate())))
  94. if decim < 1 {
  95. decim = 1
  96. }
  97. dec := dsp.Decimate(filtered, decim)
  98. inputRate = m.sampleRate / decim
  99. audio = d.Demod(dec, inputRate)
  100. }
  101. buf := &bytes.Buffer{}
  102. if err := writeWAVTo(buf, audio, inputRate, d.Channels()); err != nil {
  103. return nil, 0, err
  104. }
  105. return buf.Bytes(), inputRate, nil
  106. }