hubbub/audio/audio.go
aoife cassidy 6592b13c6f
audio!!!
2025-11-01 08:26:54 +01:00

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))))
}