From b913b006cda5949724958fe66831c0ba393a566d Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 08:29:14 -0600 Subject: [PATCH 01/10] added a parametric equalizer based on https://octovoid.com/2017/11/04/coding-a-parametric-equalizer-for-audio-applications/ --- effects/equalizer.go | 115 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 115 insertions(+) create mode 100644 effects/equalizer.go diff --git a/effects/equalizer.go b/effects/equalizer.go new file mode 100644 index 0000000..d9fd2ec --- /dev/null +++ b/effects/equalizer.go @@ -0,0 +1,115 @@ +package effects + +import ( + "math" + + "github.com/faiface/beep" +) + +// https://octovoid.com/2017/11/04/coding-a-parametric-equalizer-for-audio-applications/ +type ( + section struct { + a, b []float64 + xPast, yPast [][2]float64 + } + + Section struct { + F0, Bf, GB, G0, G float64 + } + + Equalizer struct { + streamer beep.Streamer + sections []section + } +) + +func (s *section) apply(x [][2]float64) [][2]float64 { + ord := len(s.a) - 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) == 0 { + s.xPast = make([][2]float64, len(x)) + } + + if len(s.yPast) == 0 { + s.yPast = make([][2]float64, len(x)) + } + + 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] + } 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] + } + } + + 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] + } 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] + y[i][1] = y[i][1] / s.a[0] + } + } + + s.xPast = x + s.yPast = y + return y +} + +func NewEqualizer(s beep.Streamer, fs float64, filters []Section) *Equalizer { + out := &Equalizer{ + streamer: s, + } + + for _, f := range filters { + beta := math.Tan(f.Bf/2.0*math.Pi/(fs/2.0)) * + math.Sqrt(math.Abs(math.Pow(math.Pow(10, f.GB/20.0), 2.0)- + math.Pow(math.Pow(10.0, f.G0/20.0), 2.0))) / + math.Sqrt(math.Abs(math.Pow(math.Pow(10.0, f.G/20.0), 2.0)- + math.Pow(math.Pow(10.0, f.GB/20.0), 2.0))) + + b := []float64{ + (math.Pow(10.0, f.G0/20.0) + math.Pow(10.0, f.G/20.0)*beta) / (1 + beta), + (-2 * math.Pow(10.0, f.G0/20.0) * math.Cos(f.F0*math.Pi/(fs/2.0))) / (1 + beta), + (math.Pow(10.0, f.G0/20) - math.Pow(10.0, f.G/20.0)*beta) / (1 + beta), + } + + a := []float64{ + 1.0, + -2 * math.Cos(f.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() +} From 0a977a2d5e47e1df2cff1d0da5ddb709e4e14fae Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 08:45:03 -0600 Subject: [PATCH 02/10] return beep.Streamer and rename some vars --- effects/equalizer.go | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index d9fd2ec..1b3590e 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -13,7 +13,7 @@ type ( xPast, yPast [][2]float64 } - Section struct { + EqualizerSection struct { F0, Bf, GB, G0, G float64 } @@ -72,27 +72,27 @@ func (s *section) apply(x [][2]float64) [][2]float64 { return y } -func NewEqualizer(s beep.Streamer, fs float64, filters []Section) *Equalizer { +func NewEqualizer(s beep.Streamer, fs float64, sections []EqualizerSection) beep.Streamer { out := &Equalizer{ streamer: s, } - for _, f := range filters { - beta := math.Tan(f.Bf/2.0*math.Pi/(fs/2.0)) * - math.Sqrt(math.Abs(math.Pow(math.Pow(10, f.GB/20.0), 2.0)- - math.Pow(math.Pow(10.0, f.G0/20.0), 2.0))) / - math.Sqrt(math.Abs(math.Pow(math.Pow(10.0, f.G/20.0), 2.0)- - math.Pow(math.Pow(10.0, f.GB/20.0), 2.0))) + 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, f.G0/20.0) + math.Pow(10.0, f.G/20.0)*beta) / (1 + beta), - (-2 * math.Pow(10.0, f.G0/20.0) * math.Cos(f.F0*math.Pi/(fs/2.0))) / (1 + beta), - (math.Pow(10.0, f.G0/20) - math.Pow(10.0, f.G/20.0)*beta) / (1 + beta), + (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(f.F0*math.Pi/(fs/2.0)) / (1 + beta), + -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}) From de33a5ef1d709e7d7b4380143f7faaa98f2f0c50 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 08:46:41 -0600 Subject: [PATCH 03/10] Equalizer -> equalizer --- effects/equalizer.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index 1b3590e..46c2688 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -17,7 +17,7 @@ type ( F0, Bf, GB, G0, G float64 } - Equalizer struct { + equalizer struct { streamer beep.Streamer sections []section } @@ -73,7 +73,7 @@ func (s *section) apply(x [][2]float64) [][2]float64 { } func NewEqualizer(s beep.Streamer, fs float64, sections []EqualizerSection) beep.Streamer { - out := &Equalizer{ + out := &equalizer{ streamer: s, } @@ -101,7 +101,7 @@ func NewEqualizer(s beep.Streamer, fs float64, sections []EqualizerSection) beep } // Stream streams the wrapped Streamer modified by Equalizer. -func (e *Equalizer) Stream(samples [][2]float64) (n int, ok bool) { +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)) @@ -110,6 +110,6 @@ func (e *Equalizer) Stream(samples [][2]float64) (n int, ok bool) { } // Err propagates the wrapped Streamer's errors. -func (e *Equalizer) Err() error { +func (e *equalizer) Err() error { return e.streamer.Err() } From 5049b373d97d1efaee07e2b09b40031930320471 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 08:58:00 -0600 Subject: [PATCH 04/10] Added some comments And gave some credit to the author of the blog post upon which this PR is based. --- effects/equalizer.go | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index 46c2688..7dfb7f4 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -6,20 +6,51 @@ import ( "github.com/faiface/beep" ) -// https://octovoid.com/2017/11/04/coding-a-parametric-equalizer-for-audio-applications/ 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 []float64 xPast, yPast [][2]float64 } EqualizerSection struct { - F0, Bf, GB, G0, G float64 - } + // F0 (center frequency) sets the mid-point of the section’s + // frequency range and is given in Hertz [Hz]. + F0 float64 - equalizer struct { - streamer beep.Streamer - sections []section + // 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. See Figure 1. + GB float64 + + // G0 (reference gain) is given in decibels [dB] and simply + // represents the level of the section’s offset. See Figure 1. + 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 } ) From 1caf1e87eb2d554831a4819db7ffc99848e7c052 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 09:00:02 -0600 Subject: [PATCH 05/10] remove references to 'figure 1' from the blog post --- effects/equalizer.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index 7dfb7f4..05d9bf1 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -38,11 +38,11 @@ type ( // 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. See Figure 1. + // 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. See Figure 1. + // represents the level of the section’s offset. G0 float64 //G (boost/cut gain) is given in decibels [dB] and prescribes From a187c68d847c0da9ae7d6e733d0ea914ab11b57b Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 09:07:12 -0600 Subject: [PATCH 06/10] more comments --- effects/equalizer.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/effects/equalizer.go b/effects/equalizer.go index 05d9bf1..fe9c206 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -103,6 +103,8 @@ func (s *section) apply(x [][2]float64) [][2]float64 { return y } +// NewEqualizer returns a beep.Streamer that modifies the stream based on the EqualizerSection slice that is passed in. +// The sample frequency (fs) must match that of the Streamer. func NewEqualizer(s beep.Streamer, fs float64, sections []EqualizerSection) beep.Streamer { out := &equalizer{ streamer: s, From 6bf7b547a3bca09b2c51c3abd400455192b53a69 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 09:10:57 -0600 Subject: [PATCH 07/10] spacing on comment --- effects/equalizer.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index fe9c206..3e67b44 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -45,11 +45,11 @@ type ( // 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 (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 } ) From 1aba2f33ace6e0b454e784b742d0a5906b7fb211 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Tue, 22 Jun 2021 11:24:32 -0600 Subject: [PATCH 08/10] use beep.SampleRate --- effects/equalizer.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index 3e67b44..13bc8af 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -104,8 +104,9 @@ func (s *section) apply(x [][2]float64) [][2]float64 { } // NewEqualizer returns a beep.Streamer that modifies the stream based on the EqualizerSection slice that is passed in. -// The sample frequency (fs) must match that of the Streamer. -func NewEqualizer(s beep.Streamer, fs float64, sections []EqualizerSection) beep.Streamer { +// 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, } From a48355f4b62b0e3b9b030fe8176e538480741c1f Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 23 Jun 2021 07:38:25 -0600 Subject: [PATCH 09/10] apply was incorrect --- effects/equalizer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/effects/equalizer.go b/effects/equalizer.go index 13bc8af..58c52ae 100644 --- a/effects/equalizer.go +++ b/effects/equalizer.go @@ -92,10 +92,10 @@ func (s *section) apply(x [][2]float64) [][2]float64 { 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] - y[i][1] = y[i][1] / s.a[0] } + + y[i][0] = y[i][0] / s.a[0] + y[i][1] = y[i][1] / s.a[0] } s.xPast = x From 178d18fa91aba964140151d3868f9f4d9502bf23 Mon Sep 17 00:00:00 2001 From: Craig Swank Date: Wed, 23 Jun 2021 08:46:53 -0600 Subject: [PATCH 10/10] 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 {} +}