Add real-time voice effects for outgoing audio

Implements 7 voice effects that can be cycled through with F12:
- None (default)
- Echo: Single repeating delay with feedback (250ms)
- Reverb: Multiple short delays without feedback
- High Pitch: Chipmunk voice using cubic interpolation
- Low Pitch: Deep voice effect
- Robot: Ring modulation for robotic sound
- Chorus: Layered voices with pitch variations

The effects are applied after noise suppression and AGC in the audio
pipeline. Selected effect is persisted to config file. Includes
comprehensive documentation in README.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Storm Dragon
2025-10-13 16:27:08 -04:00
parent 82b308000d
commit f96cb1f79b
9 changed files with 493 additions and 8 deletions

View File

@@ -17,6 +17,12 @@ type NoiseProcessor interface {
IsEnabled() bool
}
// EffectsProcessor interface for voice effects
type EffectsProcessor interface {
ProcessSamples(samples []int16)
IsEnabled() bool
}
const (
maxBufferSize = 11520 // Max frame size (2880) * bytes per stereo sample (4)
)
@@ -49,9 +55,10 @@ type Stream struct {
deviceSink *openal.Device
contextSink *openal.Context
noiseProcessor NoiseProcessor
micAGC *audio.AGC
noiseProcessor NoiseProcessor
micAGC *audio.AGC
effectsProcessor EffectsProcessor
}
func New(client *gumble.Client, inputDevice *string, outputDevice *string, test bool) (*Stream, error) {
@@ -112,6 +119,14 @@ func (s *Stream) SetNoiseProcessor(np NoiseProcessor) {
s.noiseProcessor = np
}
func (s *Stream) SetEffectsProcessor(ep EffectsProcessor) {
s.effectsProcessor = ep
}
func (s *Stream) GetEffectsProcessor() EffectsProcessor {
return s.effectsProcessor
}
func (s *Stream) Destroy() {
if s.link != nil {
@@ -342,12 +357,17 @@ func (s *Stream) sourceRoutine(inputDevice *string) {
if s.noiseProcessor != nil && s.noiseProcessor.IsEnabled() {
s.noiseProcessor.ProcessSamples(int16Buffer)
}
// Apply AGC to outgoing microphone audio (always enabled)
if s.micAGC != nil {
s.micAGC.ProcessSamples(int16Buffer)
}
// Apply voice effects if available and enabled
if s.effectsProcessor != nil && s.effectsProcessor.IsEnabled() {
s.effectsProcessor.ProcessSamples(int16Buffer)
}
outgoing <- gumble.AudioBuffer(int16Buffer)
}
}