add additional waveform generators

This commit is contained in:
mahonec
2022-02-26 16:34:04 -05:00
parent c2062e63e6
commit 49095b1838
6 changed files with 256 additions and 56 deletions

View File

@@ -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)
}
}

64
generators/sawtooth.go Normal file
View File

@@ -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
}

41
generators/sine.go Normal file
View File

@@ -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
}

45
generators/square.go Normal file
View File

@@ -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
}

View File

@@ -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
}

45
generators/triangle.go Normal file
View File

@@ -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
}