package audio import ( "encoding/base64" "log" "math" "unsafe" "github.com/ergochat/irc-go/ircevent" "github.com/gordonklaus/portaudio" "gopkg.in/hraban/opus.v2" ) /* #cgo pkg-config: rnnoise #include DenoiseState *rn_setup() { return rnnoise_create(NULL); } void rn_cleanup(DenoiseState *st) { rnnoise_destroy(st); } */ import "C" const sampleRate = 24000 const channels = 1 type Audio struct { Muted bool Deafened bool OutBuffer chan string } func NewAudio() Audio { return Audio{ Muted: false, Deafened: false, OutBuffer: make(chan string), } } func (au *Audio) ProcessInput(conn *ircevent.Connection, channel string) error { st := C.rn_setup() defer C.rn_cleanup(st) enc, err := opus.NewEncoder(sampleRate, channels, opus.AppVoIP) if err != nil { log.Printf("error creating opus encoder: %v", err) return err } dec, err := opus.NewDecoder(sampleRate, channels) if err != nil { log.Printf("error creating opus decoder: %v", err) return err } portaudio.Initialize() defer portaudio.Terminate() inChan := make(chan []float32, 8) stream, err := portaudio.OpenDefaultStream(channels, channels, sampleRate, 480, func(in, out []float32) { if !au.Muted { frame := make([]float32, len(in)) copy(frame, in) select { case inChan <- frame: default: } } empty := make([]float32, len(out)) if !au.Deafened { select { case str := <-au.OutBuffer: raw, err := base64.StdEncoding.DecodeString(str) if err != nil { break } if _, err = dec.DecodeFloat32(raw, out); err != nil { break } default: copy(out, empty) } } }) if err != nil { log.Printf("error opening input stream: %v", err) return err } defer stream.Close() if err = stream.Start(); err != nil { log.Printf("error starting input stream: %v", err) return err } defer stream.Stop() data := make([]byte, 480) debounce := 0 for frame := range inChan { C.rnnoise_process_frame( st, (*C.float)(unsafe.Pointer(&frame[0])), (*C.float)(unsafe.Pointer(&frame[0])), ) if rms(frame) < 0.02 { debounce++ if debounce >= 10 { continue } } else { debounce = 0 } n, err := enc.EncodeFloat32(frame, data) if err != nil { log.Printf("error encoding opus data: %v", err) return err } str := base64.StdEncoding.EncodeToString(data[:n]) conn.Privmsg(channel, str) } return nil } func rms(frame []float32) float64 { var sum float32 for _, s := range frame { sum += s * s } return math.Sqrt(float64(sum / float32(len(frame)))) }