hubbub/audio/audio.go
2025-11-01 22:29:07 +01:00

149 lines
2.8 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
Buffers map[string]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:
}
}
// mix all outputs together
mix := make([]float32, len(out))
voices := 0
for _, buffer := range au.Buffers {
select {
case str := <-buffer:
if !au.Deafened {
raw, err := base64.StdEncoding.DecodeString(str)
if err != nil {
break
}
decoded := make([]float32, len(out))
if _, err = dec.DecodeFloat32(raw, decoded); err != nil {
break
}
for i := range decoded {
mix[i] += decoded[i]
}
voices++
}
default:
}
}
if voices > 0 {
for i := range mix {
mix[i] /= float32(voices)
}
}
copy(out, mix)
})
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.Send("PING", "SPEAKING")
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))))
}