├── LICENSE ├── README.md ├── example ├── README.md └── main.go ├── go.mod └── pkg └── equalizer └── equalizer.go /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yoshiyuki Koyanagi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | go-equalizer 2 | ============ 3 | 4 | `go-equalizer` provides equalizers based on the Robert Bristow-Johnson's audio EQ cookbook. 5 | 6 | - [Cookbook formulae for audio EQ biquad filter coefficients](https://webaudio.github.io/Audio-EQ-Cookbook/audio-eq-cookbook.html) 7 | 8 | This package supports the following digital filters: 9 | 10 | - Low-pass 11 | - High-pass 12 | - All-pass 13 | - Band-pass 14 | - Band-reject 15 | - Low-shelf 16 | - High-shelf 17 | - Peaking 18 | 19 | ## Install 20 | 21 | ```console 22 | $ go get -u github.com/moutend/go-equalizer 23 | ``` 24 | 25 | ## Example 26 | 27 | See the `example` directory. 28 | 29 | ## Usage 30 | 31 | ```go 32 | package main 33 | 34 | import "github.com/moutend/go-equalizer/pkg/equalizer" 35 | 36 | func main() { 37 | // Audio signal 38 | input := []float64{ /* ... */ } 39 | output := make([]float64, len(input)) 40 | 41 | // Create the band-pass filter. 42 | bpf := equalizer.NewBandPass(44100, 440, 0.5) 43 | 44 | for i := range input { 45 | output[i] = bpf.Apply(input) 46 | } 47 | } 48 | ``` 49 | 50 | NOTE: `go-equalizer` does not provide the way to read the audio file as a float64 slice. 51 | 52 | ## LICENSE 53 | 54 | MIT 55 | 56 | ## Author 57 | 58 | [Yoshiyuki Koyanagi ](https://github.com/moutend/) 59 | -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- 1 | Applying Digital Filter 2 | ======================= 3 | 4 | This example does applying the band-pass filter to the audio signal. 5 | 6 | ## Required Tool 7 | 8 | - [go](https://golang.org/dl/) 9 | - [sox](http://sox.sourceforge.net) 10 | 11 | ## Usage 12 | 13 | Open the terminal app and run the following steps: 14 | 15 | ```console 16 | $ sox music.wav -t f64 input.raw 17 | $ go run main.go 18 | $ play -c 2 -r 44100 -t f64 output.raw 19 | ``` 20 | 21 | You hear the music that the band-pass filter applied. 22 | 23 | NOTE: the sample rate of the `music.wav` must be 44100 Hz and the channels must be stereo. 24 | -------------------------------------------------------------------------------- /example/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/binary" 5 | "io/ioutil" 6 | "math" 7 | 8 | "github.com/moutend/go-equalizer/pkg/equalizer" 9 | ) 10 | 11 | func main() { 12 | data, err := ioutil.ReadFile("input.raw") 13 | 14 | if err != nil { 15 | panic(err) 16 | } 17 | 18 | // f0 -> L channel / f1 -> R channel 19 | f0 := equalizer.NewBandPass(44100, 440, 0.5) 20 | f1 := equalizer.NewBandPass(44100, 440, 0.5) 21 | 22 | ch := 0 23 | bs := []byte{} 24 | 25 | for i := 0; i < len(data); i += 8 { 26 | input := math.Float64frombits( 27 | binary.LittleEndian.Uint64(data[i : i+8]), 28 | ) 29 | 30 | output := input 31 | 32 | if ch == 0 { 33 | output = f0.Apply(output) 34 | } else { 35 | output = f1.Apply(output) 36 | } 37 | 38 | ch = (ch + 1) % 2 39 | 40 | b := make([]byte, 8) 41 | binary.LittleEndian.PutUint64(b, math.Float64bits(output)) 42 | 43 | bs = append(bs, b...) 44 | } 45 | if err := ioutil.WriteFile("output.raw", bs, 0644); err != nil { 46 | panic(err) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/moutend/go-equalizer 2 | 3 | go 1.15 4 | -------------------------------------------------------------------------------- /pkg/equalizer/equalizer.go: -------------------------------------------------------------------------------- 1 | // Package equalizer provides equalizers based on the Robert Bristow-Johnson's audio EQ cookbook. 2 | // 3 | // This package supports the following digital filters: 4 | // 5 | // - Low-pass 6 | // - High-pass 7 | // - All-pass 8 | // - Band-pass 9 | // - Band-reject 10 | // - Low-shelf 11 | // - High-shelf 12 | // - Peaking 13 | package equalizer 14 | 15 | import "math" 16 | 17 | // Mode represents the kind of digital filters. 18 | type FilterName int 19 | 20 | // FilterName constants are digital filter names. 21 | const ( 22 | Undefined FilterName = iota 23 | LowPass 24 | HighPass 25 | AllPass 26 | BandPass 27 | BandReject 28 | LowShelf 29 | HighShelf 30 | Peaking 31 | ) 32 | 33 | // Pi value is used as the default pi value in this package. 34 | const Pi = 3.1415926535897932384626433 35 | 36 | var ( 37 | p = Pi 38 | ) 39 | 40 | // SetPi sets the pi value. After calling this function, call the constructor function such as NewLowPass(). 41 | func SetPi(value float64) { 42 | p = value 43 | } 44 | 45 | // UnsetPi sets the pi value to default value. 46 | func UnsetPi() { 47 | p = Pi 48 | } 49 | 50 | // Filter holds the digital filter parameters. 51 | type Filter struct { 52 | name FilterName 53 | 54 | // state variables 55 | in1 float64 56 | in2 float64 57 | out1 float64 58 | out2 float64 59 | 60 | // digital filter parameters 61 | a0 float64 62 | a1 float64 63 | a2 float64 64 | b0 float64 65 | b1 float64 66 | b2 float64 67 | } 68 | 69 | // IsZero returns true when the f is not initialized. 70 | func (f *Filter) IsZero() bool { 71 | return f.name == Undefined 72 | } 73 | 74 | // FilterName returns filter name. 75 | func (f *Filter) Name() FilterName { 76 | return f.name 77 | } 78 | 79 | // Apply applies the current filter and returns the value. 80 | func (f *Filter) Apply(input float64) float64 { 81 | output := (f.b0/f.a0)*input + 82 | (f.b1/f.a0)*f.in1 + 83 | (f.b2/f.a0)*f.in2 - 84 | (f.a1/f.a0)*f.out1 - 85 | (f.a2/f.a0)*f.out2 86 | 87 | f.in2 = f.in1 88 | f.in1 = input 89 | 90 | f.out2 = f.out1 91 | f.out1 = output 92 | 93 | return output 94 | } 95 | 96 | // NewLowPass returns the low-pass filter. 97 | // 98 | // Parameters: 99 | // 100 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 101 | // - frequency ... Cut off frequency in Hz. 102 | // - q ... Q value. 103 | // 104 | // NOTE: q must be greater than 0. 105 | func NewLowPass(sampleRate, frequency, q float64) *Filter { 106 | w0 := 2.0 * p * frequency / sampleRate 107 | alpha := math.Sin(w0) / (2.0 * q) 108 | 109 | return &Filter{ 110 | name: LowPass, 111 | a0: 1.0 + alpha, 112 | a1: -2.0 * math.Cos(w0), 113 | a2: 1.0 - alpha, 114 | b0: (1.0 - math.Cos(w0)) / 2.0, 115 | b1: 1.0 - math.Cos(w0), 116 | b2: (1.0 - math.Cos(w0)) / 2.0, 117 | } 118 | } 119 | 120 | // NewHighPass returns the high-pass filter. 121 | // 122 | // Parameters: 123 | // 124 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 125 | // - frequency ... Cut off frequency in Hz. 126 | // - q ... Q value. 127 | // 128 | // NOTE: q must be greater than 0. 129 | func NewHighPass(sampleRate, frequency, q float64) *Filter { 130 | w0 := 2.0 * p * frequency / sampleRate 131 | alpha := math.Sin(w0) / (2.0 * q) 132 | 133 | return &Filter{ 134 | name: HighPass, 135 | a0: 1.0 + alpha, 136 | a1: -2.0 * math.Cos(w0), 137 | a2: 1.0 - alpha, 138 | b0: (1.0 + math.Cos(w0)) / 2.0, 139 | b1: -1.0 * (1.0 + math.Cos(w0)), 140 | b2: (1.0 + math.Cos(w0)) / 2.0, 141 | } 142 | } 143 | 144 | // NewAllPass returns the all-pass filter. 145 | // 146 | // Parameters: 147 | // 148 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 149 | // - frequency ... Cut off frequency in Hz. 150 | // - q ... Q value. 151 | // 152 | // NOTE: q must be greater than 0. 153 | func NewAllPass(sampleRate, frequency, q float64) *Filter { 154 | w0 := 2.0 * p * frequency / sampleRate 155 | alpha := math.Sin(w0) / (2.0 * q) 156 | 157 | return &Filter{ 158 | name: AllPass, 159 | a0: 1.0 + alpha, 160 | a1: -2.0 * math.Cos(w0), 161 | a2: 1.0 - alpha, 162 | b0: 1.0 - alpha, 163 | b1: -2.0 * math.Cos(w0), 164 | b2: 1.0 + alpha, 165 | } 166 | } 167 | 168 | // NewBandPass returns the band-pass filter. 169 | // 170 | // Parameters: 171 | // 172 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 173 | // - frequency ... Cut off frequency in Hz. 174 | // - width ... Band width. 175 | // 176 | // NOTE: width must be greater than 0. 177 | func NewBandPass(sampleRate, frequency, width float64) *Filter { 178 | w0 := 2.0 * p * frequency / sampleRate 179 | alpha := math.Sin(w0) * math.Sinh(math.Log(2.0)/2.0*width*w0/math.Sin(w0)) 180 | 181 | return &Filter{ 182 | name: BandPass, 183 | a0: 1.0 + alpha, 184 | a1: -2.0 * math.Cos(w0), 185 | a2: 1.0 - alpha, 186 | b0: alpha, 187 | b1: 0.0, 188 | b2: -1.0 * alpha, 189 | } 190 | } 191 | 192 | // NewBandReject returns the band-reject filter. 193 | // 194 | // Parameters: 195 | // 196 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 197 | // - frequency ... Cut off frequency in Hz. 198 | // - width ... Band width. 199 | // 200 | // NOTE: width must be greater than 0. 201 | func NewBandReject(sampleRate, frequency, width float64) *Filter { 202 | w0 := 2.0 * p * frequency / sampleRate 203 | alpha := math.Sin(w0) * math.Sinh(math.Log(2.0)/2.0*width*w0/math.Sin(w0)) 204 | 205 | return &Filter{ 206 | name: BandReject, 207 | a0: 1.0 + alpha, 208 | a1: -2.0 * math.Cos(w0), 209 | a2: 1.0 - alpha, 210 | b0: 1.0, 211 | b1: -2.0 * math.Cos(w0), 212 | b2: 1.0, 213 | } 214 | } 215 | 216 | // NewLowShelf returns the low-shelf filter. 217 | // 218 | // Parameters: 219 | // 220 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 221 | // - frequency ... Cut off frequency in Hz. 222 | // - q ... Q value. 223 | // - gain ... Gain value in dB. 224 | // 225 | // NOTE: q must be greater than 0. 226 | func NewLowShelf(sampleRate, frequency, q, gain float64) *Filter { 227 | w0 := 2.0 * p * frequency / sampleRate 228 | a := math.Pow(10.0, (gain / 40.0)) 229 | beta := math.Sqrt(a) / q 230 | 231 | return &Filter{ 232 | name: LowShelf, 233 | a0: (a + 1.0) + (a-1.0)*math.Cos(w0) + beta*math.Sin(w0), 234 | a1: -2.0 * ((a - 1.0) + (a+1.0)*math.Cos(w0)), 235 | a2: (a + 1.0) + (a-1.0)*math.Cos(w0) - beta*math.Sin(w0), 236 | b0: a * ((a + 1.0) - (a-1.0)*math.Cos(w0) + beta*math.Sin(w0)), 237 | b1: 2.0 * a * ((a - 1.0) - (a+1.0)*math.Cos(w0)), 238 | b2: a * ((a + 1.0) - (a-1.0)*math.Cos(w0) - beta*math.Sin(w0)), 239 | } 240 | } 241 | 242 | // NewHighShelf returns the high-shelf filter. 243 | // 244 | // Parameters: 245 | // 246 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 247 | // - frequency ... Cut off frequency in Hz. 248 | // - q ... Q value. 249 | // - gain ... Gain value in dB. 250 | // 251 | // NOTE: q must be greater than 0. 252 | func NewHighShelf(sampleRate, frequency, q, gain float64) *Filter { 253 | w0 := 2.0 * p * frequency / sampleRate 254 | a := math.Pow(10.0, (gain / 40.0)) 255 | beta := math.Sqrt(a) / q 256 | 257 | return &Filter{ 258 | name: HighShelf, 259 | a0: (a + 1.0) - (a-1.0)*math.Cos(w0) + beta*math.Sin(w0), 260 | a1: 2.0 * ((a - 1.0) - (a+1.0)*math.Cos(w0)), 261 | a2: (a + 1.0) - (a-1.0)*math.Cos(w0) - beta*math.Sin(w0), 262 | b0: a * ((a + 1.0) + (a-1.0)*math.Cos(w0) + beta*math.Sin(w0)), 263 | b1: -2.0 * a * ((a - 1.0) + (a+1.0)*math.Cos(w0)), 264 | b2: a * ((a + 1.0) + (a-1.0)*math.Cos(w0) - beta*math.Sin(w0)), 265 | } 266 | } 267 | 268 | // NewPeaking returns the peaking-shelf filter. 269 | // 270 | // Parameters: 271 | // 272 | // - sampleRate ... sample rate in Hz. e.g. 44100.0 273 | // - frequency ... Cut off frequency in Hz. 274 | // - width ... Width value. 275 | // - gain ... Gain value in dB. 276 | // 277 | // NOTE: width must be greater than 0. 278 | func NewPeaking(sampleRate, frequency, width, gain float64) *Filter { 279 | w0 := 2.0 * p * frequency / sampleRate 280 | alpha := math.Sin(w0) * math.Sinh(math.Log(2.0)/2.0*width*w0/math.Sin(w0)) 281 | a := math.Pow(10.0, (gain / 40.0)) 282 | 283 | return &Filter{ 284 | name: Peaking, 285 | a0: 1.0 + alpha/a, 286 | a1: -2.0 * math.Cos(w0), 287 | a2: 1.0 - alpha/a, 288 | b0: 1.0 + alpha*a, 289 | b1: -2.0 * math.Cos(w0), 290 | b2: 1.0 - alpha*a, 291 | } 292 | } 293 | --------------------------------------------------------------------------------