mirror of
https://github.com/faiface/beep.git
synced 2025-12-23 23:38:45 -05:00
stereo equalizer
plus examples of mono and stereo
This commit is contained in:
@@ -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 section’s
|
||||
// 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)
|
||||
}
|
||||
|
||||
36
examples/tutorial/5-equalizer/mono/main.go
Normal file
36
examples/tutorial/5-equalizer/mono/main.go
Normal 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 {}
|
||||
}
|
||||
39
examples/tutorial/5-equalizer/stereo/main.go
Normal file
39
examples/tutorial/5-equalizer/stereo/main.go
Normal 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 {}
|
||||
}
|
||||
Reference in New Issue
Block a user