Updated barnard to use openal for sound by default. This should bring with it pipewire or pulseaudio support.

This commit is contained in:
Storm Dragon
2025-12-16 15:44:10 -05:00
parent 9fe7d7ad87
commit cfbefd3f7d
3 changed files with 297 additions and 259 deletions

View File

@@ -141,6 +141,18 @@ Pass the -list_devices parameter to barnard to be given a list of audio input an
Copy lines from the above list into inputdevice and outputdevice as desired. Copy lines from the above list into inputdevice and outputdevice as desired.
To clear your inputdevice or outputdevice options and set them to defaults, set them to "" or delete them entirely. To clear your inputdevice or outputdevice options and set them to defaults, set them to "" or delete them entirely.
### Audio Backends (ALSA, PipeWire, PulseAudio)
Barnard uses OpenAL Soft for audio. By default it will pick the first available backend (often ALSA), but you can force a specific driver:
- Command line: `./barnard --audio-driver pipewire` (or `pulse`, `alsa`, `jack`)
- Config file: add `audiodriver = "pipewire"` to your `~/.barnard.toml`
- Environment: `ALSOFT_DRIVERS=pipewire ./barnard` (takes precedence over config)
If PipeWire or PulseAudio support is missing, install OpenAL Soft with the corresponding backend enabled (e.g., `libopenal1` or `openal-soft` packages built with PipeWire). After changing drivers, rerun with `--list_devices` to confirm the desired devices appear.
Leaving `audiodriver` empty in the config keeps the OpenAL default ordering (PipeWire/Pulse first if available, then ALSA).
## Keystrokes ## Keystrokes
You can see the below keystrokes in your config file. You can see the below keystrokes in your config file.

View File

