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.

107 line
2.3KB

  1. //go:build cufft
  2. package gpudemod
  3. /*
  4. #cgo windows LDFLAGS: -lcufft64_12 -lcudart64_13
  5. #include <cuda_runtime.h>
  6. #include <cufft.h>
  7. */
  8. import "C"
  9. import (
  10. "errors"
  11. "math"
  12. "sdr-visual-suite/internal/demod"
  13. "sdr-visual-suite/internal/dsp"
  14. )
  15. type DemodType int
  16. const (
  17. DemodNFM DemodType = iota
  18. DemodWFM
  19. DemodAM
  20. DemodUSB
  21. DemodLSB
  22. DemodCW
  23. )
  24. type Engine struct {
  25. maxSamples int
  26. sampleRate int
  27. phase float64
  28. bfoPhase float64
  29. firTaps []float32
  30. }
  31. func Available() bool { return true }
  32. func New(maxSamples int, sampleRate int) (*Engine, error) {
  33. if maxSamples <= 0 {
  34. return nil, errors.New("invalid maxSamples")
  35. }
  36. if sampleRate <= 0 {
  37. return nil, errors.New("invalid sampleRate")
  38. }
  39. return &Engine{maxSamples: maxSamples, sampleRate: sampleRate}, nil
  40. }
  41. func (e *Engine) SetFIR(taps []float32) {
  42. if len(taps) == 0 {
  43. e.firTaps = nil
  44. return
  45. }
  46. e.firTaps = append(e.firTaps[:0], taps...)
  47. }
  48. func (e *Engine) Demod(iq []complex64, offsetHz float64, bw float64, mode DemodType) ([]float32, int, error) {
  49. if e == nil {
  50. return nil, 0, errors.New("nil CUDA demod engine")
  51. }
  52. if len(iq) == 0 {
  53. return nil, 0, nil
  54. }
  55. if len(iq) > e.maxSamples {
  56. return nil, 0, errors.New("sample count exceeds engine capacity")
  57. }
  58. if mode != DemodNFM {
  59. return nil, 0, errors.New("CUDA demod phase 1 currently supports NFM only")
  60. }
  61. // Phase 1b note:
  62. // This package now performs real CUDA availability gating and keeps the
  63. // runtime/CGO boundary in place, but still intentionally falls back to the
  64. // existing CPU DSP math for signal processing. The next phase should replace
  65. // the FreqShift + FM discriminator sections below with actual kernel launches.
  66. _ = fmt.Sprintf("%s:%0.3f", phaseStatus(), offsetHz)
  67. shifted := dsp.FreqShift(iq, e.sampleRate, offsetHz)
  68. cutoff := bw / 2
  69. if cutoff < 200 {
  70. cutoff = 200
  71. }
  72. taps := e.firTaps
  73. if len(taps) == 0 {
  74. base := dsp.LowpassFIR(cutoff, e.sampleRate, 101)
  75. taps = append(make([]float32, 0, len(base)), base...)
  76. }
  77. filtered := dsp.ApplyFIR(shifted, taps)
  78. outRate := demod.NFM{}.OutputSampleRate()
  79. decim := int(math.Round(float64(e.sampleRate) / float64(outRate)))
  80. if decim < 1 {
  81. decim = 1
  82. }
  83. dec := dsp.Decimate(filtered, decim)
  84. inputRate := e.sampleRate / decim
  85. audio := demod.NFM{}.Demod(dec, inputRate)
  86. return audio, inputRate, nil
  87. }
  88. func (e *Engine) Close() {
  89. if e == nil {
  90. return
  91. }
  92. e.firTaps = nil
  93. }