-
Notifications
You must be signed in to change notification settings - Fork 151
/
equalizer.go
194 lines (160 loc) · 5.23 KB
/
equalizer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package effects
import (
"math"
"github.com/faiface/beep"
)
type (
// This parametric equalizer is based on the GK Nilsen's post at:
// https://octovoid.com/2017/11/04/coding-a-parametric-equalizer-for-audio-applications/
equalizer struct {
streamer beep.Streamer
sections []section
}
section struct {
a, b [2][]float64
xPast, yPast [][2]float64
}
// 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 section’s
// frequency range and is given in Hertz [Hz].
F0 float64
// Bf (bandwidth) represents the width of the section across
// frequency and is measured in Hertz [Hz]. A low bandwidth
// corresponds to a narrow frequency range meaning that the
// section will concentrate its operation to only the
// frequencies close to the center frequency. On the other hand,
// a high bandwidth yields a section of wide frequency range —
// affecting a broader range of frequencies surrounding the
// center frequency.
Bf float64
// GB (bandwidth gain) is given in decibels [dB] and represents
// the level at which the bandwidth is measured. That is, to
// have a meaningful measure of bandwidth, we must define the
// level at which it is measured.
GB float64
// G0 (reference gain) is given in decibels [dB] and simply
// represents the level of the section’s offset.
G0 float64
// G (boost/cut gain) is given in decibels [dB] and prescribes
// the effect imposed on the audio loudness for the section’s
// frequency range. A boost/cut level of 0 dB corresponds to
// unity (no operation), whereas negative numbers corresponds to
// 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
)
// 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 {
x = append(x, make([][2]float64, ord-np)...)
np = ord
}
y := 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) < 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[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[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[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[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][0]
y[i][1] = y[i][1] / s.a[1][0]
}
s.xPast = x[:]
s.yPast = y[:]
copy(x, y)
}