141 lines
2.5 KiB
Go
141 lines
2.5 KiB
Go
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 <rnnoise.h>
|
|
|
|
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))))
|
|
}
|