Added mute option. It can work per user or per channel, so covers deafen functionality too.

This commit is contained in:
Storm Dragon 2025-01-16 13:36:46 -05:00
parent 46d0cbf8f5
commit a5c0e7a71c
5 changed files with 551 additions and 499 deletions

View File

@ -1,19 +1,20 @@
package config
import (
"git.2mb.codes/~cmb/barnard/uiterm"
"git.2mb.codes/~cmb/barnard/uiterm"
)
type Hotkeys struct {
Talk *uiterm.Key
VolumeDown *uiterm.Key
VolumeUp *uiterm.Key
Exit *uiterm.Key
ToggleTimestamps *uiterm.Key
SwitchViews *uiterm.Key
ClearOutput *uiterm.Key
ScrollUp *uiterm.Key
ScrollDown *uiterm.Key
ScrollToTop *uiterm.Key
ScrollToBottom *uiterm.Key
Talk *uiterm.Key
VolumeDown *uiterm.Key
VolumeUp *uiterm.Key
MuteToggle *uiterm.Key
Exit *uiterm.Key
ToggleTimestamps *uiterm.Key
SwitchViews *uiterm.Key
ClearOutput *uiterm.Key
ScrollUp *uiterm.Key
ScrollDown *uiterm.Key
ScrollToTop *uiterm.Key
ScrollToBottom *uiterm.Key
}

View File

@ -1,281 +1,291 @@
package config
import (
"fmt"
"git.2mb.codes/~cmb/barnard/uiterm"
"gopkg.in/yaml.v2"
// "encoding/yaml"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
"io/ioutil"
"os"
"os/user"
"strconv"
"strings"
"fmt"
"git.2mb.codes/~cmb/barnard/uiterm"
"gopkg.in/yaml.v2"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
"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
Hotkeys *Hotkeys
MicVolume *float32
InputDevice *string
OutputDevice *string
Servers []*server
DefaultServer *string
Username *string
NotifyCommand *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
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 := yaml.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 := yaml.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),
Exit: key(uiterm.KeyF10),
ToggleTimestamps: key(uiterm.KeyF3),
SwitchViews: key(uiterm.KeyTab),
ScrollUp: key(uiterm.KeyPgup),
ScrollDown: key(uiterm.KeyPgdn),
}
if fileExists(c.fn) {
var data []byte
data = readFile(c.fn)
if data != nil {
err := yaml.UnmarshalStrict(data, &jc)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing \"%s\".\n%s\n", c.fn, err.Error())
os.Exit(1)
} //panic
} //if data
} //if exists
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("")
jc.NotifyCommand = &ncmd
}
var jc exportableConfig
jc = exportableConfig{}
jc.Hotkeys = &Hotkeys{
Talk: key(uiterm.KeyF1),
VolumeDown: key(uiterm.KeyF5),
VolumeUp: key(uiterm.KeyF6),
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),
}
if fileExists(c.fn) {
var data []byte
data = readFile(c.fn)
if data != nil {
err := yaml.UnmarshalStrict(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("")
jc.NotifyCommand = &ncmd
}
}
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,
}
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()
}
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) 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) 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
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
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"
// If the file doesn't exist, create it, or append to the file
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)
}
}

View File

