94 lines
2.2 KiB
Go
94 lines
2.2 KiB
Go
package pmostream
|
|
|
|
import (
|
|
"math"
|
|
"sync"
|
|
|
|
"github.com/gopxl/beep"
|
|
)
|
|
|
|
// ParametricEQ applique un égaliseur paramétrique stéréo (Biquad peaking) sur un Streamer.
|
|
type ParametricEQ struct {
|
|
input beep.Streamer
|
|
sampleRate float64
|
|
mu sync.Mutex
|
|
|
|
// Coefficients Biquad
|
|
a0, a1, a2, b1, b2 float64
|
|
|
|
// États pour les deux canaux
|
|
x1, x2, y1, y2 [2]float64
|
|
}
|
|
|
|
// EQParams définit les paramètres d'un filtre peaking
|
|
type EQParams struct {
|
|
FreqHz float64 // fréquence centrale en Hz
|
|
GainDB float64 // gain en dB
|
|
Q float64 // facteur de qualité
|
|
}
|
|
|
|
// NewParametricEQ construit un EQ peaking stéréo en interrogeant le streamer pour la fréquence
|
|
func NewParametricEQ(input beep.Streamer, params EQParams, sr beep.SampleRate) *ParametricEQ {
|
|
eq := &ParametricEQ{
|
|
input: input,
|
|
sampleRate: float64(sr),
|
|
}
|
|
|
|
eq.setParams(params)
|
|
return eq
|
|
}
|
|
|
|
// setParams calcule les coefficients du Biquad
|
|
func (eq *ParametricEQ) setParams(p EQParams) {
|
|
eq.mu.Lock()
|
|
defer eq.mu.Unlock()
|
|
|
|
A := math.Pow(10, p.GainDB/40) // conversion dB -> amplitude
|
|
w0 := 2 * math.Pi * p.FreqHz / eq.sampleRate
|
|
alpha := math.Sin(w0) / (2 * p.Q)
|
|
|
|
a0 := 1 + alpha/A
|
|
eq.a0 = 1
|
|
eq.a1 = -2 * math.Cos(w0) / a0
|
|
eq.a2 = (1 - alpha/A) / a0
|
|
eq.b1 = 2 * math.Cos(w0) * -1 / a0
|
|
eq.b2 = (1 - alpha*A) / a0
|
|
}
|
|
|
|
// Stream applique l'EQ sur un chunk stéréo float64 [][2]float64
|
|
func (eq *ParametricEQ) Stream(samples [][2]float64) (n int, ok bool) {
|
|
eq.mu.Lock()
|
|
defer eq.mu.Unlock()
|
|
|
|
if len(samples) == 0 {
|
|
return 0, true
|
|
}
|
|
|
|
for i := range samples {
|
|
for ch := 0; ch < 2; ch++ {
|
|
x := samples[i][ch]
|
|
y := eq.a0*x + eq.a1*eq.x1[ch] + eq.a2*eq.x2[ch] - eq.b1*eq.y1[ch] - eq.b2*eq.y2[ch]
|
|
|
|
eq.x2[ch] = eq.x1[ch]
|
|
eq.x1[ch] = x
|
|
eq.y2[ch] = eq.y1[ch]
|
|
eq.y1[ch] = y
|
|
|
|
samples[i][ch] = y
|
|
}
|
|
}
|
|
return len(samples), true
|
|
}
|
|
|
|
// Close libère les ressources (pas nécessaire ici mais pour interface uniforme)
|
|
func (eq *ParametricEQ) Close() error {
|
|
return nil
|
|
}
|
|
|
|
// Wrap permet d'utiliser ParametricEQ comme beep.Streamer
|
|
func (eq *ParametricEQ) Streamer() beep.Streamer {
|
|
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
|
|
return eq.Stream(samples)
|
|
})
|
|
}
|