Skip to content

Commit 178d18f

Browse files
Craig SwankCraig Swank
Craig Swank
authored and
Craig Swank
committedJun 23, 2021
stereo equalizer
plus examples of mono and stereo
1 parent a48355f commit 178d18f

File tree

3 files changed

+186
-66
lines changed

3 files changed

+186
-66
lines changed
 

‎effects/equalizer.go

+111-66
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,21 @@ type (
1616
}
1717

1818
section struct {
19-
a, b []float64
19+
a, b [2][]float64
2020
xPast, yPast [][2]float64
2121
}
2222

23-
EqualizerSection struct {
23+
// EqualizerSections is the interfacd that is passed into NewEqualizer
24+
EqualizerSections interface {
25+
sections(fs float64) []section
26+
}
27+
28+
StereoEqualizerSection struct {
29+
Left MonoEqualizerSection
30+
Right MonoEqualizerSection
31+
}
32+
33+
MonoEqualizerSection struct {
2434
// F0 (center frequency) sets the mid-point of the section’s
2535
// frequency range and is given in Hertz [Hz].
2636
F0 float64
@@ -52,10 +62,90 @@ type (
5262
// cut (volume down) and positive numbers to boost (volume up).
5363
G float64
5464
}
65+
66+
// StereoEqualizerSections implements EqualizerSections and can be passed into NewEqualizer
67+
StereoEqualizerSections []StereoEqualizerSection
68+
69+
// MonoEqualizerSections implements EqualizerSections and can be passed into NewEqualizer
70+
MonoEqualizerSections []MonoEqualizerSection
5571
)
5672

57-
func (s *section) apply(x [][2]float64) [][2]float64 {
58-
ord := len(s.a) - 1
73+
// NewEqualizer returns a beep.Streamer that modifies the stream based on the EqualizerSection slice that is passed in.
74+
// The SampleRate (sr) must match that of the Streamer.
75+
func NewEqualizer(st beep.Streamer, sr beep.SampleRate, s EqualizerSections) beep.Streamer {
76+
return &equalizer{
77+
streamer: st,
78+
sections: s.sections(float64(sr)),
79+
}
80+
}
81+
82+
func (m MonoEqualizerSections) sections(fs float64) []section {
83+
out := make([]section, len(m))
84+
for i, s := range m {
85+
out[i] = s.section(fs)
86+
}
87+
return out
88+
}
89+
90+
func (m StereoEqualizerSections) sections(fs float64) []section {
91+
out := make([]section, len(m))
92+
for i, s := range m {
93+
out[i] = s.section(fs)
94+
}
95+
return out
96+
}
97+
98+
// Stream streams the wrapped Streamer modified by Equalizer.
99+
func (e *equalizer) Stream(samples [][2]float64) (n int, ok bool) {
100+
n, ok = e.streamer.Stream(samples)
101+
for _, s := range e.sections {
102+
s.apply(samples)
103+
}
104+
return n, ok
105+
}
106+
107+
// Err propagates the wrapped Streamer's errors.
108+
func (e *equalizer) Err() error {
109+
return e.streamer.Err()
110+
}
111+
112+
func (m MonoEqualizerSection) section(fs float64) section {
113+
beta := math.Tan(m.Bf/2.0*math.Pi/(fs/2.0)) *
114+
math.Sqrt(math.Abs(math.Pow(math.Pow(10, m.GB/20.0), 2.0)-
115+
math.Pow(math.Pow(10.0, m.G0/20.0), 2.0))) /
116+
math.Sqrt(math.Abs(math.Pow(math.Pow(10.0, m.G/20.0), 2.0)-
117+
math.Pow(math.Pow(10.0, m.GB/20.0), 2.0)))
118+
119+
b := []float64{
120+
(math.Pow(10.0, m.G0/20.0) + math.Pow(10.0, m.G/20.0)*beta) / (1 + beta),
121+
(-2 * math.Pow(10.0, m.G0/20.0) * math.Cos(m.F0*math.Pi/(fs/2.0))) / (1 + beta),
122+
(math.Pow(10.0, m.G0/20) - math.Pow(10.0, m.G/20.0)*beta) / (1 + beta),
123+
}
124+
125+
a := []float64{
126+
1.0,
127+
-2 * math.Cos(m.F0*math.Pi/(fs/2.0)) / (1 + beta),
128+
(1 - beta) / (1 + beta),
129+
}
130+
131+
return section{
132+
a: [2][]float64{a, a},
133+
b: [2][]float64{b, b},
134+
}
135+
}
136+
137+
func (s StereoEqualizerSection) section(fs float64) section {
138+
l := s.Left.section(fs)
139+
r := s.Right.section(fs)
140+
141+
return section{
142+
a: [2][]float64{l.a[0], r.a[0]},
143+
b: [2][]float64{l.b[0], r.b[0]},
144+
}
145+
}
146+
147+
func (s *section) apply(x [][2]float64) {
148+
ord := len(s.a[0]) - 1
59149
np := len(x) - 1
60150

61151
if np < ord {
@@ -65,85 +155,40 @@ func (s *section) apply(x [][2]float64) [][2]float64 {
65155

66156
y := make([][2]float64, len(x))
67157

68-
if len(s.xPast) == 0 {
69-
s.xPast = make([][2]float64, len(x))
158+
if len(s.xPast) < len(x) {
159+
s.xPast = append(s.xPast, make([][2]float64, len(x)-len(s.xPast))...)
70160
}
71161

72-
if len(s.yPast) == 0 {
73-
s.yPast = make([][2]float64, len(x))
162+
if len(s.yPast) < len(x) {
163+
s.yPast = append(s.yPast, make([][2]float64, len(x)-len(s.yPast))...)
74164
}
75165

76166
for i := 0; i < len(x); i++ {
77167
for j := 0; j < ord+1; j++ {
78168
if i-j < 0 {
79-
y[i][0] = y[i][0] + s.b[j]*s.xPast[len(s.xPast)-j][0]
80-
y[i][1] = y[i][1] + s.b[j]*s.xPast[len(s.xPast)-j][1]
169+
y[i][0] = y[i][0] + s.b[0][j]*s.xPast[len(s.xPast)-j][0]
170+
y[i][1] = y[i][1] + s.b[1][j]*s.xPast[len(s.xPast)-j][1]
81171
} else {
82-
y[i][0] = y[i][0] + s.b[j]*x[i-j][0]
83-
y[i][1] = y[i][1] + s.b[j]*x[i-j][1]
172+
y[i][0] = y[i][0] + s.b[0][j]*x[i-j][0]
173+
y[i][1] = y[i][1] + s.b[1][j]*x[i-j][1]
84174
}
85175
}
86176

87177
for j := 0; j < ord; j++ {
88178
if i-j-1 < 0 {
89-
y[i][0] = y[i][0] - s.a[j+1]*s.yPast[len(s.yPast)-j-1][0]
90-
y[i][1] = y[i][1] - s.a[j+1]*s.yPast[len(s.yPast)-j-1][1]
179+
y[i][0] = y[i][0] - s.a[0][j+1]*s.yPast[len(s.yPast)-j-1][0]
180+
y[i][1] = y[i][1] - s.a[1][j+1]*s.yPast[len(s.yPast)-j-1][1]
91181
} else {
92-
y[i][0] = y[i][0] - s.a[j+1]*y[i-j-1][0]
93-
y[i][1] = y[i][1] - s.a[j+1]*y[i-j-1][1]
182+
y[i][0] = y[i][0] - s.a[0][j+1]*y[i-j-1][0]
183+
y[i][1] = y[i][1] - s.a[1][j+1]*y[i-j-1][1]
94184
}
95185
}
96186

97-
y[i][0] = y[i][0] / s.a[0]
98-
y[i][1] = y[i][1] / s.a[0]
99-
}
100-
101-
s.xPast = x
102-
s.yPast = y
103-
return y
104-
}
105-
106-
// NewEqualizer returns a beep.Streamer that modifies the stream based on the EqualizerSection slice that is passed in.
107-
// The SampleRate (sr) must match that of the Streamer.
108-
func NewEqualizer(s beep.Streamer, sr beep.SampleRate, sections []EqualizerSection) beep.Streamer {
109-
fs := float64(sr)
110-
out := &equalizer{
111-
streamer: s,
112-
}
113-
114-
for _, s := range sections {
115-
beta := math.Tan(s.Bf/2.0*math.Pi/(fs/2.0)) *
116-
math.Sqrt(math.Abs(math.Pow(math.Pow(10, s.GB/20.0), 2.0)-
117-
math.Pow(math.Pow(10.0, s.G0/20.0), 2.0))) /
118-
math.Sqrt(math.Abs(math.Pow(math.Pow(10.0, s.G/20.0), 2.0)-
119-
math.Pow(math.Pow(10.0, s.GB/20.0), 2.0)))
120-
121-
b := []float64{
122-
(math.Pow(10.0, s.G0/20.0) + math.Pow(10.0, s.G/20.0)*beta) / (1 + beta),
123-
(-2 * math.Pow(10.0, s.G0/20.0) * math.Cos(s.F0*math.Pi/(fs/2.0))) / (1 + beta),
124-
(math.Pow(10.0, s.G0/20) - math.Pow(10.0, s.G/20.0)*beta) / (1 + beta),
125-
}
126-
127-
a := []float64{
128-
1.0,
129-
-2 * math.Cos(s.F0*math.Pi/(fs/2.0)) / (1 + beta),
130-
(1 - beta) / (1 + beta),
131-
}
132-
out.sections = append(out.sections, section{a: a, b: b})
133-
}
134-
return out
135-
}
136-
137-
// Stream streams the wrapped Streamer modified by Equalizer.
138-
func (e *equalizer) Stream(samples [][2]float64) (n int, ok bool) {
139-
n, ok = e.streamer.Stream(samples)
140-
for _, s := range e.sections {
141-
copy(samples, s.apply(samples))
187+
y[i][0] = y[i][0] / s.a[0][0]
188+
y[i][1] = y[i][1] / s.a[1][0]
142189
}
143-
return n, ok
144-
}
145190

146-
// Err propagates the wrapped Streamer's errors.
147-
func (e *equalizer) Err() error {
148-
return e.streamer.Err()
191+
s.xPast = x[:]
192+
s.yPast = y[:]
193+
copy(x, y)
149194
}
+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"math/rand"
5+
"time"
6+
7+
"github.com/faiface/beep"
8+
"github.com/faiface/beep/effects"
9+
"github.com/faiface/beep/speaker"
10+
)
11+
12+
func noise() beep.Streamer {
13+
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
14+
for i := range samples {
15+
samples[i][0] = rand.Float64()*2 - 1
16+
samples[i][1] = rand.Float64()*2 - 1
17+
}
18+
return len(samples), true
19+
})
20+
}
21+
22+
func main() {
23+
sr := beep.SampleRate(44100)
24+
speaker.Init(sr, sr.N(time.Second/10))
25+
26+
eq := effects.NewEqualizer(noise(), sr, effects.MonoEqualizerSections{
27+
{F0: 200, Bf: 5, GB: 3, G0: 0, G: 8},
28+
{F0: 250, Bf: 5, GB: 3, G0: 0, G: 10},
29+
{F0: 300, Bf: 5, GB: 3, G0: 0, G: 12},
30+
{F0: 350, Bf: 5, GB: 3, G0: 0, G: 14},
31+
{F0: 10000, Bf: 8000, GB: 3, G0: 0, G: -100},
32+
})
33+
34+
speaker.Play(eq)
35+
select {}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package main
2+
3+
import (
4+
"math/rand"
5+
"time"
6+
7+
"github.com/faiface/beep"
8+
"github.com/faiface/beep/effects"
9+
"github.com/faiface/beep/speaker"
10+
)
11+
12+
func noise() beep.Streamer {
13+
return beep.StreamerFunc(func(samples [][2]float64) (n int, ok bool) {
14+
for i := range samples {
15+
samples[i][0] = rand.Float64()*2 - 1
16+
samples[i][1] = rand.Float64()*2 - 1
17+
}
18+
return len(samples), true
19+
})
20+
}
21+
22+
func main() {
23+
sr := beep.SampleRate(44100)
24+
speaker.Init(sr, sr.N(time.Second/10))
25+
26+
eq := effects.NewEqualizer(noise(), sr, effects.StereoEqualizerSections{
27+
{
28+
Left: effects.MonoEqualizerSection{F0: 200, Bf: 5, GB: 3, G0: 0, G: 8},
29+
Right: effects.MonoEqualizerSection{F0: 200, Bf: 5, GB: 3, G0: 0, G: -8},
30+
},
31+
{
32+
Left: effects.MonoEqualizerSection{F0: 10000, Bf: 1000, GB: 3, G0: 0, G: 90},
33+
Right: effects.MonoEqualizerSection{F0: 10000, Bf: 1000, GB: 3, G0: 0, G: -90},
34+
},
35+
})
36+
37+
speaker.Play(eq)
38+
select {}
39+
}

0 commit comments

Comments
 (0)
Please sign in to comment.