Wideband autonomous SDR analysis engine forked from sdr-visual-suite
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.

78 lines
2.3KB

  1. package recorder
  2. import (
  3. "math"
  4. "sdr-wideband-suite/internal/demod"
  5. "sdr-wideband-suite/internal/dsp"
  6. )
  7. func demodAudioCPU(d demod.Demodulator, iq []complex64, sampleRate int, offset float64, bw float64) ([]float32, int) {
  8. shifted := dsp.FreqShift(iq, sampleRate, offset)
  9. cutoff := bw / 2
  10. if cutoff < 200 {
  11. cutoff = 200
  12. }
  13. // For WFM, ensure we capture the full FM composite (at least 75 kHz)
  14. if cutoff < 75000 && d.OutputSampleRate() >= 192000 {
  15. cutoff = 75000
  16. }
  17. taps := dsp.LowpassFIR(cutoff, sampleRate, 101)
  18. filtered := dsp.ApplyFIR(shifted, taps)
  19. // First decimation: get to a demod-friendly rate
  20. demodRate := d.OutputSampleRate()
  21. decim1 := int(math.Round(float64(sampleRate) / float64(demodRate)))
  22. if decim1 < 1 {
  23. decim1 = 1
  24. }
  25. dec := dsp.Decimate(filtered, decim1)
  26. actualDemodRate := sampleRate / decim1
  27. // Demodulate at the intermediate rate
  28. audio := d.Demod(dec, actualDemodRate)
  29. // Second decimation: resample to exactly 48 kHz for browser playback
  30. const outputRate = 48000
  31. if actualDemodRate > outputRate {
  32. // Anti-alias low-pass before decimation
  33. aaTaps := dsp.LowpassFIR(float64(outputRate)/2.0*0.9, actualDemodRate, 63)
  34. channels := d.Channels()
  35. if channels > 1 {
  36. // For stereo: de-interleave, filter, decimate, re-interleave
  37. nFrames := len(audio) / channels
  38. left := make([]float32, nFrames)
  39. right := make([]float32, nFrames)
  40. for i := 0; i < nFrames; i++ {
  41. left[i] = audio[i*2]
  42. right[i] = audio[i*2+1]
  43. }
  44. left = dsp.ApplyFIRReal(left, aaTaps)
  45. right = dsp.ApplyFIRReal(right, aaTaps)
  46. decim2 := int(math.Round(float64(actualDemodRate) / float64(outputRate)))
  47. if decim2 < 1 {
  48. decim2 = 1
  49. }
  50. outFrames := nFrames / decim2
  51. resampled := make([]float32, outFrames*2)
  52. for i := 0; i < outFrames; i++ {
  53. resampled[i*2] = left[i*decim2]
  54. resampled[i*2+1] = right[i*decim2]
  55. }
  56. return resampled, actualDemodRate / decim2
  57. }
  58. audio = dsp.ApplyFIRReal(audio, aaTaps)
  59. decim2 := int(math.Round(float64(actualDemodRate) / float64(outputRate)))
  60. if decim2 < 1 {
  61. decim2 = 1
  62. }
  63. resampled := make([]float32, 0, len(audio)/decim2+1)
  64. for i := 0; i < len(audio); i += decim2 {
  65. resampled = append(resampled, audio[i])
  66. }
  67. return resampled, actualDemodRate / decim2
  68. }
  69. return audio, actualDemodRate
  70. }