No puede seleccionar más de 25 temas Los temas deben comenzar con una letra o número, pueden incluir guiones ('-') y pueden tener hasta 35 caracteres de largo.

109 líneas
2.6KB

  1. package recorder
  2. import (
  3. "errors"
  4. "log"
  5. "math"
  6. "path/filepath"
  7. "sdr-visual-suite/internal/classifier"
  8. "sdr-visual-suite/internal/demod"
  9. "sdr-visual-suite/internal/demod/gpudemod"
  10. "sdr-visual-suite/internal/detector"
  11. "sdr-visual-suite/internal/dsp"
  12. )
  13. func (m *Manager) demodAndWrite(dir string, ev detector.Event, iq []complex64, files map[string]any) error {
  14. if ev.Class == nil {
  15. return nil
  16. }
  17. name := mapClassToDemod(ev.Class.ModType)
  18. if name == "" {
  19. return nil
  20. }
  21. d := demod.Get(name)
  22. if d == nil {
  23. return errors.New("demodulator not found")
  24. }
  25. // band-extract around signal
  26. bw := ev.Bandwidth
  27. offset := ev.CenterHz - m.centerHz
  28. var audio []float32
  29. var inputRate int
  30. if m.gpuDemod != nil && name == "NFM" {
  31. if gpuAudio, gpuRate, err := m.gpuDemod.Demod(iq, offset, bw, gpudemod.DemodNFM); err == nil {
  32. audio = gpuAudio
  33. inputRate = gpuRate
  34. if m.gpuDemod.LastShiftUsedGPU() {
  35. log.Printf("gpudemod: validated GPU freq-shift used for event %d", ev.ID)
  36. }
  37. }
  38. }
  39. if audio == nil {
  40. shifted := dsp.FreqShift(iq, m.sampleRate, offset)
  41. cutoff := bw / 2
  42. if cutoff < 200 {
  43. cutoff = 200
  44. }
  45. taps := dsp.LowpassFIR(cutoff, m.sampleRate, 101)
  46. filtered := dsp.ApplyFIR(shifted, taps)
  47. decim := int(math.Round(float64(m.sampleRate) / float64(d.OutputSampleRate())))
  48. if decim < 1 {
  49. decim = 1
  50. }
  51. dec := dsp.Decimate(filtered, decim)
  52. inputRate = m.sampleRate / decim
  53. audio = d.Demod(dec, inputRate)
  54. }
  55. wav := filepath.Join(dir, "audio.wav")
  56. if err := writeWAV(wav, audio, inputRate, d.Channels()); err != nil {
  57. return err
  58. }
  59. files["audio"] = "audio.wav"
  60. files["audio_sample_rate"] = inputRate
  61. files["audio_channels"] = d.Channels()
  62. files["audio_demod"] = name
  63. if name == "WFM_STEREO" {
  64. if rds := demod.RDSBaseband(iq, m.sampleRate); len(rds) > 0 {
  65. rdsPath := filepath.Join(dir, "rds.wav")
  66. _ = writeWAV(rdsPath, rds, 2400, 1)
  67. files["rds_baseband"] = "rds.wav"
  68. files["rds_sample_rate"] = 2400
  69. // naive decode
  70. dec := rdsdecoder{}
  71. res := dec.Decode(rds, 2400)
  72. if res.PI != 0 {
  73. files["rds_pi"] = res.PI
  74. }
  75. if res.PS != "" {
  76. files["rds_ps"] = res.PS
  77. }
  78. if res.RT != "" {
  79. files["rds_rt"] = res.RT
  80. }
  81. }
  82. }
  83. return nil
  84. }
  85. func mapClassToDemod(c classifier.SignalClass) string {
  86. switch c {
  87. case classifier.ClassAM:
  88. return "AM"
  89. case classifier.ClassNFM:
  90. return "NFM"
  91. case classifier.ClassWFM:
  92. return "WFM"
  93. case classifier.ClassSSBUSB:
  94. return "USB"
  95. case classifier.ClassSSBLSB:
  96. return "LSB"
  97. case classifier.ClassCW:
  98. return "CW"
  99. case classifier.ClassFT8, classifier.ClassWSPR, classifier.ClassFSK, classifier.ClassPSK:
  100. return "USB"
  101. default:
  102. return ""
  103. }
  104. }