88 lines
1.9 KiB
Go
88 lines
1.9 KiB
Go
package player
|
|
|
|
import (
|
|
"math"
|
|
"sync/atomic"
|
|
|
|
"github.com/gopxl/beep/v2"
|
|
)
|
|
|
|
// eqFreqs are the center frequencies for the 10-band parametric equalizer.
|
|
var eqFreqs = [10]float64{70, 180, 320, 600, 1000, 3000, 6000, 12000, 14000, 16000}
|
|
|
|
// biquad implements a second-order IIR peaking equalizer per the Audio EQ Cookbook.
|
|
// Each filter reads its gain from a shared pointer, so EQ changes take
|
|
// effect on the next Stream() call without rebuilding the pipeline.
|
|
type biquad struct {
|
|
s beep.Streamer
|
|
freq float64
|
|
q float64
|
|
gain *atomic.Uint64 // points to Player.eqBands[i], stores Float64bits
|
|
sr float64
|
|
// Per-channel filter state
|
|
x1, x2 [2]float64
|
|
y1, y2 [2]float64
|
|
// Cached coefficients
|
|
lastGain float64
|
|
b0, b1, b2, a1, a2 float64
|
|
inited bool
|
|
}
|
|
|
|
func newBiquad(s beep.Streamer, freq, q float64, gain *atomic.Uint64, sr float64) *biquad {
|
|
return &biquad{s: s, freq: freq, q: q, gain: gain, sr: sr}
|
|
}
|
|
|
|
func (b *biquad) calcCoeffs(dB float64) {
|
|
if b.inited && dB == b.lastGain {
|
|
return
|
|
}
|
|
b.lastGain = dB
|
|
b.inited = true
|
|
|
|
a := math.Pow(10, dB/40)
|
|
w0 := 2 * math.Pi * b.freq / b.sr
|
|
sinW0 := math.Sin(w0)
|
|
cosW0 := math.Cos(w0)
|
|
alpha := sinW0 / (2 * b.q)
|
|
|
|
b0 := 1 + alpha*a
|
|
b1 := -2 * cosW0
|
|
b2 := 1 - alpha*a
|
|
a0 := 1 + alpha/a
|
|
a1 := -2 * cosW0
|
|
a2 := 1 - alpha/a
|
|
|
|
b.b0 = b0 / a0
|
|
b.b1 = b1 / a0
|
|
b.b2 = b2 / a0
|
|
b.a1 = a1 / a0
|
|
b.a2 = a2 / a0
|
|
}
|
|
|
|
func (b *biquad) Stream(samples [][2]float64) (int, bool) {
|
|
n, ok := b.s.Stream(samples)
|
|
dB := math.Float64frombits(b.gain.Load())
|
|
|
|
// Skip processing when gain is effectively zero
|
|
if dB > -0.1 && dB < 0.1 {
|
|
return n, ok
|
|
}
|
|
|
|
b.calcCoeffs(dB)
|
|
|
|
for i := range n {
|
|
for ch := range 2 {
|
|
x := samples[i][ch]
|
|
y := b.b0*x + b.b1*b.x1[ch] + b.b2*b.x2[ch] - b.a1*b.y1[ch] - b.a2*b.y2[ch]
|
|
b.x2[ch] = b.x1[ch]
|
|
b.x1[ch] = x
|
|
b.y2[ch] = b.y1[ch]
|
|
b.y1[ch] = y
|
|
samples[i][ch] = y
|
|
}
|
|
}
|
|
return n, ok
|
|
}
|
|
|
|
func (b *biquad) Err() error { return b.s.Err() }
|