From 178d18fa91aba964140151d3868f9f4d9502bf23 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 23 Jun 2021 08:46:53 -0600 Subject: [PATCH] stereo equalizer plus examples of mono and stereo --- effects/equalizer.go | 177 ++++++++++++------- examples/tutorial/5-equalizer/mono/main.go | 36 ++++ examples/tutorial/5-equalizer/stereo/main.go | 39 ++++ 3 files changed, 186 insertions(+), 66 deletions(-) create mode 100644 examples/tutorial/5-equalizer/mono/main.go create mode 100644 examples/tutorial/5-equalizer/stereo/main.go diff --git a/effects/equalizer.go b/effects/equalizer.go index 58c52ae..cf95073 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -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) } diff --git a/examples/tutorial/5-equalizer/mono/main.go b/examples/tutorial/5-equalizer/mono/main.go new file mode 100644 index 0000000..34977df --- /dev/null +++ b/examples/tutorial/5-equalizer/mono/main.go @@ -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 {} +} diff --git a/examples/tutorial/5-equalizer/stereo/main.go b/examples/tutorial/5-equalizer/stereo/main.go new file mode 100644 index 0000000..db37ed7 --- /dev/null +++ b/examples/tutorial/5-equalizer/stereo/main.go @@ -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 {} +}