diff --git a/README.md b/README.md index 2fa0261..da01b58 100644 --- a/README.md +++ b/README.md @@ -18,26 +18,6 @@ If a user is too soft to hear, you can boost their audio. The audio should drastically increase once you have hit the VolumeUp key over 10 times (from the silent/0 position). The boost setting is saved per user, just like per user volume. -## Voice Effects - -Barnard includes real-time voice effects that can be applied to your outgoing microphone audio. Press F12 to cycle through the available effects. - -### Available Effects -- **None**: No effect applied (default) -- **Echo**: Single repeating delay effect with feedback (250ms) - creates distinct repetitions that fade away -- **Reverb**: Multiple short delays (12.5ms, 20ms, 33ms) without feedback - adds thickness and fullness to your voice -- **High Pitch**: Chipmunk-style voice using pitch shifting -- **Low Pitch**: Deep voice using pitch shifting -- **Robot**: Ring modulation effect for robotic sound -- **Chorus**: Layered voices with slight pitch variations for a rich, ensemble sound - -### Controls -- **F12 key**: Cycle through voice effects (configurable hotkey) -- **Configuration**: Your selected effect is saved in `~/.barnard.toml` - -### How It Works -Voice effects are applied to your outgoing audio in real-time, after noise suppression and automatic gain control. The effects use various digital signal processing techniques including delay lines, pitch shifting with cubic interpolation, and ring modulation. - ## Noise Suppression Barnard includes RNNoise-based real-time noise suppression for microphone input to filter out background noise such as keyboard typing, computer fans, and other environmental sounds. @@ -265,7 +245,6 @@ After running the command above, `barnard` will be compiled as `$(go env GOPATH) - F1: toggle voice transmission - F9: toggle noise suppression - F11: open actions menu for the focused tree item -- F12: cycle through voice effects - Ctrl+R: toggle recording - Ctrl+L: clear chat log - Tab: toggle focus between chat and user tree diff --git a/audio/effects.go b/audio/effects.go deleted file mode 100644 index 81d7abd..0000000 --- a/audio/effects.go +++ /dev/null @@ -1,408 +0,0 @@ -package audio - -import ( - "math" -) - -// VoiceEffect represents different voice effect types -type VoiceEffect int - -const ( - EffectNone VoiceEffect = iota - EffectEcho - EffectReverb - EffectHighPitch - EffectLowPitch - EffectRobot - EffectChorus - EffectCount // Keep this last for cycling -) - -// String returns the name of the effect -func (e VoiceEffect) String() string { - switch e { - case EffectNone: - return "None" - case EffectEcho: - return "Echo" - case EffectReverb: - return "Reverb" - case EffectHighPitch: - return "High Pitch" - case EffectLowPitch: - return "Low Pitch" - case EffectRobot: - return "Robot" - case EffectChorus: - return "Chorus" - default: - return "Unknown" - } -} - -// EffectsProcessor handles voice effects processing -type EffectsProcessor struct { - currentEffect VoiceEffect - enabled bool - - // Echo parameters - echoDelay int // Delay in samples - echoFeedback float32 // Echo feedback amount (0-1) - echoMix float32 // Mix of echo with original (0-1) - echoBuffer []int16 // Circular buffer for echo - echoPosition int // Current position in echo buffer - - // Reverb buffer - reverseInputBuffer []int16 // Delay line for reverb - reverseInputPos int // Write position in buffer - - // Pitch shift parameters - pitchRatio float32 // Pitch shift ratio - pitchBuffer []int16 // Buffer for pitch shifting - pitchPhase float32 // Phase accumulator for resampling - - // Robot voice parameters - robotFreq float32 // Modulation frequency - robotPhase float32 // Phase accumulator - sampleRate float32 // Audio sample rate - - // Chorus parameters - chorusDelays []int // Multiple delay times - chorusBuffers [][]int16 // Multiple delay buffers - chorusPositions []int // Positions in chorus buffers - chorusRates []float32 // LFO rates for each chorus voice - chorusPhases []float32 // LFO phases -} - -// NewEffectsProcessor creates a new voice effects processor -func NewEffectsProcessor(sampleRate int) *EffectsProcessor { - echoDelay := sampleRate / 4 // 250ms delay - - return &EffectsProcessor{ - currentEffect: EffectNone, - enabled: true, - sampleRate: float32(sampleRate), - - // Echo setup - echoDelay: echoDelay, - echoFeedback: 0.4, - echoMix: 0.5, - echoBuffer: make([]int16, echoDelay), - echoPosition: 0, - - // Reverb setup - 100ms buffer for delay lines - reverseInputBuffer: make([]int16, sampleRate/10), // 100ms buffer - reverseInputPos: 0, - - // Pitch shift setup - pitchRatio: 1.0, - pitchBuffer: make([]int16, 4096), - pitchPhase: 0.0, - - // Robot voice setup - robotFreq: 30.0, // 30 Hz modulation - robotPhase: 0.0, - - // Chorus setup (3 voices) - chorusDelays: []int{sampleRate/50, sampleRate/40, sampleRate/35}, // ~20-30ms - chorusBuffers: make([][]int16, 3), - chorusPositions: make([]int, 3), - chorusRates: []float32{1.5, 2.0, 2.3}, // LFO rates in Hz - chorusPhases: make([]float32, 3), - } -} - -// GetCurrentEffect returns the current effect -func (ep *EffectsProcessor) GetCurrentEffect() VoiceEffect { - return ep.currentEffect -} - -// SetEffect sets the current effect -func (ep *EffectsProcessor) SetEffect(effect VoiceEffect) { - if effect >= 0 && effect < EffectCount { - ep.currentEffect = effect - ep.resetBuffers() - } -} - -// CycleEffect cycles to the next effect -func (ep *EffectsProcessor) CycleEffect() VoiceEffect { - ep.currentEffect = (ep.currentEffect + 1) % EffectCount - ep.resetBuffers() - return ep.currentEffect -} - -// SetEnabled enables or disables effects processing -func (ep *EffectsProcessor) SetEnabled(enabled bool) { - ep.enabled = enabled -} - -// IsEnabled returns whether effects are enabled -func (ep *EffectsProcessor) IsEnabled() bool { - return ep.enabled -} - -// ProcessSamples applies the current voice effect to audio samples -func (ep *EffectsProcessor) ProcessSamples(samples []int16) { - if !ep.enabled || ep.currentEffect == EffectNone || len(samples) == 0 { - return - } - - switch ep.currentEffect { - case EffectEcho: - ep.processEcho(samples) - case EffectReverb: - ep.processReverb(samples) - case EffectHighPitch: - ep.processPitchShift(samples, 1.5) - case EffectLowPitch: - ep.processPitchShift(samples, 0.75) - case EffectRobot: - ep.processRobot(samples) - case EffectChorus: - ep.processChorus(samples) - } -} - -// processEcho applies echo effect -func (ep *EffectsProcessor) processEcho(samples []int16) { - for i := range samples { - // Get delayed sample - delayedSample := ep.echoBuffer[ep.echoPosition] - - // Mix original with echo - outputSample := float32(samples[i])*(1.0-ep.echoMix) + - float32(delayedSample)*ep.echoMix - - // Create new echo sample (current + feedback) - newEchoSample := float32(samples[i]) + float32(delayedSample)*ep.echoFeedback - - // Store in buffer with clipping - if newEchoSample > 32767 { - newEchoSample = 32767 - } else if newEchoSample < -32767 { - newEchoSample = -32767 - } - ep.echoBuffer[ep.echoPosition] = int16(newEchoSample) - - // Advance buffer position - ep.echoPosition = (ep.echoPosition + 1) % len(ep.echoBuffer) - - // Apply to output with clipping - if outputSample > 32767 { - outputSample = 32767 - } else if outputSample < -32767 { - outputSample = -32767 - } - samples[i] = int16(outputSample) - } -} - -// processReverb applies reverb effect - like echo but with multiple short delays -func (ep *EffectsProcessor) processReverb(samples []int16) { - bufLen := len(ep.reverseInputBuffer) - - // Three quick echoes instead of one long repeating echo - delays := []int{ - bufLen / 8, // ~12.5ms - bufLen / 5, // ~20ms - bufLen / 3, // ~33ms - } - gains := []float32{0.3, 0.2, 0.15} - - for i := range samples { - // Store current sample - ep.reverseInputBuffer[ep.reverseInputPos] = samples[i] - - // Add the three quick echoes - reverbSample := float32(0) - for j := 0; j < len(delays); j++ { - readPos := (ep.reverseInputPos - delays[j] + bufLen) % bufLen - reverbSample += float32(ep.reverseInputBuffer[readPos]) * gains[j] - } - - // Mix dry and wet signal - outputSample := float32(samples[i])*0.7 + reverbSample - - // Advance position - ep.reverseInputPos = (ep.reverseInputPos + 1) % bufLen - - // Apply with clipping - if outputSample > 32767 { - outputSample = 32767 - } else if outputSample < -32767 { - outputSample = -32767 - } - - samples[i] = int16(outputSample) - } -} - -// processPitchShift applies pitch shifting using cubic interpolation -func (ep *EffectsProcessor) processPitchShift(samples []int16, ratio float32) { - if ratio == 1.0 { - return - } - - bufLen := len(ep.pitchBuffer) - - // Copy samples to pitch buffer (maintaining history) - copy(ep.pitchBuffer[bufLen-len(samples):], samples) - - // Resample using cubic interpolation for smoother output - for i := range samples { - // Calculate source position - srcPos := float32(bufLen-len(samples)) + float32(i)*ratio - - // Bounds check with extra padding for cubic interpolation - if srcPos >= float32(bufLen-2) { - srcPos = float32(bufLen - 3) - } - if srcPos < 1 { - srcPos = 1 - } - - // Cubic interpolation (Hermite interpolation) - idx := int(srcPos) - frac := srcPos - float32(idx) - - // Get 4 samples around the target position - y0 := float32(ep.pitchBuffer[idx-1]) - y1 := float32(ep.pitchBuffer[idx]) - y2 := float32(ep.pitchBuffer[idx+1]) - y3 := float32(ep.pitchBuffer[idx+2]) - - // Cubic Hermite interpolation - c0 := y1 - c1 := 0.5 * (y2 - y0) - c2 := y0 - 2.5*y1 + 2.0*y2 - 0.5*y3 - c3 := 0.5*(y3-y0) + 1.5*(y1-y2) - - interpolated := c0 + c1*frac + c2*frac*frac + c3*frac*frac*frac - - // Soft clipping to reduce harshness - if interpolated > 32767 { - interpolated = 32767 - } else if interpolated < -32767 { - interpolated = -32767 - } - - samples[i] = int16(interpolated) - } - - // Shift buffer for next frame - copy(ep.pitchBuffer, ep.pitchBuffer[len(samples):]) -} - -// processRobot applies ring modulation for robot voice -func (ep *EffectsProcessor) processRobot(samples []int16) { - phaseIncrement := 2.0 * math.Pi * ep.robotFreq / ep.sampleRate - - for i := range samples { - // Generate carrier wave (sine wave) - carrier := float32(math.Sin(float64(ep.robotPhase))) - - // Ring modulation: multiply signal by carrier - modulated := float32(samples[i]) * (0.5 + carrier*0.5) - - // Advance phase - ep.robotPhase += phaseIncrement - if ep.robotPhase >= 2.0*math.Pi { - ep.robotPhase -= 2.0 * math.Pi - } - - // Apply with clipping - if modulated > 32767 { - modulated = 32767 - } else if modulated < -32767 { - modulated = -32767 - } - - samples[i] = int16(modulated) - } -} - -// processChorus applies chorus effect with multiple delayed voices -func (ep *EffectsProcessor) processChorus(samples []int16) { - // Initialize chorus buffers if needed - for j := range ep.chorusBuffers { - if len(ep.chorusBuffers[j]) == 0 { - ep.chorusBuffers[j] = make([]int16, ep.chorusDelays[j]) - } - } - - for i := range samples { - output := float32(samples[i]) * 0.4 // Original signal at 40% - - // Add multiple chorus voices - for j := 0; j < len(ep.chorusDelays); j++ { - // LFO modulation for slight pitch variation - lfoPhaseInc := 2.0 * math.Pi * ep.chorusRates[j] / ep.sampleRate - lfo := float32(math.Sin(float64(ep.chorusPhases[j]))) - ep.chorusPhases[j] += lfoPhaseInc - if ep.chorusPhases[j] >= 2.0*math.Pi { - ep.chorusPhases[j] -= 2.0 * math.Pi - } - - // Get delayed sample with LFO modulation - modDelay := int(float32(ep.chorusDelays[j]) * (1.0 + lfo*0.03)) - if modDelay >= len(ep.chorusBuffers[j]) { - modDelay = len(ep.chorusBuffers[j]) - 1 - } - - readPos := (ep.chorusPositions[j] - modDelay + len(ep.chorusBuffers[j])) % len(ep.chorusBuffers[j]) - delayedSample := ep.chorusBuffers[j][readPos] - - // Add this voice to output (20% each) - output += float32(delayedSample) * 0.2 - - // Store current sample in buffer - ep.chorusBuffers[j][ep.chorusPositions[j]] = samples[i] - ep.chorusPositions[j] = (ep.chorusPositions[j] + 1) % len(ep.chorusBuffers[j]) - } - - // Apply with clipping - if output > 32767 { - output = 32767 - } else if output < -32767 { - output = -32767 - } - - samples[i] = int16(output) - } -} - -// resetBuffers clears all effect buffers -func (ep *EffectsProcessor) resetBuffers() { - // Clear echo buffer - for i := range ep.echoBuffer { - ep.echoBuffer[i] = 0 - } - ep.echoPosition = 0 - - // Clear reverb buffer - for i := range ep.reverseInputBuffer { - ep.reverseInputBuffer[i] = 0 - } - ep.reverseInputPos = 0 - - // Clear pitch buffer - for i := range ep.pitchBuffer { - ep.pitchBuffer[i] = 0 - } - ep.pitchPhase = 0 - - // Reset robot phase - ep.robotPhase = 0 - - // Clear chorus buffers - for j := range ep.chorusBuffers { - if len(ep.chorusBuffers[j]) > 0 { - for i := range ep.chorusBuffers[j] { - ep.chorusBuffers[j][i] = 0 - } - } - ep.chorusPositions[j] = 0 - ep.chorusPhases[j] = 0 - } -} diff --git a/barnard.go b/barnard.go index ad08677..9c4ded3 100644 --- a/barnard.go +++ b/barnard.go @@ -4,7 +4,6 @@ import ( "crypto/tls" "sync" - "git.stormux.org/storm/barnard/audio" "git.stormux.org/storm/barnard/config" "git.stormux.org/storm/barnard/fileplayback" "git.stormux.org/storm/barnard/gumble/gumble" @@ -59,9 +58,6 @@ type Barnard struct { // Added for noise suppression NoiseSuppressor *noise.Suppressor - // Added for voice effects - VoiceEffects *audio.EffectsProcessor - // Added for file playback FileStream *fileplayback.Player FileStreamMutex sync.Mutex diff --git a/client.go b/client.go index 2c09bff..1d5324d 100644 --- a/client.go +++ b/client.go @@ -53,7 +53,6 @@ func (b *Barnard) connect(reconnect bool) bool { b.Stream = stream b.Stream.AttachStream(b.Client) b.Stream.SetNoiseProcessor(b.NoiseSuppressor) - b.Stream.SetEffectsProcessor(b.VoiceEffects) // Initialize stereo encoder for file playback b.Client.AudioEncoderStereo = opus.NewStereoEncoder() diff --git a/config/hotkey_config.go b/config/hotkey_config.go index 120dafc..c4244a1 100644 --- a/config/hotkey_config.go +++ b/config/hotkey_config.go @@ -21,5 +21,4 @@ type Hotkeys struct { ScrollToBottom *uiterm.Key AdminMenu *uiterm.Key NoiseSuppressionToggle *uiterm.Key - CycleVoiceEffect *uiterm.Key } diff --git a/config/user_config.go b/config/user_config.go index 57d2508..ff026ae 100644 --- a/config/user_config.go +++ b/config/user_config.go @@ -27,7 +27,6 @@ type exportableConfig struct { Username *string NotifyCommand *string NoiseSuppressionEnabled *bool - VoiceEffect *int Certificate *string RecordingFormat *string RecordingDirectory *string @@ -83,7 +82,6 @@ func (c *Config) LoadConfig() { ScrollDown: key(uiterm.KeyPgdn), AdminMenu: key(uiterm.KeyF11), NoiseSuppressionToggle: key(uiterm.KeyF9), - CycleVoiceEffect: key(uiterm.KeyF12), } if fileExists(c.fn) { var data []byte @@ -130,10 +128,6 @@ func (c *Config) LoadConfig() { enabled := false jc.NoiseSuppressionEnabled = &enabled } - if c.config.VoiceEffect == nil { - effect := 0 // Default to EffectNone - jc.VoiceEffect = &effect - } if c.config.Certificate == nil { cert := string("") jc.Certificate = &cert @@ -166,7 +160,6 @@ func (c *Config) ensureHotkeys() { ScrollDown: key(uiterm.KeyPgdn), AdminMenu: key(uiterm.KeyF11), NoiseSuppressionToggle: key(uiterm.KeyF9), - CycleVoiceEffect: key(uiterm.KeyF12), } hotkeys := c.config.Hotkeys if hotkeys.Talk == nil { @@ -208,9 +201,6 @@ func (c *Config) ensureHotkeys() { if hotkeys.NoiseSuppressionToggle == nil { hotkeys.NoiseSuppressionToggle = defaults.NoiseSuppressionToggle } - if hotkeys.CycleVoiceEffect == nil { - hotkeys.CycleVoiceEffect = defaults.CycleVoiceEffect - } } func (c *Config) findServer(address string) *server { @@ -319,18 +309,6 @@ func (c *Config) SetNoiseSuppressionEnabled(enabled bool) { c.SaveConfig() } -func (c *Config) GetVoiceEffect() int { - if c.config.VoiceEffect == nil { - return 0 - } - return *c.config.VoiceEffect -} - -func (c *Config) SetVoiceEffect(effect int) { - c.config.VoiceEffect = &effect - c.SaveConfig() -} - func (c *Config) GetRecordingFormat() string { if c.config.RecordingFormat == nil { return "flac" diff --git a/gumble/gumbleopenal/stream.go b/gumble/gumbleopenal/stream.go index f58082f..819f35e 100644 --- a/gumble/gumbleopenal/stream.go +++ b/gumble/gumbleopenal/stream.go @@ -19,12 +19,6 @@ type NoiseProcessor interface { IsEnabled() bool } -// EffectsProcessor interface for voice effects -type EffectsProcessor interface { - ProcessSamples(samples []int16) - IsEnabled() bool -} - // FilePlayer interface for file playback type FilePlayer interface { GetAudioFrame() []int16 @@ -72,15 +66,13 @@ type Stream struct { deviceSink *openal.Device contextSink *openal.Context - noiseProcessor NoiseProcessor - noiseProcessorRight NoiseProcessor - micAGC *audio.AGC - micAGCRight *audio.AGC - effectsProcessor EffectsProcessor - effectsProcessorRight EffectsProcessor - filePlayer FilePlayer - recorderMu sync.RWMutex - recorder Recorder + noiseProcessor NoiseProcessor + noiseProcessorRight NoiseProcessor + micAGC *audio.AGC + micAGCRight *audio.AGC + filePlayer FilePlayer + recorderMu sync.RWMutex + recorder Recorder } func New(client *gumble.Client, inputDevice *string, outputDevice *string, test bool) (*Stream, error) { @@ -153,15 +145,6 @@ func (s *Stream) SetNoiseProcessor(np NoiseProcessor) { s.noiseProcessorRight = cloneNoiseProcessor(np) } -func (s *Stream) SetEffectsProcessor(ep EffectsProcessor) { - s.effectsProcessor = ep - s.effectsProcessorRight = cloneEffectsProcessor(ep) -} - -func (s *Stream) GetEffectsProcessor() EffectsProcessor { - return s.effectsProcessor -} - func (s *Stream) SetFilePlayer(fp FilePlayer) { s.filePlayer = fp } @@ -542,7 +525,7 @@ func scaleForRecording(sample int16, volume float32) int16 { } func (s *Stream) processMonoSamples(samples []int16) { - s.processChannel(samples, s.noiseProcessor, s.micAGC, s.effectsProcessor) + s.processChannel(samples, s.noiseProcessor, s.micAGC) } func (s *Stream) processStereoSamples(samples []int16, frameSize int) { @@ -562,8 +545,8 @@ func (s *Stream) processStereoSamples(samples []int16, frameSize int) { right[i] = samples[idx+1] } - s.processChannel(left, s.noiseProcessor, s.micAGC, s.effectsProcessor) - s.processChannel(right, s.noiseProcessorRight, s.micAGCRight, s.effectsProcessorRight) + s.processChannel(left, s.noiseProcessor, s.micAGC) + s.processChannel(right, s.noiseProcessorRight, s.micAGCRight) for i := 0; i < frameSize; i++ { idx := i * 2 @@ -572,16 +555,13 @@ func (s *Stream) processStereoSamples(samples []int16, frameSize int) { } } -func (s *Stream) processChannel(samples []int16, noiseProcessor NoiseProcessor, micAGC *audio.AGC, effectsProcessor EffectsProcessor) { +func (s *Stream) processChannel(samples []int16, noiseProcessor NoiseProcessor, micAGC *audio.AGC) { if noiseProcessor != nil && noiseProcessor.IsEnabled() { noiseProcessor.ProcessSamples(samples) } if micAGC != nil { micAGC.ProcessSamples(samples) } - if effectsProcessor != nil && effectsProcessor.IsEnabled() { - effectsProcessor.ProcessSamples(samples) - } } func (s *Stream) ensureStereoProcessors() { @@ -591,9 +571,6 @@ func (s *Stream) ensureStereoProcessors() { if s.noiseProcessorRight == nil { s.noiseProcessorRight = cloneNoiseProcessor(s.noiseProcessor) } - if s.effectsProcessorRight == nil { - s.effectsProcessorRight = cloneEffectsProcessor(s.effectsProcessor) - } } func (s *Stream) syncStereoProcessors() { @@ -605,16 +582,6 @@ func (s *Stream) syncStereoProcessors() { } } - leftEffects, leftOk := s.effectsProcessor.(*audio.EffectsProcessor) - rightEffects, rightOk := s.effectsProcessorRight.(*audio.EffectsProcessor) - if leftOk && rightOk { - if leftEffects.IsEnabled() != rightEffects.IsEnabled() { - rightEffects.SetEnabled(leftEffects.IsEnabled()) - } - if leftEffects.GetCurrentEffect() != rightEffects.GetCurrentEffect() { - rightEffects.SetEffect(leftEffects.GetCurrentEffect()) - } - } } func cloneNoiseProcessor(np NoiseProcessor) NoiseProcessor { @@ -628,16 +595,3 @@ func cloneNoiseProcessor(np NoiseProcessor) NoiseProcessor { } return nil } - -func cloneEffectsProcessor(ep EffectsProcessor) EffectsProcessor { - if ep == nil { - return nil - } - if processor, ok := ep.(*audio.EffectsProcessor); ok { - clone := audio.NewEffectsProcessor(gumble.AudioSampleRate) - clone.SetEnabled(processor.IsEnabled()) - clone.SetEffect(processor.GetCurrentEffect()) - return clone - } - return nil -} diff --git a/main.go b/main.go index 07a1e05..cd9637c 100644 --- a/main.go +++ b/main.go @@ -15,7 +15,6 @@ import ( "strings" "syscall" - "git.stormux.org/storm/barnard/audio" "git.stormux.org/storm/barnard/config" "git.stormux.org/storm/barnard/gumble/go-openal/openal" "git.stormux.org/storm/barnard/gumble/gumble" @@ -181,7 +180,6 @@ func main() { Address: *server, MutedChannels: make(map[uint32]bool), NoiseSuppressor: noise.NewSuppressor(), - VoiceEffects: audio.NewEffectsProcessor(gumble.AudioSampleRate), } b.Config.Buffers = *buffers @@ -196,9 +194,6 @@ func main() { } b.NoiseSuppressor.SetEnabled(enabled) - // Configure voice effects - b.VoiceEffects.SetEffect(audio.VoiceEffect(b.UserConfig.GetVoiceEffect())) - b.Config.Username = *username b.Config.Password = *password diff --git a/ui.go b/ui.go index 569a054..c83e31a 100644 --- a/ui.go +++ b/ui.go @@ -109,12 +109,6 @@ func (b *Barnard) OnNoiseSuppressionToggle(ui *uiterm.Ui, key uiterm.Key) { } } -func (b *Barnard) OnVoiceEffectCycle(ui *uiterm.Ui, key uiterm.Key) { - effect := b.VoiceEffects.CycleEffect() - b.UserConfig.SetVoiceEffect(int(effect)) - b.UpdateGeneralStatus(fmt.Sprintf("Voice effect: %s", effect.String()), false) -} - func (b *Barnard) UpdateGeneralStatus(text string, notice bool) { b.statusText = text b.statusNotice = notice @@ -504,7 +498,6 @@ func (b *Barnard) OnUiInitialize(ui *uiterm.Ui) { b.Ui.AddKeyListener(b.OnVoiceToggle, b.Hotkeys.Talk) b.Ui.AddKeyListener(b.OnTimestampToggle, b.Hotkeys.ToggleTimestamps) b.Ui.AddKeyListener(b.OnNoiseSuppressionToggle, b.Hotkeys.NoiseSuppressionToggle) - b.Ui.AddKeyListener(b.OnVoiceEffectCycle, b.Hotkeys.CycleVoiceEffect) b.Ui.AddKeyListener(b.OnRecordingToggle, b.Hotkeys.RecordToggle) b.Ui.AddKeyListener(b.OnQuitPress, b.Hotkeys.Exit) b.Ui.AddKeyListener(b.OnScrollOutputUp, b.Hotkeys.ScrollUp)