|
- package dsp
-
- import "math"
-
- // HilbertFilter implements a FIR-based Hilbert transform with matched delay.
- // It produces the analytic signal's imaginary part (90° phase shift of all
- // frequency components) while also providing the appropriately delayed
- // version of the original signal.
- //
- // Used for SSB-SC stereo encoding (Foti/Tarsio): the L-R difference signal
- // is split into in-phase (delayed) and quadrature (Hilbert-transformed)
- // components for single-sideband modulation.
- type HilbertFilter struct {
- coeffs []float64 // FIR coefficients (length N, odd)
- delay []float64 // circular buffer
- pos int // write position in buffer
- n int // filter length
- mid int // group delay = (N-1)/2
- }
-
- // NewHilbertFilter creates a Hilbert transform FIR filter.
- // taps must be odd (typical: 127 for 228 kHz). Higher = better low-freq response.
- func NewHilbertFilter(taps int) *HilbertFilter {
- if taps%2 == 0 {
- taps++ // must be odd
- }
- mid := (taps - 1) / 2
- coeffs := make([]float64, taps)
-
- // Hilbert FIR: h[n] = 2/(π·(n-mid)) for odd (n-mid), 0 for even, Hann windowed.
- for i := 0; i < taps; i++ {
- k := i - mid
- if k == 0 {
- coeffs[i] = 0 // center tap is always zero
- } else if k%2 != 0 {
- // Odd offset: ideal Hilbert coefficient × Hann window
- window := 0.5 * (1.0 - math.Cos(2*math.Pi*float64(i)/float64(taps)))
- coeffs[i] = (2.0 / (math.Pi * float64(k))) * window
- }
- // Even offset: coefficient is zero (already initialized)
- }
-
- return &HilbertFilter{
- coeffs: coeffs,
- delay: make([]float64, taps),
- pos: 0,
- n: taps,
- mid: mid,
- }
- }
-
- // Process takes one input sample and returns (delayed, hilbert).
- // - delayed: input delayed by (N-1)/2 samples (group delay matched)
- // - hilbert: 90° phase-shifted version of the input
- //
- // Both outputs are time-aligned: delayed[n] corresponds to hilbert[n].
- func (h *HilbertFilter) Process(in float64) (delayed, hilbert float64) {
- // Write new sample
- h.delay[h.pos] = in
-
- // Delayed output: sample from (N-1)/2 ago
- delayIdx := (h.pos - h.mid + h.n) % h.n
- delayed = h.delay[delayIdx]
-
- // Hilbert output: FIR convolution
- var sum float64
- for i := 0; i < h.n; i++ {
- idx := (h.pos - i + h.n) % h.n
- sum += h.delay[idx] * h.coeffs[i]
- }
- hilbert = sum
-
- h.pos = (h.pos + 1) % h.n
- return
- }
-
- // Reset clears the filter state.
- func (h *HilbertFilter) Reset() {
- for i := range h.delay {
- h.delay[i] = 0
- }
- h.pos = 0
- }
|