|
- package dsp
-
- import "math"
-
- // PreEmphasis implements a first-order high-shelf filter for FM broadcast
- // pre-emphasis. Standard time constants are 50 µs (Europe/world) and
- // 75 µs (North America/South Korea).
- //
- // Transfer function: H(s) = 1 + s*τ
- // Bilinear transform to discrete: H(z) = (b0 + b1*z^-1) / (1 + a1*z^-1)
- type PreEmphasis struct {
- b0, b1 float64
- x1 float64 // state
- enabled bool
- }
-
- // NewPreEmphasis creates a pre-emphasis filter for the given time constant
- // and sample rate. tau is in microseconds (50 or 75).
- func NewPreEmphasis(tauMicroseconds, sampleRate float64) *PreEmphasis {
- if tauMicroseconds <= 0 || sampleRate <= 0 {
- return &PreEmphasis{enabled: false}
- }
- tau := tauMicroseconds * 1e-6
-
- // Bilinear transform of H(s) = 1 + s*tau
- // With pre-warping: let c = 2*fs
- // Numerator: (1 + tau*c) + (-1 + tau*c)*z^-1 => b0 = 1+tau*c, b1 = -1+tau*c
- // Denominator: (1 + tau*c) + (1 - tau*c)*z^-1 (but we normalize so a0=1)
- // Wait - cleaner approach: standard first-order shelf.
-
- // H(s) = (1 + s*tau) mapped with bilinear: s = c*(1 - z^-1)/(1 + z^-1), c = 2*fs
- // De-emphasis: H_de(z) = (1-alpha)/(1 - alpha*z^-1), alpha = exp(-1/(tau*fs))
- // Pre-emphasis: H_pre(z) = 1/H_de(z) = (1 - alpha*z^-1)/(1-alpha)
- // y[n] = (x[n] - alpha*x[n-1]) / (1 - alpha)
- alpha := math.Exp(-1.0 / (tau * sampleRate))
- gain := 1.0 / (1.0 - alpha)
-
- return &PreEmphasis{
- b0: gain,
- b1: -alpha * gain,
- enabled: true,
- }
- }
-
- // Process applies the pre-emphasis filter to a single sample.
- func (p *PreEmphasis) Process(in float64) float64 {
- if !p.enabled {
- return in
- }
- out := p.b0*in + p.b1*p.x1
- p.x1 = in
- return out
- }
-
- // Reset clears the filter state.
- func (p *PreEmphasis) Reset() {
- p.x1 = 0
- }
-
- // DeEmphasis implements the complementary de-emphasis filter.
- // H(z) = (1-alpha)/(1 - alpha*z^-1)
- type DeEmphasis struct {
- alpha float64
- gain float64
- prevOut float64
- enabled bool
- }
-
- // NewDeEmphasis creates a de-emphasis filter.
- func NewDeEmphasis(tauMicroseconds, sampleRate float64) *DeEmphasis {
- if tauMicroseconds <= 0 || sampleRate <= 0 {
- return &DeEmphasis{enabled: false}
- }
- tau := tauMicroseconds * 1e-6
- alpha := math.Exp(-1.0 / (tau * sampleRate))
- return &DeEmphasis{alpha: alpha, gain: 1.0 - alpha, enabled: true}
- }
-
- // Process applies the de-emphasis filter.
- func (d *DeEmphasis) Process(in float64) float64 {
- if !d.enabled {
- return in
- }
- out := d.gain*in + d.alpha*d.prevOut
- d.prevOut = out
- return out
- }
-
- // Reset clears the filter state.
- func (d *DeEmphasis) Reset() {
- d.prevOut = 0
- }
|