@@ -1,354 +1,366 @@
package config package config
import ( import (
"fmt" "fmt"
"git.stormux.org/storm/barnard/uiterm" "git.stormux.org/storm/barnard/gumble/gumble"
"github.com/pelletier/go-toml/v2" "git.stormux.org/storm/barnard/uiterm"
"git.stormux.org/storm/barnard/gumble/gumble" "github.com/pelletier/go-toml/v2"
"io/ioutil" "io/ioutil"
"os" "os"
"os/user" "os/user"
"strconv" "strconv"
"strings" "strings"
) )
type Config struct { type Config struct {
config *exportableConfig config *exportableConfig
fn string fn string
} }
type exportableConfig struct { type exportableConfig struct {
Hotkeys *Hotkeys Hotkeys *Hotkeys
MicVolume *float32 AudioDriver *string
InputDevice *string MicVolume *float32
OutputDevice *string InputDevice *string
Servers []*server OutputDevice *string
DefaultServer *string Servers []*server
Username *string DefaultServer *string
NotifyCommand *string Username *string
NoiseSuppressionEnabled *bool NotifyCommand *string
NoiseSuppressionThreshold *float32 NoiseSuppressionEnabled *bool
VoiceEffect *int NoiseSuppressionThreshold *float32
Certificate *string VoiceEffect *int
Certificate *string
} }
type server struct { type server struct {
Host string Host string
Port int Port int
Users []*eUser Users []*eUser
} }
type eUser struct { type eUser struct {
Username string Username string
Boost uint16 Boost uint16
Volume float32 Volume float32
LocallyMuted bool // Changed from Muted to LocallyMuted to match User struct LocallyMuted bool // Changed from Muted to LocallyMuted to match User struct
} }
func (c *Config) SaveConfig() { func (c *Config) SaveConfig() {
var data []byte var data []byte
data, err := toml.Marshal(c.config) data, err := toml.Marshal(c.config)
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = ioutil.WriteFile(c.fn+".tmp", data, 0600) err = ioutil.WriteFile(c.fn+".tmp", data, 0600)
if err != nil { if err != nil {
panic(err) panic(err)
} }
err = os.Rename(c.fn+".tmp", c.fn) err = os.Rename(c.fn+".tmp", c.fn)
if err != nil { if err != nil {
panic(err) panic(err)
} }
} }
func key(k uiterm.Key) *uiterm.Key { func key(k uiterm.Key) *uiterm.Key {
return &k return &k
} }
func (c *Config) LoadConfig() { func (c *Config) LoadConfig() {
var jc exportableConfig var jc exportableConfig
jc = exportableConfig{} jc = exportableConfig{}
jc.Hotkeys = &Hotkeys{ jc.Hotkeys = &Hotkeys{
Talk: key(uiterm.KeyF1), Talk: key(uiterm.KeyF1),
VolumeDown: key(uiterm.KeyF5), VolumeDown: key(uiterm.KeyF5),
VolumeUp: key(uiterm.KeyF6), VolumeUp: key(uiterm.KeyF6),
VolumeReset: key(uiterm.KeyF8), VolumeReset: key(uiterm.KeyF8),
MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey MuteToggle: key(uiterm.KeyF7), // Added mute toggle hotkey
Exit: key(uiterm.KeyF10), Exit: key(uiterm.KeyF10),
ToggleTimestamps: key(uiterm.KeyF3), ToggleTimestamps: key(uiterm.KeyF3),
SwitchViews: key(uiterm.KeyTab), SwitchViews: key(uiterm.KeyTab),
ScrollUp: key(uiterm.KeyPgup), ScrollUp: key(uiterm.KeyPgup),
ScrollDown: key(uiterm.KeyPgdn), ScrollDown: key(uiterm.KeyPgdn),
NoiseSuppressionToggle: key(uiterm.KeyF9), NoiseSuppressionToggle: key(uiterm.KeyF9),
CycleVoiceEffect: key(uiterm.KeyF12), CycleVoiceEffect: key(uiterm.KeyF12),
} }
if fileExists(c.fn) { if fileExists(c.fn) {
var data []byte var data []byte
data = readFile(c.fn) data = readFile(c.fn)
if data != nil { if data != nil {
err := toml.Unmarshal(data, &jc) err := toml.Unmarshal(data, &jc)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error()) fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error())
os.Exit(1) os.Exit(1)
} }
} }
} }
c.config = &jc c.config = &jc
if c.config.MicVolume == nil { if c.config.MicVolume == nil {
micvol := float32(1.0) micvol := float32(1.0)
jc.MicVolume = &micvol jc.MicVolume = &micvol
} }
if c.config.InputDevice == nil { if c.config.AudioDriver == nil {
idev := string("") driver := string("")
jc.InputDevice = &idev jc.AudioDriver = &driver
} }
if c.config.OutputDevice == nil { if c.config.InputDevice == nil {
odev := string("") idev := string("")
jc.OutputDevice = &odev jc.InputDevice = &idev
} }
if c.config.DefaultServer == nil { if c.config.OutputDevice == nil {
defaultServer := string("localhost:64738") odev := string("")
jc.DefaultServer = &defaultServer jc.OutputDevice = &odev
} }
if c.config.Username == nil { if c.config.DefaultServer == nil {
username := string("") defaultServer := string("localhost:64738")
jc.Username = &username jc.DefaultServer = &defaultServer
} }
if c.config.NotifyCommand == nil { if c.config.Username == nil {
ncmd := string("/usr/share/barnard/barnard-sound.sh \"%event\" \"%who\" \"%what\"") username := string("")
jc.NotifyCommand = &ncmd jc.Username = &username
} }
if c.config.NoiseSuppressionEnabled == nil { if c.config.NotifyCommand == nil {
enabled := false ncmd := string("/usr/share/barnard/barnard-sound.sh \"%event\" \"%who\" \"%what\"")
jc.NoiseSuppressionEnabled = &enabled jc.NotifyCommand = &ncmd
} }
if c.config.NoiseSuppressionThreshold == nil { if c.config.NoiseSuppressionEnabled == nil {
threshold := float32(0.02) enabled := false
jc.NoiseSuppressionThreshold = &threshold jc.NoiseSuppressionEnabled = &enabled
} }
if c.config.VoiceEffect == nil { if c.config.NoiseSuppressionThreshold == nil {
effect := 0 // Default to EffectNone threshold := float32(0.02)
jc.VoiceEffect = &effect jc.NoiseSuppressionThreshold = &threshold
} }
if c.config.Certificate == nil { if c.config.VoiceEffect == nil {
cert := string("") effect := 0 // Default to EffectNone
jc.Certificate = &cert jc.VoiceEffect = &effect
} }
if c.config.Certificate == nil {
cert := string("")
jc.Certificate = &cert
}
} }
func (c *Config) findServer(address string) *server { func (c *Config) findServer(address string) *server {
if c.config.Servers == nil { if c.config.Servers == nil {
c.config.Servers = make([]*server, 0) c.config.Servers = make([]*server, 0)
} }
host, port := makeHostPort(address) host, port := makeHostPort(address)
var t *server var t *server
for _, s := range c.config.Servers { for _, s := range c.config.Servers {
if s.Port == port && s.Host == host { if s.Port == port && s.Host == host {
t = s t = s
break break
} }
} }
if t == nil { if t == nil {
t = &server{ t = &server{
Host: host, Host: host,
Port: port, Port: port,
} }
c.config.Servers = append(c.config.Servers, t) c.config.Servers = append(c.config.Servers, t)
} }
return t return t
} }
func (c *Config) findUser(address string, username string) *eUser { func (c *Config) findUser(address string, username string) *eUser {
var s *server var s *server
s = c.findServer(address) s = c.findServer(address)
if s.Users == nil { if s.Users == nil {
s.Users = make([]*eUser, 0) s.Users = make([]*eUser, 0)
} }
var t *eUser var t *eUser
for _, u := range s.Users { for _, u := range s.Users {
if u.Username == username { if u.Username == username {
t = u t = u
break break
} }
} }
if t == nil { if t == nil {
t = &eUser{ t = &eUser{
Username: username, Username: username,
Boost: uint16(1), Boost: uint16(1),
Volume: 1.0, Volume: 1.0,
LocallyMuted: false, // Initialize local mute state LocallyMuted: false, // Initialize local mute state
} }
s.Users = append(s.Users, t) s.Users = append(s.Users, t)
} }
return t return t
} }
func (c *Config) ToggleMute(u *gumble.User) { func (c *Config) ToggleMute(u *gumble.User) {
j := c.findUser(u.GetClient().Config.Address, u.Name) j := c.findUser(u.GetClient().Config.Address, u.Name)
j.LocallyMuted = !j.LocallyMuted j.LocallyMuted = !j.LocallyMuted
u.LocallyMuted = j.LocallyMuted u.LocallyMuted = j.LocallyMuted
c.SaveConfig() c.SaveConfig()
} }
func (c *Config) SetMicVolume(v float32) { func (c *Config) SetMicVolume(v float32) {
t := float32(v) t := float32(v)
c.config.MicVolume = &t c.config.MicVolume = &t
} }
func (c *Config) GetHotkeys() *Hotkeys { func (c *Config) GetHotkeys() *Hotkeys {
return c.config.Hotkeys return c.config.Hotkeys
} }
func (c *Config) GetNotifyCommand() *string { func (c *Config) GetNotifyCommand() *string {
return c.config.NotifyCommand return c.config.NotifyCommand
}
func (c *Config) GetAudioDriver() string {
if c.config.AudioDriver == nil {
return ""
}
return *c.config.AudioDriver
} }
func (c *Config) GetInputDevice() *string { func (c *Config) GetInputDevice() *string {
return c.config.InputDevice return c.config.InputDevice
} }
func (c *Config) GetOutputDevice() *string { func (c *Config) GetOutputDevice() *string {
return c.config.OutputDevice return c.config.OutputDevice
} }
func (c *Config) GetDefaultServer() *string { func (c *Config) GetDefaultServer() *string {
return c.config.DefaultServer return c.config.DefaultServer
} }
func (c *Config) GetUsername() *string { func (c *Config) GetUsername() *string {
return c.config.Username return c.config.Username
} }
func (c *Config) GetCertificate() *string { func (c *Config) GetCertificate() *string {
return c.config.Certificate return c.config.Certificate
} }
func (c *Config) GetNoiseSuppressionEnabled() bool { func (c *Config) GetNoiseSuppressionEnabled() bool {
if c.config.NoiseSuppressionEnabled == nil { if c.config.NoiseSuppressionEnabled == nil {
return false return false
} }
return *c.config.NoiseSuppressionEnabled return *c.config.NoiseSuppressionEnabled
} }
func (c *Config) SetNoiseSuppressionEnabled(enabled bool) { func (c *Config) SetNoiseSuppressionEnabled(enabled bool) {
c.config.NoiseSuppressionEnabled = &enabled c.config.NoiseSuppressionEnabled = &enabled
c.SaveConfig() c.SaveConfig()
} }
func (c *Config) GetNoiseSuppressionThreshold() float32 { func (c *Config) GetNoiseSuppressionThreshold() float32 {
if c.config.NoiseSuppressionThreshold == nil { if c.config.NoiseSuppressionThreshold == nil {
return 0.02 return 0.02
} }
return *c.config.NoiseSuppressionThreshold return *c.config.NoiseSuppressionThreshold
} }
func (c *Config) SetNoiseSuppressionThreshold(threshold float32) { func (c *Config) SetNoiseSuppressionThreshold(threshold float32) {
c.config.NoiseSuppressionThreshold = &threshold c.config.NoiseSuppressionThreshold = &threshold
c.SaveConfig() c.SaveConfig()
} }
func (c *Config) GetVoiceEffect() int { func (c *Config) GetVoiceEffect() int {
if c.config.VoiceEffect == nil { if c.config.VoiceEffect == nil {
return 0 return 0
} }
return *c.config.VoiceEffect return *c.config.VoiceEffect
} }
func (c *Config) SetVoiceEffect(effect int) { func (c *Config) SetVoiceEffect(effect int) {
c.config.VoiceEffect = &effect c.config.VoiceEffect = &effect
c.SaveConfig() c.SaveConfig()
} }
func (c *Config) UpdateUser(u *gumble.User) { func (c *Config) UpdateUser(u *gumble.User) {
var j *eUser var j *eUser
var uc *gumble.Client var uc *gumble.Client
uc = u.GetClient() uc = u.GetClient()
if uc != nil { if uc != nil {
j = c.findUser(uc.Config.Address, u.Name) j = c.findUser(uc.Config.Address, u.Name)
u.Boost = j.Boost u.Boost = j.Boost
u.Volume = j.Volume u.Volume = j.Volume
u.LocallyMuted = j.LocallyMuted // Update LocallyMuted state from config u.LocallyMuted = j.LocallyMuted // Update LocallyMuted state from config
if u.Boost < 1 { if u.Boost < 1 {
u.Boost = 1 u.Boost = 1
} }
} }
} }
func (c *Config) UpdateConfig(u *gumble.User) { func (c *Config) UpdateConfig(u *gumble.User) {
var j *eUser var j *eUser
j = c.findUser(u.GetClient().Config.Address, u.Name) j = c.findUser(u.GetClient().Config.Address, u.Name)
j.Boost = u.Boost j.Boost = u.Boost
j.Volume = u.Volume j.Volume = u.Volume
j.LocallyMuted = u.LocallyMuted // Save LocallyMuted state to config j.LocallyMuted = u.LocallyMuted // Save LocallyMuted state to config
} }
func NewConfig(fn *string) *Config { func NewConfig(fn *string) *Config {
var c *Config var c *Config
c = &Config{} c = &Config{}
c.fn = resolvePath(*fn) c.fn = resolvePath(*fn)
c.LoadConfig() c.LoadConfig()
return c return c
} }
func readFile(path string) []byte { func readFile(path string) []byte {
if !fileExists(path) { if !fileExists(path) {
return nil return nil
} }
dat, err := ioutil.ReadFile(path) dat, err := ioutil.ReadFile(path)
if err != nil { if err != nil {
return nil return nil
} }
return dat return dat
} }
func fileExists(path string) bool { func fileExists(path string) bool {
info, err := os.Stat(path) info, err := os.Stat(path)
if os.IsNotExist(err) { if os.IsNotExist(err) {
return false return false
} }
return !info.IsDir() return !info.IsDir()
} }
func resolvePath(path string) string { func resolvePath(path string) string {
if strings.HasPrefix(path, "~/") || strings.Contains(path, "$HOME") { if strings.HasPrefix(path, "~/") || strings.Contains(path, "$HOME") {
usr, err := user.Current() usr, err := user.Current()
if err != nil { if err != nil {
panic(err) panic(err)
} }
var hd = usr.HomeDir var hd = usr.HomeDir
if strings.Contains(path, "$HOME") { if strings.Contains(path, "$HOME") {
path = strings.Replace(path, "$HOME", hd, 1) path = strings.Replace(path, "$HOME", hd, 1)
} else { } else {
path = strings.Replace(path, "~", hd, 1) path = strings.Replace(path, "~", hd, 1)
} }
} }
return path return path
} }
func makeHostPort(addr string) (string, int) { func makeHostPort(addr string) (string, int) {
parts := strings.Split(addr, ":") parts := strings.Split(addr, ":")
host := parts[0] host := parts[0]
port, err := strconv.Atoi(parts[1]) port, err := strconv.Atoi(parts[1])
if err != nil { if err != nil {
panic(err) panic(err)
} }
return host, port return host, port
} }
func Log(s string) { func Log(s string) {
log(s) log(s)
} }
func log(s string) { func log(s string) {
s += "\n" s += "\n"
f, err := os.OpenFile("log.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) f, err := os.OpenFile("log.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
if err != nil { if err != nil {
panic(err) panic(err)
} }
if _, err := f.Write([]byte(s)); err != nil { if _, err := f.Write([]byte(s)); err != nil {
panic(err) panic(err)
} }
if err := f.Close(); err != nil { if err := f.Close(); err != nil {
panic(err) panic(err)
} }
} }

28
main.go
View File

@@ -15,15 +15,15 @@ import (
//"github.com/google/shlex" //"github.com/google/shlex"
"crypto/tls" "crypto/tls"
"flag" "flag"
"github.com/alessio/shellescape"
"git.stormux.org/storm/barnard/audio" "git.stormux.org/storm/barnard/audio"
"git.stormux.org/storm/barnard/config" "git.stormux.org/storm/barnard/config"
"git.stormux.org/storm/barnard/noise" "git.stormux.org/storm/barnard/noise"
"github.com/alessio/shellescape"
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
"git.stormux.org/storm/barnard/gumble/gumble" "git.stormux.org/storm/barnard/gumble/gumble"
_ "git.stormux.org/storm/barnard/gumble/opus" _ "git.stormux.org/storm/barnard/gumble/opus"
"git.stormux.org/storm/barnard/uiterm" "git.stormux.org/storm/barnard/uiterm"
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
) )
func show_devs(name string, args []string) { func show_devs(name string, args []string) {
@@ -109,6 +109,7 @@ func main() {
insecure := flag.Bool("insecure", false, "skip server certificate verification") insecure := flag.Bool("insecure", false, "skip server certificate verification")
certificate := flag.String("certificate", "", "PEM encoded certificate and private key") certificate := flag.String("certificate", "", "PEM encoded certificate and private key")
cfgfn := flag.String("config", "~/.barnard.toml", "Path to TOML formatted configuration file") cfgfn := flag.String("config", "~/.barnard.toml", "Path to TOML formatted configuration file")
audioDriver := flag.String("audio-driver", "", "preferred OpenAL backend (pipewire, pulse, alsa, jack)")
list_devices := flag.Bool("list_devices", false, "do not connect; instead, list available audio devices and exit") list_devices := flag.Bool("list_devices", false, "do not connect; instead, list available audio devices and exit")
fifo := flag.String("fifo", "", "path of a FIFO from which to read commands") fifo := flag.String("fifo", "", "path of a FIFO from which to read commands")
serverSet := false serverSet := false
@@ -149,6 +150,19 @@ func main() {
certificate = userConfig.GetCertificate() certificate = userConfig.GetCertificate()
} }
driver := strings.TrimSpace(*audioDriver)
if driver == "" {
// Environment variable takes precedence over config
if envDriver := os.Getenv("ALSOFT_DRIVERS"); envDriver != "" {
driver = envDriver
} else {
driver = strings.TrimSpace(userConfig.GetAudioDriver())
}
}
if driver != "" {
os.Setenv("ALSOFT_DRIVERS", driver)
}
if os.Getenv("ALSOFT_LOGLEVEL") == "" { if os.Getenv("ALSOFT_LOGLEVEL") == "" {
os.Setenv("ALSOFT_LOGLEVEL", "0") os.Setenv("ALSOFT_LOGLEVEL", "0")
} }
@@ -164,12 +178,12 @@ func main() {
// Initialize // Initialize
b := Barnard{ b := Barnard{
Config: gumble.NewConfig(), Config: gumble.NewConfig(),
UserConfig: userConfig, UserConfig: userConfig,
Address: *server, Address: *server,
MutedChannels: make(map[uint32]bool), MutedChannels: make(map[uint32]bool),
NoiseSuppressor: noise.NewSuppressor(), NoiseSuppressor: noise.NewSuppressor(),
VoiceEffects: audio.NewEffectsProcessor(gumble.AudioSampleRate), VoiceEffects: audio.NewEffectsProcessor(gumble.AudioSampleRate),
} }
b.Config.Buffers = *buffers b.Config.Buffers = *buffers