feature/high-low-pass-filter (#2)

Co-authored-by: Bastian Bührig <bastian.buehrig@2bconsult.eu>
Reviewed-on: #2
This commit was merged in pull request #2.
This commit is contained in:
2025-07-11 10:46:34 +02:00
parent a113550aee
commit bb9a93a4f0
5 changed files with 171 additions and 1 deletions

View File

@@ -341,3 +341,115 @@ func FadeOutLinear(data []float64, fadeSamples int) []float64 {
}
return out
}
// ApplyLowpassButterworth applies a 2nd-order Butterworth low-pass filter to the data.
// cutoffHz: cutoff frequency in Hz, sampleRate: sample rate in Hz.
func ApplyLowpassButterworth(data []float64, sampleRate int, cutoffHz float64) []float64 {
if cutoffHz <= 0 || cutoffHz >= float64(sampleRate)/2 {
return data
}
// Biquad coefficients
w0 := 2 * math.Pi * cutoffHz / float64(sampleRate)
cosw0 := math.Cos(w0)
sinw0 := math.Sin(w0)
Q := 1.0 / math.Sqrt(2) // Butterworth Q
alpha := sinw0 / (2 * Q)
b0 := (1 - cosw0) / 2
b1 := 1 - cosw0
b2 := (1 - cosw0) / 2
a0 := 1 + alpha
a1 := -2 * cosw0
a2 := 1 - alpha
// Normalize
b0 /= a0
b1 /= a0
b2 /= a0
a1 /= a0
a2 /= a0
// Apply filter (Direct Form I)
out := make([]float64, len(data))
var x1, x2, y1, y2 float64
for i := 0; i < len(data); i++ {
x0 := data[i]
y0 := b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2
out[i] = y0
x2 = x1
x1 = x0
y2 = y1
y1 = y0
}
return out
}
// ApplyHighpassButterworth applies a 2nd-order Butterworth high-pass filter to the data.
// cutoffHz: cutoff frequency in Hz, sampleRate: sample rate in Hz.
func ApplyHighpassButterworth(data []float64, sampleRate int, cutoffHz float64) []float64 {
if cutoffHz <= 0 || cutoffHz >= float64(sampleRate)/2 {
return data
}
// Biquad coefficients
w0 := 2 * math.Pi * cutoffHz / float64(sampleRate)
cosw0 := math.Cos(w0)
sinw0 := math.Sin(w0)
Q := 1.0 / math.Sqrt(2) // Butterworth Q
alpha := sinw0 / (2 * Q)
b0 := (1 + cosw0) / 2
b1 := -(1 + cosw0)
b2 := (1 + cosw0) / 2
a0 := 1 + alpha
a1 := -2 * cosw0
a2 := 1 - alpha
// Normalize
b0 /= a0
b1 /= a0
b2 /= a0
a1 /= a0
a2 /= a0
// Apply filter (Direct Form I)
out := make([]float64, len(data))
var x1, x2, y1, y2 float64
for i := 0; i < len(data); i++ {
x0 := data[i]
y0 := b0*x0 + b1*x1 + b2*x2 - a1*y1 - a2*y2
out[i] = y0
x2 = x1
x1 = x0
y2 = y1
y1 = y0
}
return out
}
// CascadeLowcut applies the low-cut (high-pass) filter multiple times for steeper slopes.
// slopeDb: 12, 24, 36, ... (dB/octave)
func CascadeLowcut(data []float64, sampleRate int, cutoffHz float64, slopeDb int) []float64 {
if slopeDb < 12 {
slopeDb = 12
}
n := slopeDb / 12
out := data
for i := 0; i < n; i++ {
out = ApplyHighpassButterworth(out, sampleRate, cutoffHz)
}
return out
}
// CascadeHighcut applies the high-cut (low-pass) filter multiple times for steeper slopes.
// slopeDb: 12, 24, 36, ... (dB/octave)
func CascadeHighcut(data []float64, sampleRate int, cutoffHz float64, slopeDb int) []float64 {
if slopeDb < 12 {
slopeDb = 12
}
n := slopeDb / 12
out := data
for i := 0; i < n; i++ {
out = ApplyLowpassButterworth(out, sampleRate, cutoffHz)
}
return out
}