package discordspeaker import ( "context" "fmt" "io" "log" "sync" "time" "github.com/diamondburned/arikawa/v3/voice" "github.com/diamondburned/arikawa/v3/voice/voicegateway" "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 frameSize int = 960 channels int = 2 sampleRate int = 48000 maxBytes int = (frameSize * 2) * 2 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 mu.Lock() defer mu.Unlock() Close() pr, pw = io.Pipe() buf = make([]byte, maxBytes) mixer = beep.Mixer{} samples = make([][2]float64, frameSize) session = dgv encoder, err = opus.NewEncoder(sampleRate, channels, opus.AppVoIP) encoder.SetBitrateToMax() if err != nil { 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() { } func Lock() { mu.Lock() } // Unlock unlocks the speaker. Call after modifying any currently playing Streamer. func Unlock() { mu.Unlock() } func Play(s ...beep.Streamer) { mu.Lock() mixer.Add(s...) mu.Unlock() } func Clear() { mu.Lock() mixer.Clear() mu.Unlock() } func Speak(s bool) { switch s { case true: case false: } } func update() { mu.Lock() mixer.Stream(samples) mu.Unlock() if IsSilent(samples) { if spk && spklimit.Allow() { log.Println("Notspeaking") session.Speaking(context.Background(), voicegateway.NotSpeaking) spk = false } return } 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) return } pw.Write(buf[:n]) } func IsSilent(in [][2]float64) bool { for _, v := range in { if v != Silence { return false } } return true }