package discordspeaker import ( "bytes" "encoding/binary" "log" "sync" "github.com/bwmarrin/discordgo" "github.com/faiface/beep" "github.com/pkg/errors" "layeh.com/gopus" ) const bufferSize = 1000 var ( mu sync.Mutex mixer beep.Mixer samples [][2]float64 done chan struct{} encoder *gopus.Encoder voice *discordgo.VoiceConnection frameSize int = 960 channels int = 2 sampleRate int = 48000 maxBytes int = (frameSize * 2) * 2 pcm []int16 buf []byte pause bool ) func Init(dgv *discordgo.VoiceConnection) error { var err error mu.Lock() defer mu.Unlock() Close() mixer = beep.Mixer{} buf = make([]byte, maxBytes) pcm = make([]int16, frameSize*channels) samples = make([][2]float64, frameSize) pause = true voice = dgv encoder, err = gopus.NewEncoder(sampleRate, channels, gopus.Audio) if err != nil { return errors.Wrap(err, "failed to initialize speaker") } go func() { for { select { default: update() case <-done: return } } }() return nil } 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 Pause(p bool) { pause = p } func IsPaused() bool { return pause } func update() { if pause { return } mu.Lock() mixer.Stream(samples) mu.Unlock() for i := range samples { for c := range samples[i] { val := samples[i][c] if val < -1 { val = -1 } if val > +1 { val = +1 } valInt16 := int16(val * (1<<15 - 1)) low := byte(valInt16) high := byte(valInt16 >> 8) buf[i*4+c*2+0] = low buf[i*4+c*2+1] = high } } binary.Read(bytes.NewReader(buf), binary.LittleEndian, &pcm) opus, err := encoder.Encode(pcm, frameSize, maxBytes) if err != nil { log.Println(err) return } if voice.Ready == false || voice.OpusSend == nil { return } voice.OpusSend <- opus }