Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

84 行
2.4KB

  1. package dsp
  2. import "math"
  3. // HilbertFilter implements a FIR-based Hilbert transform with matched delay.
  4. // It produces the analytic signal's imaginary part (90° phase shift of all
  5. // frequency components) while also providing the appropriately delayed
  6. // version of the original signal.
  7. //
  8. // Used for SSB-SC stereo encoding (Foti/Tarsio): the L-R difference signal
  9. // is split into in-phase (delayed) and quadrature (Hilbert-transformed)
  10. // components for single-sideband modulation.
  11. type HilbertFilter struct {
  12. coeffs []float64 // FIR coefficients (length N, odd)
  13. delay []float64 // circular buffer
  14. pos int // write position in buffer
  15. n int // filter length
  16. mid int // group delay = (N-1)/2
  17. }
  18. // NewHilbertFilter creates a Hilbert transform FIR filter.
  19. // taps must be odd (typical: 127 for 228 kHz). Higher = better low-freq response.
  20. func NewHilbertFilter(taps int) *HilbertFilter {
  21. if taps%2 == 0 {
  22. taps++ // must be odd
  23. }
  24. mid := (taps - 1) / 2
  25. coeffs := make([]float64, taps)
  26. // Hilbert FIR: h[n] = 2/(π·(n-mid)) for odd (n-mid), 0 for even, Hann windowed.
  27. for i := 0; i < taps; i++ {
  28. k := i - mid
  29. if k == 0 {
  30. coeffs[i] = 0 // center tap is always zero
  31. } else if k%2 != 0 {
  32. // Odd offset: ideal Hilbert coefficient × Hann window
  33. window := 0.5 * (1.0 - math.Cos(2*math.Pi*float64(i)/float64(taps)))
  34. coeffs[i] = (2.0 / (math.Pi * float64(k))) * window
  35. }
  36. // Even offset: coefficient is zero (already initialized)
  37. }
  38. return &HilbertFilter{
  39. coeffs: coeffs,
  40. delay: make([]float64, taps),
  41. pos: 0,
  42. n: taps,
  43. mid: mid,
  44. }
  45. }
  46. // Process takes one input sample and returns (delayed, hilbert).
  47. // - delayed: input delayed by (N-1)/2 samples (group delay matched)
  48. // - hilbert: 90° phase-shifted version of the input
  49. //
  50. // Both outputs are time-aligned: delayed[n] corresponds to hilbert[n].
  51. func (h *HilbertFilter) Process(in float64) (delayed, hilbert float64) {
  52. // Write new sample
  53. h.delay[h.pos] = in
  54. // Delayed output: sample from (N-1)/2 ago
  55. delayIdx := (h.pos - h.mid + h.n) % h.n
  56. delayed = h.delay[delayIdx]
  57. // Hilbert output: FIR convolution
  58. var sum float64
  59. for i := 0; i < h.n; i++ {
  60. idx := (h.pos - i + h.n) % h.n
  61. sum += h.delay[idx] * h.coeffs[i]
  62. }
  63. hilbert = sum
  64. h.pos = (h.pos + 1) % h.n
  65. return
  66. }
  67. // Reset clears the filter state.
  68. func (h *HilbertFilter) Reset() {
  69. for i := range h.delay {
  70. h.delay[i] = 0
  71. }
  72. h.pos = 0
  73. }