mix multiple voices

This commit is contained in:
aoife cassidy 2025-11-01 22:29:07 +01:00
parent 51e55d2428
commit 39f934d810
No known key found for this signature in database
GPG Key ID: 7184AC1C9835CE48
3 changed files with 44 additions and 30 deletions

View File

@ -22,7 +22,7 @@ reasonably shouldn't, let me know (or fix it and then let me know!)
- [x] handling everyone's state - [x] handling everyone's state
- [x] any of the actual audio stuff - [x] any of the actual audio stuff
- [x] noise suppression with rnnoise - [x] noise suppression with rnnoise
- [ ] chat with more than a singular other person, probably - [x] chat with more than a singular other person, probably
## building ## building
hubbub requires go 1.23 to build. it might run on earlier versions, but i think hubbub requires go 1.23 to build. it might run on earlier versions, but i think

View File

@ -29,17 +29,9 @@ const sampleRate = 24000
const channels = 1 const channels = 1
type Audio struct { type Audio struct {
Muted bool Muted bool
Deafened bool Deafened bool
OutBuffer chan string Buffers map[string]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 { func (au *Audio) ProcessInput(conn *ircevent.Connection, channel string) error {
@ -72,23 +64,36 @@ func (au *Audio) ProcessInput(conn *ircevent.Connection, channel string) error {
} }
} }
empty := make([]float32, len(out)) // mix all outputs together
select { mix := make([]float32, len(out))
case str := <-au.OutBuffer: voices := 0
if !au.Deafened { for _, buffer := range au.Buffers {
raw, err := base64.StdEncoding.DecodeString(str) select {
if err != nil { case str := <-buffer:
break 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++
} }
if _, err = dec.DecodeFloat32(raw, out); err != nil { default:
break
}
} else {
copy(out, empty)
} }
default:
copy(out, empty)
} }
if voices > 0 {
for i := range mix {
mix[i] /= float32(voices)
}
}
copy(out, mix)
}) })
if err != nil { if err != nil {
log.Printf("error opening input stream: %v", err) log.Printf("error opening input stream: %v", err)

17
main.go
View File

@ -119,6 +119,7 @@ func (m model) Init() tea.Cmd {
u, ok := m.users[e.Nick()] u, ok := m.users[e.Nick()]
if !ok { if !ok {
m.users[e.Nick()] = user{} m.users[e.Nick()] = user{}
m.au.Buffers[e.Nick()] = make(chan string)
} }
switch message { switch message {
@ -133,7 +134,7 @@ func (m model) Init() tea.Cmd {
u.isMuted = false u.isMuted = false
default: default:
u.lastSpoke = time.Now() u.lastSpoke = time.Now()
m.au.OutBuffer <- message m.au.Buffers[e.Nick()] <- message
} }
m.users[e.Nick()] = u m.users[e.Nick()] = u
@ -141,6 +142,9 @@ func (m model) Init() tea.Cmd {
m.conn.AddCallback("JOIN", func(e ircmsg.Message) { m.conn.AddCallback("JOIN", func(e ircmsg.Message) {
m.users[e.Nick()] = user{} m.users[e.Nick()] = user{}
if e.Nick() != m.nick {
m.au.Buffers[e.Nick()] = make(chan string)
}
// advertise your muted and deafened statuses on someone joining // advertise your muted and deafened statuses on someone joining
if m.deafen { if m.deafen {
@ -157,10 +161,12 @@ func (m model) Init() tea.Cmd {
m.conn.AddCallback("PART", func(e ircmsg.Message) { m.conn.AddCallback("PART", func(e ircmsg.Message) {
delete(m.users, e.Nick()) delete(m.users, e.Nick())
delete(m.au.Buffers, e.Nick())
}) })
m.conn.AddCallback("QUIT", func(e ircmsg.Message) { m.conn.AddCallback("QUIT", func(e ircmsg.Message) {
delete(m.users, e.Nick()) delete(m.users, e.Nick())
delete(m.au.Buffers, e.Nick())
}) })
m.conn.Join(m.channel) m.conn.Join(m.channel)
@ -215,8 +221,9 @@ func (m model) View() (s string) {
} }
s += fmt.Sprintf("%d user%s connected:\n", numUsers, plural) s += fmt.Sprintf("%d user%s connected:\n", numUsers, plural)
for _, nick := range slices.Sorted(maps.Keys(m.users)) { users := maps.Clone(m.users)
user := m.users[nick] for _, nick := range slices.Sorted(maps.Keys(users)) {
user := users[nick]
status := " " status := " "
nickStyled := styleInactive.Render(nick) nickStyled := styleInactive.Render(nick)
if user.isDeafened { if user.isDeafened {
@ -282,7 +289,9 @@ func start(c *cli.Context) error {
return err return err
} }
au := audio.NewAudio() au := audio.Audio{
Buffers: map[string](chan string){},
}
go func() { go func() {
if err := au.ProcessInput(&conn.Connection, config.Channel); err != nil { if err := au.ProcessInput(&conn.Connection, config.Channel); err != nil {
os.Exit(1) os.Exit(1)