stereo equalizer

plus examples of mono and stereo
This commit is contained in:
Craig Swank
2021-06-23 08:46:53 -06:00
parent a48355f4b6
commit 178d18fa91
3 changed files with 186 additions and 66 deletions

View File

@@ -16,11 +16,21 @@ type (
}
section struct {
a, b []float64
a, b [2][]float64
xPast, yPast [][2]float64
}
EqualizerSection struct {
// EqualizerSections is the interfacd that is passed into NewEqualizer
EqualizerSections interface {
sections(fs float64) []section
}
StereoEqualizerSection struct {
Left MonoEqualizerSection
Right MonoEqualizerSection
}
MonoEqualizerSection struct {
// F0 (center frequency) sets the mid-point of the sections
// frequency range and is given in Hertz [Hz].
F0 float64
@@ -52,10 +62,90 @@ type (
// cut (volume down) and positive numbers to boost (volume up).
G float64
}
// StereoEqualizerSections implements EqualizerSections and can be passed into NewEqualizer
StereoEqualizerSections []StereoEqualizerSection
// MonoEqualizerSections implements EqualizerSections and can be passed into NewEqualizer
MonoEqualizerSections []MonoEqualizerSection
)
func (s *section) apply(x [][2]float64) [][2]float64 {
ord := len(s.a) - 1
// NewEqualizer returns a beep.Streamer that modifies the stream based on the EqualizerSection slice that is passed in.
// The SampleRate (sr) must match that of the Streamer.
func NewEqualizer(st beep.Streamer, sr beep.SampleRate, s EqualizerSections) beep.Streamer {
return &equalizer{
streamer: st,
sections: s.sections(float64(sr)),
}
}
func (m MonoEqualizerSections) sections(fs float64) []section {
out := make([]section, len(m))
for i, s := range m {
out[i] = s.section(fs)
}
return out
}
func (m StereoEqualizerSections) sections(fs float64) []section {
out := make([]section, len(m))
for i, s := range m {
out[i] = s.section(fs)
}
return out
}
// Stream streams the wrapped Streamer modified by Equalizer.
func (e *equalizer) Stream(samples [][2]float64) (n int, ok bool) {
n, ok = e.streamer.Stream(samples)
for _, s := range e.sections {
s.apply(samples)
}
return n, ok
}
// Err propagates the wrapped Streamer's errors.
func (e *equalizer) Err() error {
return e.streamer.Err()
}
func (m MonoEqualizerSection) section(fs float64) section {
beta := math.Tan(m.Bf/2.0*math.Pi/(fs/2.0)) *
math.Sqrt(math.Abs(math.Pow(math.Pow(10, m.GB/20.0), 2.0)-
math.Pow(math.Pow(10.0, m.G0/20.0), 2.0))) /
math.Sqrt(math.Abs(math.Pow(math.Pow(10.0, m.G/20.0), 2.0)-
math.Pow(math.Pow(10.0, m.GB/20.0), 2.0)))
b := []float64{
(math.Pow(10.0, m.G0/20.0) + math.Pow(10.0, m.G/20.0)*beta) / (1 + beta),
(-2 * math.Pow(10.0, m.G0/20.0) * math.Cos(m.F0*math.Pi/(fs/2.0))) / (1 + beta),
(math.Pow(10.0, m.G0/20) - math.Pow(10.0, m.G/20.0)*beta) / (1 + beta),
}
a := []float64{
1.0,
-2 * math.Cos(m.F0*math.Pi/(fs/2.0)) / (1 + beta),
(1 - beta) / (1 + beta),
}
return section{
a: [2][]float64{a, a},
b: [2][]float64{b, b},
}
}
func (s StereoEqualizerSection) section(fs float64) section {
l := s.Left.section(fs)
r := s.Right.section(fs)
return section{
a: [2][]float64{l.a[0], r.a[0]},
b: [2][]float64{l.b[0], r.b[0]},
}
}
func (s *section) apply(x [][2]float64) {
ord := len(s.a[0]) - 1
np := len(x) - 1
if np < ord {
@@ -65,85 +155,40 @@ func (s *section) apply(x [][2]float64) [][2]float64 {
y := make([][2]float64, len(x))
if len(s.xPast) == 0 {
s.xPast = make([][2]float64, len(x))
if len(s.xPast) < len(x) {
s.xPast = append(s.xPast, make([][2]float64, len(x)-len(s.xPast))...)
}
if len(s.yPast) == 0 {
s.yPast = make([][2]float64, len(x))
if len(s.yPast) < len(x) {
s.yPast = append(s.yPast, make([][2]float64, len(x)-len(s.yPast))...)
}
for i := 0; i < len(x); i++ {
for j := 0; j < ord+1; j++ {
if i-j < 0 {
y[i][0] = y[i][0] + s.b[j]*s.xPast[len(s.xPast)-j][0]
y[i][1] = y[i][1] + s.b[j]*s.xPast[len(s.xPast)-j][1]
y[i][0] = y[i][0] + s.b[0][j]*s.xPast[len(s.xPast)-j][0]
y[i][1] = y[i][1] + s.b[1][j]*s.xPast[len(s.xPast)-j][1]
} else {
y[i][0] = y[i][0] + s.b[j]*x[i-j][0]
y[i][1] = y[i][1] + s.b[j]*x[i-j][1]
y[i][0] = y[i][0] + s.b[0][j]*x[i-j][0]
y[i][1] = y[i][1] + s.b[1][j]*x[i-j][1]
}
}
for j := 0; j < ord; j++ {
if i-j-1 < 0 {
y[i][0] = y[i][0] - s.a[j+1]*s.yPast[len(s.yPast)-j-1][0]
y[i][1] = y[i][1] - s.a[j+1]*s.yPast[len(s.yPast)-j-1][1]
y[i][0] = y[i][0] - s.a[0][j+1]*s.yPast[len(s.yPast)-j-1][0]
y[i][1] = y[i][1] - s.a[1][j+1]*s.yPast[len(s.yPast)-j-1][1]
} else {
y[i][0] = y[i][0] - s.a[j+1]*y[i-j-1][0]
y[i][1] = y[i][1] - s.a[j+1]*y[i-j-1][1]
y[i][0] = y[i][0] - s.a[0][j+1]*y[i-j-1][0]
y[i][1] = y[i][1] - s.a[1][j+1]*y[i-j-1][1]
}
}
y[i][0] = y[i][0] / s.a[0]
y[i][1] = y[i][1] / s.a[0]
y[i][0] = y[i][0] / s.a[0][0]
y[i][1] = y[i][1] / s.a[1][0]
}
s.xPast = x
s.yPast = y
return y
}
// NewEqualizer returns a beep.Streamer that modifies the stream based on the EqualizerSection slice that is passed in.
// The SampleRate (sr) must match that of the Streamer.
func NewEqualizer(s beep.Streamer, sr beep.SampleRate, sections []EqualizerSection) beep.Streamer {
fs := float64(sr)
out := &equalizer{
streamer: s,
}
for _, s := range sections {
beta := math.Tan(s.Bf/2.0*math.Pi/(fs/2.0)) *
math.Sqrt(math.Abs(math.Pow(math.Pow(10, s.GB/20.0), 2.0)-
math.Pow(math.Pow(10.0, s.G0/20.0), 2.0))) /
math.Sqrt(math.Abs(math.Pow(math.Pow(10.0, s.G/20.0), 2.0)-
math.Pow(math.Pow(10.0, s.GB/20.0), 2.0)))
b := []float64{
(math.Pow(10.0, s.G0/20.0) + math.Pow(10.0, s.G/20.0)*beta) / (1 + beta),
(-2 * math.Pow(10.0, s.G0/20.0) * math.Cos(s.F0*math.Pi/(fs/2.0))) / (1 + beta),
(math.Pow(10.0, s.G0/20) - math.Pow(10.0, s.G/20.0)*beta) / (1 + beta),
}
a := []float64{
1.0,
-2 * math.Cos(s.F0*math.Pi/(fs/2.0)) / (1 + beta),
(1 - beta) / (1 + beta),
}
out.sections = append(out.sections, section{a: a, b: b})
}
return out
}
// Stream streams the wrapped Streamer modified by Equalizer.
func (e *equalizer) Stream(samples [][2]float64) (n int, ok bool) {
n, ok = e.streamer.Stream(samples)
for _, s := range e.sections {
copy(samples, s.apply(samples))
}
return n, ok
}
// Err propagates the wrapped Streamer's errors.
func (e *equalizer) Err() error {
return e.streamer.Err()
s.xPast = x[:]
s.yPast = y[:]
copy(x, y)
}

View File

@@ -0,0 +1,36 @@
package main
import (
"math/rand"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/speaker"
)
func noise() beep.Streamer {
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
for i := range samples {
samples[i][0] = rand.Float64()*2 - 1
samples[i][1] = rand.Float64()*2 - 1
}
return len(samples), true
})
}
func main() {
sr := beep.SampleRate(44100)
speaker.Init(sr, sr.N(time.Second/10))
eq := effects.NewEqualizer(noise(), sr, effects.MonoEqualizerSections{
{F0: 200, Bf: 5, GB: 3, G0: 0, G: 8},
{F0: 250, Bf: 5, GB: 3, G0: 0, G: 10},
{F0: 300, Bf: 5, GB: 3, G0: 0, G: 12},
{F0: 350, Bf: 5, GB: 3, G0: 0, G: 14},
{F0: 10000, Bf: 8000, GB: 3, G0: 0, G: -100},
})
speaker.Play(eq)
select {}
}

View File

@@ -0,0 +1,39 @@
package main
import (
"math/rand"
"time"
"github.com/faiface/beep"
"github.com/faiface/beep/effects"
"github.com/faiface/beep/speaker"
)
func noise() beep.Streamer {
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
for i := range samples {
samples[i][0] = rand.Float64()*2 - 1
samples[i][1] = rand.Float64()*2 - 1
}
return len(samples), true
})
}
func main() {
sr := beep.SampleRate(44100)
speaker.Init(sr, sr.N(time.Second/10))
eq := effects.NewEqualizer(noise(), sr, effects.StereoEqualizerSections{
{
Left: effects.MonoEqualizerSection{F0: 200, Bf: 5, GB: 3, G0: 0, G: 8},
Right: effects.MonoEqualizerSection{F0: 200, Bf: 5, GB: 3, G0: 0, G: -8},
},
{
Left: effects.MonoEqualizerSection{F0: 10000, Bf: 1000, GB: 3, G0: 0, G: 90},
Right: effects.MonoEqualizerSection{F0: 10000, Bf: 1000, GB: 3, G0: 0, G: -90},
},
})
speaker.Play(eq)
select {}
}