Wideband autonomous SDR analysis engine forked from sdr-visual-suite
Nie możesz wybrać więcej, niż 25 tematów Tematy muszą się zaczynać od litery lub cyfry, mogą zawierać myślniki ('-') i mogą mieć do 35 znaków.

107 wiersze
2.7KB

  1. package recorder
  2. import (
  3. "errors"
  4. "log"
  5. "path/filepath"
  6. "sdr-wideband-suite/internal/classifier"
  7. "sdr-wideband-suite/internal/demod"
  8. "sdr-wideband-suite/internal/detector"
  9. )
  10. func (m *Manager) demodAndWrite(dir string, ev detector.Event, iq []complex64, files map[string]any) error {
  11. if ev.Class == nil {
  12. return nil
  13. }
  14. name := mapClassToDemod(ev.Class.ModType)
  15. if name == "" {
  16. return nil
  17. }
  18. d := demod.Get(name)
  19. if d == nil {
  20. return errors.New("demodulator not found")
  21. }
  22. bw := ev.Bandwidth
  23. offset := ev.CenterHz - m.centerHz
  24. var audio []float32
  25. var inputRate int
  26. gpu := m.gpuEngine()
  27. if gpu != nil {
  28. gpuMode, useGPU := gpuModeFor(name)
  29. if useGPU {
  30. if gpuAudio, gpuRate, ok := tryGPUAudio(gpu, name, iq, offset, bw, gpuMode); ok {
  31. audio = gpuAudio
  32. inputRate = gpuRate
  33. }
  34. }
  35. }
  36. var stereoHybrid *wfmHybridResult
  37. if audio == nil {
  38. if name == "WFM_STEREO" {
  39. log.Printf("gpudemod: WFM_STEREO using hybrid stereo/RDS post-process for event %d", ev.ID)
  40. m.mu.RLock()
  41. deemphasisUs := m.policy.DeemphasisUs
  42. m.mu.RUnlock()
  43. res := demodWFMStereoHybrid(m.gpuEngine(), iq, m.sampleRate, offset, bw, deemphasisUs)
  44. stereoHybrid = &res
  45. audio = res.Audio
  46. inputRate = res.AudioRate
  47. } else {
  48. log.Printf("gpudemod: CPU demod fallback used for event %d (%s)", ev.ID, name)
  49. audio, inputRate = demodAudioCPU(d, iq, m.sampleRate, offset, bw)
  50. }
  51. }
  52. wav := filepath.Join(dir, "audio.wav")
  53. if err := writeWAV(wav, audio, inputRate, d.Channels()); err != nil {
  54. return err
  55. }
  56. files["audio"] = "audio.wav"
  57. files["audio_sample_rate"] = inputRate
  58. files["audio_channels"] = d.Channels()
  59. files["audio_demod"] = name
  60. if name == "WFM_STEREO" && stereoHybrid != nil {
  61. if len(stereoHybrid.RDS) > 0 {
  62. rdsPath := filepath.Join(dir, "rds.wav")
  63. _ = writeWAV(rdsPath, stereoHybrid.RDS, stereoHybrid.RDSRate, 1)
  64. files["rds_baseband"] = "rds.wav"
  65. files["rds_sample_rate"] = stereoHybrid.RDSRate
  66. dec := rdsdecoder{}
  67. res := dec.DecodeFloat32(stereoHybrid.RDS, stereoHybrid.RDSRate)
  68. if res.PI != 0 {
  69. files["rds_pi"] = res.PI
  70. }
  71. if res.PS != "" {
  72. files["rds_ps"] = res.PS
  73. }
  74. if res.RT != "" {
  75. files["rds_rt"] = res.RT
  76. }
  77. }
  78. }
  79. return nil
  80. }
  81. func mapClassToDemod(c classifier.SignalClass) string {
  82. switch c {
  83. case classifier.ClassAM:
  84. return "AM"
  85. case classifier.ClassNFM:
  86. return "NFM"
  87. case classifier.ClassWFM:
  88. return "WFM"
  89. case classifier.ClassWFMStereo:
  90. return "WFM_STEREO"
  91. case classifier.ClassSSBUSB:
  92. return "USB"
  93. case classifier.ClassSSBLSB:
  94. return "LSB"
  95. case classifier.ClassCW:
  96. return "CW"
  97. case classifier.ClassFT8, classifier.ClassWSPR, classifier.ClassFSK, classifier.ClassPSK:
  98. return "USB"
  99. default:
  100. return ""
  101. }
  102. }