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.
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
You can see the below keystrokes in your config file.

View File

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

30
main.go
View File

@@ -15,15 +15,15 @@ import (
//"github.com/google/shlex"
"crypto/tls"
"flag"
"github.com/alessio/shellescape"
"git.stormux.org/storm/barnard/audio"
"git.stormux.org/storm/barnard/config"
"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/opus"
"git.stormux.org/storm/barnard/uiterm"
"git.stormux.org/storm/barnard/gumble/go-openal/openal"
)
func show_devs(name string, args []string) {
@@ -109,6 +109,7 @@ func main() {
insecure := flag.Bool("insecure", false, "skip server certificate verification")
certificate := flag.String("certificate", "", "PEM encoded certificate and private key")
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")
fifo := flag.String("fifo", "", "path of a FIFO from which to read commands")
serverSet := false
@@ -149,6 +150,19 @@ func main() {
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") == "" {
os.Setenv("ALSOFT_LOGLEVEL", "0")
}
@@ -164,18 +178,18 @@ func main() {
// Initialize
b := Barnard{
Config: gumble.NewConfig(),
UserConfig: userConfig,
Address: *server,
MutedChannels: make(map[uint32]bool),
Config: gumble.NewConfig(),
UserConfig: userConfig,
Address: *server,
MutedChannels: make(map[uint32]bool),
NoiseSuppressor: noise.NewSuppressor(),
VoiceEffects: audio.NewEffectsProcessor(gumble.AudioSampleRate),
VoiceEffects: audio.NewEffectsProcessor(gumble.AudioSampleRate),
}
b.Config.Buffers = *buffers
b.Hotkeys = b.UserConfig.GetHotkeys()
b.UserConfig.SaveConfig()
// Configure noise suppression
enabled := b.UserConfig.GetNoiseSuppressionEnabled()
if *noiseSuppressionEnabled {