Go-based FM stereo transmitter with RDS, Windows-first and cross-platform
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

95 строки
2.6KB

  1. package dsp
  2. import "math"
  3. // PreEmphasis implements a first-order high-shelf filter for FM broadcast
  4. // pre-emphasis. Standard time constants are 50 µs (Europe/world) and
  5. // 75 µs (North America/South Korea).
  6. //
  7. // Transfer function: H(s) = 1 + s*τ
  8. // Bilinear transform to discrete: H(z) = (b0 + b1*z^-1) / (1 + a1*z^-1)
  9. type PreEmphasis struct {
  10. b0, b1, a1 float64
  11. x1, y1 float64 // state
  12. enabled bool
  13. }
  14. // NewPreEmphasis creates a pre-emphasis filter for the given time constant
  15. // and sample rate. tau is in microseconds (50 or 75).
  16. func NewPreEmphasis(tauMicroseconds, sampleRate float64) *PreEmphasis {
  17. if tauMicroseconds <= 0 || sampleRate <= 0 {
  18. return &PreEmphasis{enabled: false}
  19. }
  20. tau := tauMicroseconds * 1e-6
  21. // Bilinear transform of H(s) = 1 + s*tau
  22. // With pre-warping: let c = 2*fs
  23. // Numerator: (1 + tau*c) + (-1 + tau*c)*z^-1 => b0 = 1+tau*c, b1 = -1+tau*c
  24. // Denominator: (1 + tau*c) + (1 - tau*c)*z^-1 (but we normalize so a0=1)
  25. // Wait - cleaner approach: standard first-order shelf.
  26. // H(s) = (1 + s*tau) mapped with bilinear: s = c*(1 - z^-1)/(1 + z^-1), c = 2*fs
  27. // De-emphasis: H_de(z) = (1-alpha)/(1 - alpha*z^-1), alpha = exp(-1/(tau*fs))
  28. // Pre-emphasis: H_pre(z) = 1/H_de(z) = (1 - alpha*z^-1)/(1-alpha)
  29. // y[n] = (x[n] - alpha*x[n-1]) / (1 - alpha)
  30. alpha := math.Exp(-1.0 / (tau * sampleRate))
  31. gain := 1.0 / (1.0 - alpha)
  32. return &PreEmphasis{
  33. b0: gain,
  34. b1: -alpha * gain,
  35. a1: 0, // FIR, no feedback
  36. enabled: true,
  37. }
  38. }
  39. // Process applies the pre-emphasis filter to a single sample.
  40. func (p *PreEmphasis) Process(in float64) float64 {
  41. if !p.enabled {
  42. return in
  43. }
  44. out := p.b0*in + p.b1*p.x1
  45. p.x1 = in
  46. return out
  47. }
  48. // Reset clears the filter state.
  49. func (p *PreEmphasis) Reset() {
  50. p.x1 = 0
  51. p.y1 = 0
  52. }
  53. // DeEmphasis implements the complementary de-emphasis filter.
  54. // H(z) = (1-alpha)/(1 - alpha*z^-1)
  55. type DeEmphasis struct {
  56. alpha float64
  57. gain float64
  58. prevOut float64
  59. enabled bool
  60. }
  61. // NewDeEmphasis creates a de-emphasis filter.
  62. func NewDeEmphasis(tauMicroseconds, sampleRate float64) *DeEmphasis {
  63. if tauMicroseconds <= 0 || sampleRate <= 0 {
  64. return &DeEmphasis{enabled: false}
  65. }
  66. tau := tauMicroseconds * 1e-6
  67. alpha := math.Exp(-1.0 / (tau * sampleRate))
  68. return &DeEmphasis{alpha: alpha, gain: 1.0 - alpha, enabled: true}
  69. }
  70. // Process applies the de-emphasis filter.
  71. func (d *DeEmphasis) Process(in float64) float64 {
  72. if !d.enabled {
  73. return in
  74. }
  75. out := d.gain*in + d.alpha*d.prevOut
  76. d.prevOut = out
  77. return out
  78. }
  79. // Reset clears the filter state.
  80. func (d *DeEmphasis) Reset() {
  81. d.prevOut = 0
  82. }