You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

106 lines
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. if m.gpuDemod != nil {
  46. var gpuMode gpudemod.DemodType
  47. var useGPU bool
  48. switch name {
  49. case "NFM":
  50. gpuMode, useGPU = gpudemod.DemodNFM, true
  51. case "WFM":
  52. gpuMode, useGPU = gpudemod.DemodWFM, true
  53. case "AM":
  54. gpuMode, useGPU = gpudemod.DemodAM, true
  55. case "USB":
  56. gpuMode, useGPU = gpudemod.DemodUSB, true
  57. case "LSB":
  58. gpuMode, useGPU = gpudemod.DemodLSB, true
  59. case "CW":
  60. gpuMode, useGPU = gpudemod.DemodCW, true
  61. }
  62. if useGPU {
  63. if gpuAudio, gpuRate, err := m.gpuDemod.DemodFused(segment, offset, bw, gpuMode); err == nil {
  64. audio = gpuAudio
  65. inputRate = gpuRate
  66. log.Printf("gpudemod: fused GPU live demod used (%s)", name)
  67. } else {
  68. log.Printf("gpudemod: fused GPU live demod failed (%s): %v", name, err)
  69. if gpuAudio, gpuRate, err := m.gpuDemod.Demod(segment, offset, bw, gpuMode); err == nil {
  70. audio = gpuAudio
  71. inputRate = gpuRate
  72. log.Printf("gpudemod: legacy GPU live demod used (%s)", name)
  73. } else {
  74. log.Printf("gpudemod: legacy GPU live demod failed (%s): %v", name, err)
  75. }
  76. }
  77. }
  78. }
  79. if audio == nil {
  80. log.Printf("gpudemod: CPU live demod fallback used (%s)", name)
  81. shifted := dsp.FreqShift(segment, m.sampleRate, offset)
  82. cutoff := bw / 2
  83. if cutoff < 200 {
  84. cutoff = 200
  85. }
  86. taps := dsp.LowpassFIR(cutoff, m.sampleRate, 101)
  87. filtered := dsp.ApplyFIR(shifted, taps)
  88. decim := int(math.Round(float64(m.sampleRate) / float64(d.OutputSampleRate())))
  89. if decim < 1 {
  90. decim = 1
  91. }
  92. dec := dsp.Decimate(filtered, decim)
  93. inputRate = m.sampleRate / decim
  94. audio = d.Demod(dec, inputRate)
  95. }
  96. buf := &bytes.Buffer{}
  97. if err := writeWAVTo(buf, audio, inputRate, d.Channels()); err != nil {
  98. return nil, 0, err
  99. }
  100. return buf.Bytes(), inputRate, nil
  101. }