diff --git a/bot.go b/bot.go index e2560bd..a6d89e5 100644 --- a/bot.go +++ b/bot.go @@ -13,8 +13,8 @@ import ( "github.com/diamondburned/arikawa/v3/state" "github.com/diamondburned/arikawa/v3/voice" - "github.com/faiface/beep" "github.com/gohugoio/hugo/cache/filecache" + "github.com/gopxl/beep" "github.com/jackc/pgx/v5" "github.com/julienschmidt/httprouter" "github.com/kataras/go-events" diff --git a/events.go b/events.go index 19fa233..69ca6ac 100644 --- a/events.go +++ b/events.go @@ -11,9 +11,9 @@ import ( "strconv" "time" - "github.com/faiface/beep" - "github.com/faiface/beep/effects" "github.com/google/uuid" + "github.com/gopxl/beep" + "github.com/gopxl/beep/effects" "github.com/kataras/go-events" "github.com/steino/gompd/v2/mpd" "golang.org/x/time/rate" diff --git a/ffmpeg/pcm.go b/ffmpeg/pcm.go index c98faaf..4f53717 100644 --- a/ffmpeg/pcm.go +++ b/ffmpeg/pcm.go @@ -7,7 +7,7 @@ import ( "math" "time" - "github.com/faiface/beep" + "github.com/gopxl/beep" ) type PCM struct { diff --git a/go.mod b/go.mod index e9a242f..ac2ca4d 100644 --- a/go.mod +++ b/go.mod @@ -5,13 +5,14 @@ go 1.21 toolchain go1.21.3 require ( + github.com/IzumiSy/go-fdkaac v0.0.0-20220502080852-c56d1bb3e32d github.com/bitly/go-simplejson v0.5.1 github.com/davecheney/xattr v0.0.0-20151008032638-dc6dbbe49f0b github.com/diamondburned/arikawa/v3 v3.2.1-0.20230320210521-82c55dffac71 - github.com/faiface/beep v1.1.0 github.com/gohugoio/hugo v0.106.0 github.com/golang-jwt/jwt/v5 v5.0.0-rc.2 github.com/google/uuid v1.3.0 + github.com/gopxl/beep v1.1.0 github.com/gorilla/sessions v1.1.1 github.com/gorilla/websocket v1.5.0 github.com/grafov/bcast v0.0.0-20190217190352-1447f067e08d @@ -26,10 +27,11 @@ require ( github.com/spf13/afero v1.9.5 github.com/spf13/viper v1.16.0 github.com/steino/gompd/v2 v2.3.1 + github.com/tejzpr/ordered-concurrently/v3 v3.0.1 golang.org/x/exp v0.0.0-20230321023759-10a507213a29 golang.org/x/net v0.15.0 golang.org/x/time v0.3.0 - gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0 + gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 ) require ( @@ -71,6 +73,7 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/niklasfasching/go-org v1.6.5 // indirect github.com/olekukonko/tablewriter v0.0.5 // indirect + github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38 // indirect github.com/pelletier/go-toml/v2 v2.1.0 // indirect github.com/rivo/uniseg v0.4.4 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect diff --git a/go.sum b/go.sum index 97cf6f5..299196d 100644 --- a/go.sum +++ b/go.sum @@ -42,6 +42,8 @@ github.com/BurntSushi/locker v0.0.0-20171006230638-a6e239ea1c69/go.mod h1:L1AbZd github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= +github.com/IzumiSy/go-fdkaac v0.0.0-20220502080852-c56d1bb3e32d h1:2Fn0vK/lH/pI2/TtUTFgb3iKkbw7TDlxBLglELToDRo= +github.com/IzumiSy/go-fdkaac v0.0.0-20220502080852-c56d1bb3e32d/go.mod h1:jmxJwIPbDUrofy2H3d28lYzz+RL9qt8GltDAAJ/mlb0= github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= @@ -119,8 +121,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanw/esbuild v0.15.14 h1:J/cqgL3yfj/HDHDo9txKAqyzTBYfAMuqCknkS2jhX24= github.com/evanw/esbuild v0.15.14/go.mod h1:iINY06rn799hi48UqEnaQvVfZWe6W9bET78LbvN8VWk= -github.com/faiface/beep v1.1.0 h1:A2gWP6xf5Rh7RG/p9/VAW2jRSDEGQm5sbOb38sf5d4c= -github.com/faiface/beep v1.1.0/go.mod h1:6I8p6kK2q4opL/eWb+kAkk38ehnTunWeToJB+s51sT4= github.com/fhs/gompd/v2 v2.3.0 h1:wuruUjmOODRlJhrYx73rJnzS7vTSXSU7pWmZtM3VPE0= github.com/fhs/gompd/v2 v2.3.0/go.mod h1:nNdZtcpD5VpmzZbRl5rV6RhxeMmAWTxEsSIMBkmMIy4= github.com/frankban/quicktest v1.7.2/go.mod h1:jaStnuzAqU1AJdCO0l53JDCJrVDKcS03DbaAcR7Ks/o= @@ -231,6 +231,8 @@ github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gopxl/beep v1.1.0 h1:YBfaDhZh4bC6IJfDsEi/8wmtUanir0dMIxpRu3F6Yeo= +github.com/gopxl/beep v1.1.0/go.mod h1:N5ClU2N8ESeO6ibbz5UThPRFpdEgbU9G60CLZ6u3v9s= github.com/gorilla/context v1.1.1 h1:AWwleXJkX/nhcU9bZSnZoi3h/qGYqQAGhq6zZe/aQW8= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2 h1:Pgr17XVTNXAk3q/r4CpKzC5xBM/qW1uVLV+IhRZpIIk= @@ -338,6 +340,8 @@ github.com/niklasfasching/go-org v1.6.5 h1:5YAIqNTdl6lAOb7lD2AyQ1RuFGPVrAKvUexph github.com/niklasfasching/go-org v1.6.5/go.mod h1:ybv0eGDnxylFUfFE+ySaQc734j/L3+/ChKZ/h63a2wM= github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= +github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38 h1:4Upfs5rLQdx7KwBct3bmPYAhWsDDJdx660gYb7Lv9TQ= +github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38/go.mod h1:Xj06yMta9R1RSKiHmxL0Bo2TB8wiKVnMgA0KVopHHkk= github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= @@ -394,6 +398,8 @@ github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZy github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tejzpr/ordered-concurrently/v3 v3.0.1 h1:TLHtzlQEDshbmGveS8S+hxLw4s5u67aoJw5LLf+X2xY= +github.com/tejzpr/ordered-concurrently/v3 v3.0.1/go.mod h1:mu/neZ6AGXm5jdPc7PEgViYK3rkYNPvVCEm15Cx/iRI= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -755,8 +761,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/fatih/set.v0 v0.2.1 h1:Xvyyp7LXu34P0ROhCyfXkmQCAoOUKb1E2JS9I7SE5CY= gopkg.in/fatih/set.v0 v0.2.1/go.mod h1:5eLWEndGL4zGGemXWrKuts+wTJR0y+w+auqUJZbmyBg= -gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0 h1:B8lK1KhYrE4H3urNYBAL/UquYftW65IHPY8JP3gpZ4M= -gopkg.in/hraban/opus.v2 v2.0.0-20220302220929-eeacdbcb92d0/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= +gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302 h1:xeVptzkP8BuJhoIjNizd2bRHfq9KB9HfOLZu90T04XM= +gopkg.in/hraban/opus.v2 v2.0.0-20230925203106-0188a62cb302/go.mod h1:/L5E7a21VWl8DeuCPKxQBdVG5cy+L0MRZ08B1wnqt7g= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/neurosnap/sentences.v1 v1.0.6/go.mod h1:YlK+SN+fLQZj+kY3r8DkGDhDr91+S3JmTb5LSxFRQo0= diff --git a/loop/loop.go b/loop/loop.go index b11a22d..8e89aa8 100644 --- a/loop/loop.go +++ b/loop/loop.go @@ -1,6 +1,6 @@ package loop -import "github.com/faiface/beep" +import "github.com/gopxl/beep" type loop struct { s beep.StreamSeekCloser diff --git a/mpd.go b/mpd.go index a1e9afa..d4b5cbd 100644 --- a/mpd.go +++ b/mpd.go @@ -3,6 +3,8 @@ package main import ( "bytes" "context" + discordspeaker "dndmusicbot/speaker" + "io" "log" "os" "os/exec" @@ -11,7 +13,7 @@ import ( "text/template" "time" - "github.com/faiface/beep" + "github.com/gopxl/beep" "github.com/kataras/go-events" "github.com/steino/gompd/v2/mpd" ) @@ -116,14 +118,31 @@ func init() { log.Println("mpd.go done.") } +var MPD_PCM *io.PipeReader + func NewMPD() (*MPD, error) { out := new(MPD) + var pcm *io.PipeWriter + MPD_PCM, pcm = io.Pipe() + f, err := syscall.Open(config.GetString("mpd.fifo"), syscall.O_CREAT|syscall.O_RDONLY|syscall.O_CLOEXEC|syscall.O_NONBLOCK, 0644) if err != nil { return nil, err } + go func() { + buf := make([]byte, 2048) + for { + _, err := syscall.Read(f, buf) + if err != nil { + pcm.Write(make([]byte, 2048)) + continue + } + pcm.Write(buf) + } + }() + out.f = beep.Format{ SampleRate: beep.SampleRate(sampleRate), NumChannels: channels, @@ -143,13 +162,14 @@ func (m *MPD) Stream(samples [][2]float64) (n int, ok bool) { tmp := make([]byte, m.f.NumChannels+2) for i := range samples { - dn, err := syscall.Read(m.file, tmp) + //dn, err := syscall.Read(m.file, tmp) + dn, err := MPD_PCM.Read(tmp) if dn == len(tmp) { samples[i], _ = m.f.DecodeSigned(tmp) ok = true } if err != nil { - samples[i] = [2]float64{} + samples[i] = discordspeaker.Silence ok = true break } diff --git a/opus/decode.go b/opus/decode.go index d2a82e2..15e9322 100644 --- a/opus/decode.go +++ b/opus/decode.go @@ -5,7 +5,7 @@ import ( "log" "sync" - "github.com/faiface/beep" + "github.com/gopxl/beep" "github.com/pkg/errors" opusd "gopkg.in/hraban/opus.v2" ) diff --git a/opus/decode_test.go b/opus/decode_test.go index 14d7079..d288bb0 100644 --- a/opus/decode_test.go +++ b/opus/decode_test.go @@ -8,7 +8,7 @@ import ( "testing" "time" - "github.com/faiface/beep" + "github.com/gopxl/beep" ) var ( @@ -19,6 +19,7 @@ var ( buf []byte maxBytes int = (frameSize * 2) * 2 done chan struct{} + f beep.Format ) func update() { @@ -62,6 +63,12 @@ func TestMain(t *testing.T) { log.Fatal(err) } + f = beep.Format{ + SampleRate: beep.SampleRate(48000), + NumChannels: 2, + Precision: 2, + } + loop := beep.Loop(-1, d) mu.Lock() diff --git a/opus/test.opus b/opus/test.opus new file mode 100644 index 0000000..8613650 Binary files /dev/null and b/opus/test.opus differ diff --git a/queue.go b/queue.go index 072f343..9a1bbc1 100644 --- a/queue.go +++ b/queue.go @@ -1,15 +1,11 @@ package main import ( - "container/list" "log" - "time" - "github.com/faiface/beep" - "github.com/faiface/beep/effects" - "github.com/kataras/go-events" + "github.com/gopxl/beep" + "github.com/gopxl/beep/effects" - "dndmusicbot/ffmpeg" discordspeaker "dndmusicbot/speaker" ) @@ -17,7 +13,7 @@ var pl_volume *effects.Volume var amb_volume *effects.Volume func init() { - log.Println("queue.go loading..") + log.Println("beep.go loading..") app.ambiance = beep.Mixer{} @@ -43,161 +39,5 @@ func init() { discordspeaker.Play(pl_volume) - /* - app.queue = new(Queue) - app.queue.list = list.New() - app.queue.Events = app.events - discordspeaker.Play(app.queue) - */ - - log.Println("queue.go done.") -} - -type Song struct { - Title string - Channel string - VideoID string - Length time.Duration - PCM *ffmpeg.PCM - Playlist Playlist - DLuri string -} - -func (s *Song) NewStream() (err error) { - s.PCM, err = ffmpeg.NewPCM(s.DLuri, sampleRate, channels) - return -} - -type Queue struct { - Events events.EventEmmiter - playing bool - list *list.List - current *list.Element -} - -func (q Queue) IsPlaying() bool { - return q.playing -} - -func (q *Queue) Play() { - q.playing = true - if q.list.Len() > 0 { - play := q.list.Front() - q.current = play - app.events.Emit("song_start") - } -} - -func (q *Queue) Add(s *Song) { - el := q.list.PushBack(s) - if el == q.list.Front() { - q.Play() - } -} - -func (q Queue) QLen() int { - return q.list.Len() -} - -func (q Queue) Current() *Song { - if q.current == nil { - return new(Song) - } - return q.current.Value.(*Song) -} - -func (q *Queue) Reset() { - err := app.mpd.Clear() - if err != nil { - log.Println(err) - } -} - -func (q *Queue) Next() { - if q.current == nil { - return - } - next := q.current.Next() - if next == nil { - next = q.list.Front() - } - - pcm := next.Value.(*Song).PCM - if pcm != nil && pcm.Position() != 0 { - err := next.Value.(*Song).NewStream() - log.Println(err) - } - - q.current = next -} - -func (q *Queue) Prev() { - if q.current == nil { - return - } - - prev := q.current.Prev() - if prev == nil { - prev = q.list.Back() - } - - err := prev.Value.(*Song).NewStream() - if err != nil { - log.Println(err) - } - - q.current = prev -} - -func (q *Queue) Preload() { -} - -func (q *Queue) Stream(samples [][2]float64) (n int, ok bool) { - // We use the filled variable to track how many samples we've - // successfully filled already. We loop until all samples are filled. - filled := 0 - - for filled < len(samples) { - // There are no streamers in the queue, so we stream silence. - if q.current == nil || q.list.Len() == 0 { - for i := range samples[filled:] { - samples[i][0] = 0 - samples[i][1] = 0 - } - break - } - - // We stream from the first streamer in the queue. - n, ok := q.current.Value.(*Song).PCM.Stream(samples[filled:]) - // If it's drained, we pop it from the queue, thus continuing with - // the next streamer. - if !ok { - q.Next() - q.Events.Emit("song_over", nil) - } - // We update the number of filled samples. - filled += n - } - - return len(samples), true -} - -func (q *Queue) Err() error { - return nil -} - -func (q *Queue) Position() int { - if q.current == nil || q.current.Value.(*Song).PCM == nil { - return 0 - } - - return q.current.Value.(*Song).PCM.Position() -} - -func (q Queue) Len() int { - if q.current == nil { - return 0 - } - - return int(q.current.Value.(*Song).Length.Milliseconds()) + log.Println("beep.go done.") } diff --git a/speaker/discord.go b/speaker/discord.go index 079e008..5451844 100644 --- a/speaker/discord.go +++ b/speaker/discord.go @@ -2,24 +2,28 @@ package discordspeaker import ( "context" + "fmt" + "io" "log" "sync" "time" "github.com/diamondburned/arikawa/v3/voice" "github.com/diamondburned/arikawa/v3/voice/voicegateway" - "github.com/faiface/beep" + "github.com/gopxl/beep" "github.com/pkg/errors" + "golang.org/x/time/rate" "gopkg.in/hraban/opus.v2" + + cc "github.com/tejzpr/ordered-concurrently/v3" ) var ( - mu sync.Mutex - mixer beep.Mixer - samples [][2]float64 - done chan struct{} - encoder *opus.Encoder - //voice *discordgo.VoiceConnection + mu sync.Mutex + mixer beep.Mixer + samples [][2]float64 + done chan struct{} + encoder *opus.Encoder frameSize int = 960 channels int = 2 sampleRate int = 48000 @@ -27,8 +31,14 @@ var ( buf []byte session *voice.Session spk bool + input chan cc.WorkFunction + pw *io.PipeWriter + pr *io.PipeReader + spklimit = rate.NewLimiter(rate.Every(2*time.Second), 1) ) +var Silence = [2]float64{} + func Init(dgv *voice.Session) error { var err error @@ -36,8 +46,9 @@ func Init(dgv *voice.Session) error { defer mu.Unlock() Close() - mixer = beep.Mixer{} + pr, pw = io.Pipe() buf = make([]byte, maxBytes) + mixer = beep.Mixer{} samples = make([][2]float64, frameSize) session = dgv @@ -49,20 +60,44 @@ func Init(dgv *voice.Session) error { return errors.Wrap(err, "failed to initialize speaker") } + input = make(chan cc.WorkFunction) + ctx := context.Background() + output := cc.Process(ctx, input, &cc.Options{PoolSize: 4, OutChannelBuffer: 4}) + go func() { for { select { default: update() + case out := <-output: + fmt.Println(pw.Write(out.Value.([]byte))) case <-done: return } } }() + go ReadSend() + return nil } +func ReadSend() { + for { + buf := make([]byte, maxBytes) + n, err := pr.Read(buf) + if err != nil { + log.Println(err) + return + } + _, err = session.Write(buf[:n]) + if err != nil { + log.Println(err) + return + } + } +} + func Close() { } @@ -87,56 +122,58 @@ func Clear() { mu.Unlock() } +func Speak(s bool) { + switch s { + case true: + + case false: + + } + +} + func update() { mu.Lock() mixer.Stream(samples) mu.Unlock() - var f32 []float32 - for _, sample := range samples { - for _, val := range sample { - f32 = append(f32, float32(val)) - } - - } - - if Silence(f32) { - if spk { + if IsSilent(samples) { + if spk && spklimit.Allow() { log.Println("Notspeaking") session.Speaking(context.Background(), voicegateway.NotSpeaking) spk = false } - time.Sleep(100 * time.Millisecond) + return } - if !spk { + if !spk && spklimit.Allow() { log.Println("Speaking") session.Speaking(context.Background(), voicegateway.Microphone) spk = true } + var f32 []float32 + + for _, sample := range samples { + f32 = append(f32, float32(sample[0])) + f32 = append(f32, float32(sample[1])) + } + n, err := encoder.EncodeFloat32(f32, buf) if err != nil { log.Println(err) - time.Sleep(100 * time.Millisecond) return } - _, err = session.Write(buf[:n]) - if err != nil { - log.Println(err) - time.Sleep(100 * time.Millisecond) - return - } + pw.Write(buf[:n]) } -func Silence(in []float32) bool { +func IsSilent(in [][2]float64) bool { for _, v := range in { - if v != 0 { + if v != Silence { return false } } - return true }