@ -1,228 +1,235 @@
package gumble
import (
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
"git.2mb.codes/~cmb/go-openal/openal"
"github.com/golang/protobuf/proto"
"git.2mb.codes/~cmb/barnard/gumble/gumble/MumbleProto"
"git.2mb.codes/~cmb/go-openal/openal"
"github.com/golang/protobuf/proto"
)
// User represents a user that is currently connected to the server.
type User struct {
// The user's unique session ID.
Session uint32
// The user's ID. Contains an invalid value if the user is not registered.
UserID uint32
// The user's name.
Name string
// The channel that the user is currently in.
Channel *Channel
// The user's unique session ID.
Session uint32
// The user's ID. Contains an invalid value if the user is not registered.
UserID uint32
// The user's name.
Name string
// The channel that the user is currently in.
Channel *Channel
// Has the user has been muted?
Muted bool
// Has the user been deafened?
Deafened bool
// Has the user been suppressed?
Suppressed bool
// Has the user been muted by him/herself?
SelfMuted bool
// Has the user been deafened by him/herself?
SelfDeafened bool
// Is the user a priority speaker in the channel?
PrioritySpeaker bool
// Is the user recording audio?
Recording bool
// Has the user has been muted?
Muted bool
// Has the user been deafened?
Deafened bool
// Has the user been suppressed?
Suppressed bool
// Has the user been muted by him/herself?
SelfMuted bool
// Has the user been deafened by him/herself?
SelfDeafened bool
// Is the user a priority speaker in the channel?
PrioritySpeaker bool
// Is the user recording audio?
Recording bool
// Has the user been locally muted by the client?
LocallyMuted bool
// The user's comment. Contains the empty string if the user does not have a
// comment, or if the comment needs to be requested.
Comment string
// The user's comment hash. nil if User.Comment has been populated.
CommentHash []byte
// The hash of the user's certificate (can be empty).
Hash string
// The user's texture (avatar). nil if the user does not have a
// texture, or if the texture needs to be requested.
Texture []byte
// The user's texture hash. nil if User.Texture has been populated.
TextureHash []byte
// The user's comment. Contains the empty string if the user does not have a
// comment, or if the comment needs to be requested.
Comment string
// The user's comment hash. nil if User.Comment has been populated.
CommentHash []byte
// The hash of the user's certificate (can be empty).
Hash string
// The user's texture (avatar). nil if the user does not have a
// texture, or if the texture needs to be requested.
Texture []byte
// The user's texture hash. nil if User.Texture has been populated.
TextureHash []byte
// The user's stats. Contains nil if the stats have not yet been requested.
Stats *UserStats
// The user's stats. Contains nil if the stats have not yet been requested.
Stats *UserStats
client *Client
decoder AudioDecoder
client *Client
decoder AudioDecoder
AudioSource *openal.Source
Boost uint16
Volume float32
AudioSource *openal.Source
Boost uint16
Volume float32
}
// IsMuted returns true if the user is muted either server-side or locally
func (u *User) IsMuted() bool {
return u.Muted || u.LocallyMuted
}
func (u *User) GetClient() *Client {
return u.client
return u.client
}
// SetTexture sets the user's texture.
func (u *User) SetTexture(texture []byte) {
packet := MumbleProto.UserState{
Session: &u.Session,
Texture: texture,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
Texture: texture,
}
u.client.Conn.WriteProto(&packet)
}
// SetPrioritySpeaker sets if the user is a priority speaker in the channel.
func (u *User) SetPrioritySpeaker(prioritySpeaker bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
PrioritySpeaker: &prioritySpeaker,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
PrioritySpeaker: &prioritySpeaker,
}
u.client.Conn.WriteProto(&packet)
}
// SetRecording sets if the user is recording audio.
func (u *User) SetRecording(recording bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
Recording: &recording,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
Recording: &recording,
}
u.client.Conn.WriteProto(&packet)
}
// IsRegistered returns true if the user's certificate has been registered with
// the server. A registered user will have a valid user ID.
func (u *User) IsRegistered() bool {
return u.UserID > 0
return u.UserID > 0
}
// Register will register the user with the server. If the client has
// permission to do so, the user will shortly be given a UserID.
func (u *User) Register() {
packet := MumbleProto.UserState{
Session: &u.Session,
UserId: proto.Uint32(0),
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
UserId: proto.Uint32(0),
}
u.client.Conn.WriteProto(&packet)
}
// SetComment will set the user's comment to the given string. The user's
// comment will be erased if the comment is set to the empty string.
func (u *User) SetComment(comment string) {
packet := MumbleProto.UserState{
Session: &u.Session,
Comment: &comment,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
Comment: &comment,
}
u.client.Conn.WriteProto(&packet)
}
// Move will move the user to the given channel.
func (u *User) Move(channel *Channel) {
packet := MumbleProto.UserState{
Session: &u.Session,
ChannelId: &channel.ID,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
ChannelId: &channel.ID,
}
u.client.Conn.WriteProto(&packet)
}
// Kick will kick the user from the server.
func (u *User) Kick(reason string) {
packet := MumbleProto.UserRemove{
Session: &u.Session,
Reason: &reason,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserRemove{
Session: &u.Session,
Reason: &reason,
}
u.client.Conn.WriteProto(&packet)
}
// Ban will ban the user from the server.
func (u *User) Ban(reason string) {
packet := MumbleProto.UserRemove{
Session: &u.Session,
Reason: &reason,
Ban: proto.Bool(true),
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserRemove{
Session: &u.Session,
Reason: &reason,
Ban: proto.Bool(true),
}
u.client.Conn.WriteProto(&packet)
}
// SetMuted sets whether the user can transmit audio or not.
func (u *User) SetMuted(muted bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
Mute: &muted,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
Mute: &muted,
}
u.client.Conn.WriteProto(&packet)
}
// SetSuppressed sets whether the user is suppressed by the server or not.
func (u *User) SetSuppressed(supressed bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
Suppress: &supressed,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
Suppress: &supressed,
}
u.client.Conn.WriteProto(&packet)
}
// SetDeafened sets whether the user can receive audio or not.
func (u *User) SetDeafened(muted bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
Deaf: &muted,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
Deaf: &muted,
}
u.client.Conn.WriteProto(&packet)
}
// SetSelfMuted sets whether the user can transmit audio or not.
//
// This method should only be called on Client.Self().
func (u *User) SetSelfMuted(muted bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
SelfMute: &muted,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
SelfMute: &muted,
}
u.client.Conn.WriteProto(&packet)
}
// SetSelfDeafened sets whether the user can receive audio or not.
//
// This method should only be called on Client.Self().
func (u *User) SetSelfDeafened(muted bool) {
packet := MumbleProto.UserState{
Session: &u.Session,
SelfDeaf: &muted,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
SelfDeaf: &muted,
}
u.client.Conn.WriteProto(&packet)
}
// RequestStats requests that the user's stats be sent to the client.
func (u *User) RequestStats() {
packet := MumbleProto.UserStats{
Session: &u.Session,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserStats{
Session: &u.Session,
}
u.client.Conn.WriteProto(&packet)
}
// RequestTexture requests that the user's actual texture (i.e. non-hashed) be
// sent to the client.
func (u *User) RequestTexture() {
packet := MumbleProto.RequestBlob{
SessionTexture: []uint32{u.Session},
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.RequestBlob{
SessionTexture: []uint32{u.Session},
}
u.client.Conn.WriteProto(&packet)
}
// RequestComment requests that the user's actual comment (i.e. non-hashed) be
// sent to the client.
func (u *User) RequestComment() {
packet := MumbleProto.RequestBlob{
SessionComment: []uint32{u.Session},
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.RequestBlob{
SessionComment: []uint32{u.Session},
}
u.client.Conn.WriteProto(&packet)
}
// Send will send a text message to the user.
func (u *User) Send(message string) {
textMessage := TextMessage{
Users: []*User{u},
Message: message,
}
u.client.Send(&textMessage)
textMessage := TextMessage{
Users: []*User{u},
Message: message,
}
u.client.Send(&textMessage)
}
// SetPlugin sets the user's plugin data.
@ -233,10 +240,10 @@ func (u *User) Send(message string) {
//
// PluginShortName + "\x00" + AdditionalContextInformation
func (u *User) SetPlugin(context []byte, identity string) {
packet := MumbleProto.UserState{
Session: &u.Session,
PluginContext: context,
PluginIdentity: &identity,
}
u.client.Conn.WriteProto(&packet)
packet := MumbleProto.UserState{
Session: &u.Session,
PluginContext: context,
PluginIdentity: &identity,
}
u.client.Conn.WriteProto(&packet)
}

View File

@ -164,7 +164,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
go func(e *gumble.AudioStreamEvent) {
var source = openal.NewSource()
e.User.AudioSource = &source
e.User.AudioSource.SetGain(e.User.Volume)
// Set initial gain based on volume and mute state
if e.User.LocallyMuted {
e.User.AudioSource.SetGain(0)
} else {
e.User.AudioSource.SetGain(e.User.Volume)
}
bufferCount := e.Client.Config.Buffers
if bufferCount < 64 {
@ -183,6 +189,11 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
var raw [maxBufferSize]byte
for packet := range e.C {
// Skip processing if user is locally muted
if e.User.LocallyMuted {
continue
}
var boost uint16 = uint16(1)
samples := len(packet.AudioBuffer)
if samples > cap(raw)/2 {
@ -192,17 +203,13 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
boost = e.User.Boost
// Check if sample count suggests stereo data
// If it's not a multiple of 2, it must be mono
// If it's more than standard frameSize, it's likely stereo
isStereo := samples > gumble.AudioDefaultFrameSize && samples%2 == 0
format := openal.FormatMono16
if isStereo {
format = openal.FormatStereo16
// Adjust samples to represent stereo frame count
samples = samples / 2
}
// Process samples
rawPtr := 0
if isStereo {
// Process stereo samples as pairs
@ -244,7 +251,6 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
buffer := emptyBufs[last]
emptyBufs = emptyBufs[:last]
// Set buffer data with correct format
buffer.SetData(format, raw[:rawPtr], gumble.AudioSampleRate)
source.QueueBuffer(buffer)

View File

@ -1,176 +1,204 @@
package main
import (
//"math"
// "fmt"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
"git.2mb.codes/~cmb/barnard/uiterm"
"sort"
"git.2mb.codes/~cmb/barnard/gumble/gumble"
"git.2mb.codes/~cmb/barnard/uiterm"
"sort"
)
type TreeItem struct {
User *gumble.User
Channel *gumble.Channel
User *gumble.User
Channel *gumble.Channel
}
func (ti TreeItem) String() string {
if ti.User != nil {
return ti.User.Name
}
if ti.Channel != nil {
return "#" + ti.Channel.Name
}
return ""
if ti.User != nil {
if ti.User.LocallyMuted {
return "[MUTED] " + ti.User.Name
}
return ti.User.Name
}
if ti.Channel != nil {
return "#" + ti.Channel.Name
}
return ""
}
func (ti TreeItem) TreeItemStyle(fg, bg uiterm.Attribute, active bool) (uiterm.Attribute, uiterm.Attribute) {
if ti.Channel != nil {
fg |= uiterm.AttrBold
}
if active {
fg, bg = bg, fg
}
return fg, bg
if ti.Channel != nil {
fg |= uiterm.AttrBold
}
if active {
fg, bg = bg, fg
}
return fg, bg
}
func (b *Barnard) TreeItemCharacter(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, ch rune) {
}
func (b *Barnard) changeVolume(users []*gumble.User, change float32) {
for _, u := range users {
au := u.AudioSource
if au == nil {
continue
}
var boost uint16
var cv float32
var ng float32
var curboost float32
curboost = float32((u.Boost - 1)) / 10
cv = au.GetGain() + curboost
ng = cv + change
boost = uint16(1)
//b.AddOutputLine(fmt.Sprintf("cv %.2f change %.2f ng %.2f",cv,change,ng))
if ng > 1.0 {
//1.0 will give volume of one and boost of 1
//1.1 will give volume of 1 and boost of 2
//b.AddOutputLine(fmt.Sprintf("partperc %.2f",(ng*10)))
perc := uint16((ng * 10)) - 10
perc += 1
boost = perc
ng = 1.0
}
if ng < 0 {
ng = 0.0
}
//b.AddOutputLine(fmt.Sprintf("boost %d ng %.2f",boost,ng))
u.Boost = boost
u.Volume = ng
au.SetGain(ng)
b.UserConfig.UpdateConfig(u)
}
b.UserConfig.SaveConfig()
for _, u := range users {
au := u.AudioSource
if au == nil {
continue
}
var boost uint16
var cv float32
var ng float32
var curboost float32
curboost = float32((u.Boost - 1)) / 10
cv = au.GetGain() + curboost
ng = cv + change
boost = uint16(1)
if ng > 1.0 {
perc := uint16((ng * 10)) - 10
perc += 1
boost = perc
ng = 1.0
}
if ng < 0 {
ng = 0.0
}
u.Boost = boost
u.Volume = ng
if !u.LocallyMuted {
au.SetGain(ng)
}
b.UserConfig.UpdateConfig(u)
}
b.UserConfig.SaveConfig()
}
func makeUsersArray(users gumble.Users) []*gumble.User {
t := make([]*gumble.User, 0, len(users))
for _, u := range users {
t = append(t, u)
}
return t
t := make([]*gumble.User, 0, len(users))
for _, u := range users {
t = append(t, u)
}
return t
}
func (b *Barnard) TreeItemKeyPress(ui *uiterm.Ui, tree *uiterm.Tree, item uiterm.TreeItem, key uiterm.Key) {
treeItem := item.(TreeItem)
if key == uiterm.KeyEnter {
if treeItem.Channel != nil {
b.Client.Self.Move(treeItem.Channel)
b.SetSelectedUser(nil)
b.GotoChat()
}
if treeItem.User != nil {
if b.selectedUser == treeItem.User {
b.SetSelectedUser(nil)
b.GotoChat()
} else {
b.SetSelectedUser(treeItem.User)
b.GotoChat()
} //select
} //if user and not selected
} //if enter key
if treeItem.Channel != nil {
var c = treeItem.Channel
if key == *b.Hotkeys.VolumeDown {
b.changeVolume(makeUsersArray(c.Users), -0.1)
}
if key == *b.Hotkeys.VolumeUp {
b.changeVolume(makeUsersArray(c.Users), 0.1)
}
} //set volume
if treeItem.User != nil {
var u = treeItem.User
if key == *b.Hotkeys.VolumeDown {
b.changeVolume([]*gumble.User{u}, -0.1)
}
if key == *b.Hotkeys.VolumeUp {
b.changeVolume([]*gumble.User{u}, 0.1)
}
} //user highlighted
} //func
treeItem := item.(TreeItem)
if key == uiterm.KeyEnter {
if treeItem.Channel != nil {
b.Client.Self.Move(treeItem.Channel)
b.SetSelectedUser(nil)
b.GotoChat()
}
if treeItem.User != nil {
if b.selectedUser == treeItem.User {
b.SetSelectedUser(nil)
b.GotoChat()
} else {
b.SetSelectedUser(treeItem.User)
b.GotoChat()
}
}
}
// Handle mute toggle
if treeItem.Channel != nil {
if key == *b.Hotkeys.MuteToggle {
// Toggle mute for all users in channel
users := makeUsersArray(treeItem.Channel.Users)
for _, u := range users {
b.UserConfig.ToggleMute(u)
if u.AudioSource != nil {
if u.LocallyMuted {
u.AudioSource.SetGain(0)
} else {
u.AudioSource.SetGain(u.Volume)
}
}
}
b.UiTree.Rebuild()
b.Ui.Refresh()
}
if key == *b.Hotkeys.VolumeDown {
b.changeVolume(makeUsersArray(treeItem.Channel.Users), -0.1)
}
if key == *b.Hotkeys.VolumeUp {
b.changeVolume(makeUsersArray(treeItem.Channel.Users), 0.1)
}
}
if treeItem.User != nil {
if key == *b.Hotkeys.MuteToggle {
// Toggle mute for single user
b.UserConfig.ToggleMute(treeItem.User)
if treeItem.User.AudioSource != nil {
if treeItem.User.LocallyMuted {
treeItem.User.AudioSource.SetGain(0)
} else {
treeItem.User.AudioSource.SetGain(treeItem.User.Volume)
}
}
b.UiTree.Rebuild()
b.Ui.Refresh()
}
if key == *b.Hotkeys.VolumeDown {
b.changeVolume([]*gumble.User{treeItem.User}, -0.1)
}
if key == *b.Hotkeys.VolumeUp {
b.changeVolume([]*gumble.User{treeItem.User}, 0.1)
}
}
}
func (b *Barnard) TreeItemBuild(item uiterm.TreeItem) []uiterm.TreeItem {
if b.Client == nil {
return nil
}
if b.Client == nil {
return nil
}
var treeItem TreeItem
if ti, ok := item.(TreeItem); !ok {
root := b.Client.Channels[0]
if root == nil {
return nil
}
return []uiterm.TreeItem{
TreeItem{
Channel: root,
},
}
} else {
treeItem = ti
}
var treeItem TreeItem
if ti, ok := item.(TreeItem); !ok {
root := b.Client.Channels[0]
if root == nil {
return nil
}
return []uiterm.TreeItem{
TreeItem{
Channel: root,
},
}
} else {
treeItem = ti
}
if treeItem.User != nil {
return nil
}
if treeItem.User != nil {
return nil
}
users := []uiterm.TreeItem{}
ul := []*gumble.User{}
for _, user := range treeItem.Channel.Users {
ul = append(ul, user)
var u = ul[len(ul)-1]
_ = u
}
sort.Slice(ul, func(i, j int) bool {
return ul[i].Name < ul[j].Name
})
for _, user := range ul {
users = append(users, TreeItem{
User: user,
})
}
users := []uiterm.TreeItem{}
ul := []*gumble.User{}
for _, user := range treeItem.Channel.Users {
ul = append(ul, user)
var u = ul[len(ul)-1]
_ = u
}
sort.Slice(ul, func(i, j int) bool {
return ul[i].Name < ul[j].Name
})
for _, user := range ul {
users = append(users, TreeItem{
User: user,
})
}
channels := []uiterm.TreeItem{}
cl := []*gumble.Channel{}
for _, subchannel := range treeItem.Channel.Children {
cl = append(cl, subchannel)
}
sort.Slice(cl, func(i, j int) bool {
return cl[i].Name < cl[j].Name
})
for _, subchannel := range cl {
channels = append(channels, TreeItem{
Channel: subchannel,
})
}
channels := []uiterm.TreeItem{}
cl := []*gumble.Channel{}
for _, subchannel := range treeItem.Channel.Children {
cl = append(cl, subchannel)
}
sort.Slice(cl, func(i, j int) bool {
return cl[i].Name < cl[j].Name
})
for _, subchannel := range cl {
channels = append(channels, TreeItem{
Channel: subchannel,
})
}
return append(users, channels...)
return append(users, channels...)
}