From 49095b1838d112feedef3ef9523bbbd5e5f38693 Mon Sep 17 00:00:00 2001 From: mahonec Date: Sat, 26 Feb 2022 16:34:04 -0500 Subject: [PATCH 1/3] add additional waveform generators --- examples/tone-player/main.go | 69 +++++++++++++++++++++++++++++++----- generators/sawtooth.go | 64 +++++++++++++++++++++++++++++++++ generators/sine.go | 41 +++++++++++++++++++++ generators/square.go | 45 +++++++++++++++++++++++ generators/toner.go | 48 ------------------------- generators/triangle.go | 45 +++++++++++++++++++++++ 6 files changed, 256 insertions(+), 56 deletions(-) create mode 100644 generators/sawtooth.go create mode 100644 generators/sine.go create mode 100644 generators/square.go delete mode 100644 generators/toner.go create mode 100644 generators/triangle.go diff --git a/examples/tone-player/main.go b/examples/tone-player/main.go index 93f87d6..51391df 100644 --- a/examples/tone-player/main.go +++ b/examples/tone-player/main.go @@ -2,35 +2,88 @@ package main import ( "fmt" + "os" + "strconv" + "time" + "github.com/faiface/beep" "github.com/faiface/beep/generators" "github.com/faiface/beep/speaker" - "os" - "strconv" ) func usage() { fmt.Printf("usage: %s freq\n", os.Args[0]) - fmt.Println("where freq must be an integer from 1 to 24000") + fmt.Println("where freq must be a float between 1 and 24000") fmt.Println("24000 because samplerate of 48000 is hardcoded") } + func main() { if len(os.Args) < 2 { usage() return } - f, err := strconv.Atoi(os.Args[1]) + + f, err := strconv.ParseFloat(os.Args[1], 64) if err != nil { usage() return } - speaker.Init(beep.SampleRate(48000), 4800) - s, err := generators.SinTone(beep.SampleRate(48000), f) + + sr := beep.SampleRate(48000) + speaker.Init(sr, 4800) + + sine, err := generators.SineTone(sr, f) if err != nil { panic(err) } - speaker.Play(s) - for { + triangle, err := generators.TriangleTone(sr, f) + if err != nil { + panic(err) + } + + square, err := generators.SquareTone(sr, f) + if err != nil { + panic(err) + } + + sawtooth, err := generators.SawtoothTone(sr, f) + if err != nil { + panic(err) + } + + sawtoothReversed, err := generators.SawtoothToneReversed(sr, f) + if err != nil { + panic(err) + } + + // Play 2 seconds of each tone + two := sr.N(2 * time.Second) + + ch := make(chan struct{}) + sounds := []beep.Streamer{ + beep.Callback(print("sine")), + beep.Take(two, sine), + beep.Callback(print("triangle")), + beep.Take(two, triangle), + beep.Callback(print("square")), + beep.Take(two, square), + beep.Callback(print("sawtooth")), + beep.Take(two, sawtooth), + beep.Callback(print("sawtooth reversed")), + beep.Take(two, sawtoothReversed), + beep.Callback(func() { + ch <- struct{}{} + }), + } + speaker.Play(beep.Seq(sounds...)) + + <-ch +} + +// a simple clousure to wrap fmt.Println +func print(s string) func() { + return func() { + fmt.Println(s) } } diff --git a/generators/sawtooth.go b/generators/sawtooth.go new file mode 100644 index 0000000..54a7aad --- /dev/null +++ b/generators/sawtooth.go @@ -0,0 +1,64 @@ +package generators + +import ( + "errors" + "math" + + "github.com/faiface/beep" +) + +type sawGenerator struct { + dt float64 + t float64 + + reverse bool +} + +// Creates a streamer which will procude an infinite sawtooth wave with the given frequency. +// use other wrappers of this package to change amplitude or add time limit. +// sampleRate must be at least two times grater then frequency, otherwise this function will return an error. +func SawtoothTone(sr beep.SampleRate, freq float64) (beep.Streamer, error) { + dt := freq / float64(sr) + + if dt >= 1.0/2.0 { + return nil, errors.New("faiface sawtooth tone generator: samplerate must be at least 2 times grater then frequency") + } + + return &sawGenerator{dt, 0, false}, nil +} + +// Creates a streamer which will procude an infinite sawtooth tone with the given frequency. +// sawtooth is reversed so the slope is negative. +// use other wrappers of this package to change amplitude or add time limit. +// sampleRate must be at least two times grater then frequency, otherwise this function will return an error. +func SawtoothToneReversed(sr beep.SampleRate, freq float64) (beep.Streamer, error) { + dt := freq / float64(sr) + + if dt >= 1.0/2.0 { + return nil, errors.New("faiface triangle tone generator: samplerate must be at least 2 times grater then frequency") + } + + return &sawGenerator{dt, 0, true}, nil +} + +func (g *sawGenerator) Stream(samples [][2]float64) (n int, ok bool) { + if g.reverse { + for i := range samples { + samples[i][0] = 2.0 * (1 - g.t) + samples[i][1] = 2.0 * (1 - g.t) + _, g.t = math.Modf(g.t + g.dt) + } + } else { + for i := range samples { + samples[i][0] = 2.0*g.t - 1.0 + samples[i][1] = 2.0*g.t - 1.0 + _, g.t = math.Modf(g.t + g.dt) + } + } + + return len(samples), true +} + +func (*sawGenerator) Err() error { + return nil +} diff --git a/generators/sine.go b/generators/sine.go new file mode 100644 index 0000000..5e9b207 --- /dev/null +++ b/generators/sine.go @@ -0,0 +1,41 @@ +package generators + +import ( + "errors" + "math" + + "github.com/faiface/beep" +) + +type sineGenerator struct { + dt float64 + t float64 +} + +// Creates a streamer which will procude an infinite sine wave with the given frequency. +// use other wrappers of this package to change amplitude or add time limit. +// sampleRate must be at least two times grater then frequency, otherwise this function will return an error. +func SineTone(sr beep.SampleRate, freq float64) (beep.Streamer, error) { + dt := freq / float64(sr) + + if dt >= 1.0/2.0 { + return nil, errors.New("faiface sine tone generator: samplerate must be at least 2 times grater then frequency") + } + + return &sineGenerator{dt, 0}, nil +} + +func (g *sineGenerator) Stream(samples [][2]float64) (n int, ok bool) { + for i := range samples { + v := math.Sin(g.t * 2.0 * math.Pi) + samples[i][0] = v + samples[i][1] = v + _, g.t = math.Modf(g.t + g.dt) + } + + return len(samples), true +} + +func (*sineGenerator) Err() error { + return nil +} diff --git a/generators/square.go b/generators/square.go new file mode 100644 index 0000000..d4768e6 --- /dev/null +++ b/generators/square.go @@ -0,0 +1,45 @@ +package generators + +import ( + "errors" + "math" + + "github.com/faiface/beep" +) + +type squareGenerator struct { + dt float64 + t float64 +} + +// Creates a streamer which will procude an infinite square wave with the given frequency. +// use other wrappers of this package to change amplitude or add time limit. +// sampleRate must be at least two times grater then frequency, otherwise this function will return an error. +func SquareTone(sr beep.SampleRate, freq float64) (beep.Streamer, error) { + dt := freq / float64(sr) + + if dt >= 1.0/2.0 { + return nil, errors.New("faiface square tone generator: samplerate must be at least 2 times grater then frequency") + } + + return &squareGenerator{dt, 0}, nil +} + +func (g *squareGenerator) Stream(samples [][2]float64) (n int, ok bool) { + for i := range samples { + if g.t < 0.5 { + samples[i][0] = 1.0 + samples[i][1] = 1.0 + } else { + samples[i][0] = -1.0 + samples[i][1] = -1.0 + } + _, g.t = math.Modf(g.t + g.dt) + } + + return len(samples), true +} + +func (*squareGenerator) Err() error { + return nil +} diff --git a/generators/toner.go b/generators/toner.go deleted file mode 100644 index 01ee03a..0000000 --- a/generators/toner.go +++ /dev/null @@ -1,48 +0,0 @@ -// tones generator - -package generators - -import ( - "math" - "errors" - . "github.com/faiface/beep" -) - -// simple sinusoid tone generator -type toneStreamer struct { - stat float64 - delta float64 -} - -// create streamer which will produce infinite sinusoid tone with the given frequency -// use other wrappers of this package to change amplitude or add time limit -// sampleRate must be at least two times grater then frequency, otherwise this function will return an error -func SinTone(sr SampleRate, freq int) (Streamer, error) { - if int(sr)/freq < 2 { - return nil, errors.New("faiface beep tone generator: samplerate must be at least 2 times grater then frequency") - } - r := new(toneStreamer) - r.stat = 0.0 - srf := float64(sr) - ff := float64(freq) - steps := srf / ff - r.delta = 1.0 / steps - return r, nil -} - -func (c *toneStreamer) nextSample() float64 { - r := math.Sin(c.stat * 2.0 * math.Pi) - _, c.stat = math.Modf(c.stat + c.delta) - return r -} - -func (c *toneStreamer) Stream(buf [][2]float64) (int, bool) { - for i := 0; i < len(buf); i++ { - s := c.nextSample() - buf[i] = [2]float64{s, s} - } - return len(buf), true -} -func (_ *toneStreamer) Err() error { - return nil -} diff --git a/generators/triangle.go b/generators/triangle.go new file mode 100644 index 0000000..9426248 --- /dev/null +++ b/generators/triangle.go @@ -0,0 +1,45 @@ +package generators + +import ( + "errors" + "math" + + "github.com/faiface/beep" +) + +type triangleGenerator struct { + dt float64 + t float64 +} + +// Creates a streamer which will procude an infinite triangle wave with the given frequency. +// use other wrappers of this package to change amplitude or add time limit. +// sampleRate must be at least two times grater then frequency, otherwise this function will return an error. +func TriangleTone(sr beep.SampleRate, freq float64) (beep.Streamer, error) { + dt := freq / float64(sr) + + if dt >= 1.0/2.0 { + return nil, errors.New("faiface triangle tone generator: samplerate must be at least 2 times grater then frequency") + } + + return &triangleGenerator{dt, 0}, nil +} + +func (g *triangleGenerator) Stream(samples [][2]float64) (n int, ok bool) { + for i := range samples { + if g.t < 0.5 { + samples[i][0] = 2.0 * g.t + samples[i][1] = 2.0 * g.t + } else { + samples[i][0] = 2.0 * (1.0 - g.t) + samples[i][1] = 2.0 * (1.0 - g.t) + } + _, g.t = math.Modf(g.t + g.dt) + } + + return len(samples), true +} + +func (*triangleGenerator) Err() error { + return nil +} From e40117435823ab64deeeed275383c3160846bbf7 Mon Sep 17 00:00:00 2001 From: Christopher Mahoney <56850185+Alextopher@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:39:20 -0500 Subject: [PATCH 2/3] Update generators/sawtooth.go Co-authored-by: Xavier MALPARTY <77609814+malparty@users.noreply.github.com> --- generators/sawtooth.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/generators/sawtooth.go b/generators/sawtooth.go index 54a7aad..3a3f912 100644 --- a/generators/sawtooth.go +++ b/generators/sawtooth.go @@ -44,8 +44,8 @@ func SawtoothToneReversed(sr beep.SampleRate, freq float64) (beep.Streamer, erro func (g *sawGenerator) Stream(samples [][2]float64) (n int, ok bool) { if g.reverse { for i := range samples { - samples[i][0] = 2.0 * (1 - g.t) - samples[i][1] = 2.0 * (1 - g.t) + samples[i][0] = 2.0*(1-g.t) - 1 + samples[i][1] = 2.0*(1-g.t) - 1 _, g.t = math.Modf(g.t + g.dt) } } else { From 6a1394df21e47f8e6b2465c44c0d4a4fe8a72bcd Mon Sep 17 00:00:00 2001 From: Christopher Mahoney <56850185+Alextopher@users.noreply.github.com> Date: Wed, 2 Mar 2022 17:39:29 -0500 Subject: [PATCH 3/3] Update generators/triangle.go Co-authored-by: Xavier MALPARTY <77609814+malparty@users.noreply.github.com> --- generators/triangle.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/generators/triangle.go b/generators/triangle.go index 9426248..6b5c75b 100644 --- a/generators/triangle.go +++ b/generators/triangle.go @@ -28,11 +28,11 @@ func TriangleTone(sr beep.SampleRate, freq float64) (beep.Streamer, error) { func (g *triangleGenerator) Stream(samples [][2]float64) (n int, ok bool) { for i := range samples { if g.t < 0.5 { - samples[i][0] = 2.0 * g.t - samples[i][1] = 2.0 * g.t + samples[i][0] = 2.0*(1-g.t) - 1 + samples[i][1] = 2.0*(1-g.t) - 1 } else { - samples[i][0] = 2.0 * (1.0 - g.t) - samples[i][1] = 2.0 * (1.0 - g.t) + samples[i][0] = 2.0*g.t - 1.0 + samples[i][1] = 2.0*g.t - 1.0 } _, g.t = math.Modf(g.t + g.dt) }