Fix audio distortion and improve noise suppression.
- Add saturation protection to prevent crackling at high volume boost levels - Implement keyboard click detection using energy spike analysis - Reduce aggressive noise suppression parameters to prevent audio artifacts - Apply stronger suppression specifically for detected keyboard clicks while preserving voice quality 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -226,28 +226,49 @@ func (s *Stream) OnAudioStream(e *gumble.AudioStreamEvent) {
|
||||
if isStereo {
|
||||
// Process stereo samples as pairs
|
||||
for i := 0; i < samples*2; i += 2 {
|
||||
// Process left channel
|
||||
// Process left channel with saturation protection
|
||||
sample := packet.AudioBuffer[i]
|
||||
if boost > 1 {
|
||||
sample = int16((int32(sample) * int32(boost)))
|
||||
boosted := int32(sample) * int32(boost)
|
||||
if boosted > 32767 {
|
||||
sample = 32767
|
||||
} else if boosted < -32767 {
|
||||
sample = -32767
|
||||
} else {
|
||||
sample = int16(boosted)
|
||||
}
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
rawPtr += 2
|
||||
|
||||
// Process right channel
|
||||
// Process right channel with saturation protection
|
||||
sample = packet.AudioBuffer[i+1]
|
||||
if boost > 1 {
|
||||
sample = int16((int32(sample) * int32(boost)))
|
||||
boosted := int32(sample) * int32(boost)
|
||||
if boosted > 32767 {
|
||||
sample = 32767
|
||||
} else if boosted < -32767 {
|
||||
sample = -32767
|
||||
} else {
|
||||
sample = int16(boosted)
|
||||
}
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
rawPtr += 2
|
||||
}
|
||||
} else {
|
||||
// Process mono samples
|
||||
// Process mono samples with saturation protection
|
||||
for i := 0; i < samples; i++ {
|
||||
sample := packet.AudioBuffer[i]
|
||||
if boost > 1 {
|
||||
sample = int16((int32(sample) * int32(boost)))
|
||||
boosted := int32(sample) * int32(boost)
|
||||
if boosted > 32767 {
|
||||
sample = 32767
|
||||
} else if boosted < -32767 {
|
||||
sample = -32767
|
||||
} else {
|
||||
sample = int16(boosted)
|
||||
}
|
||||
}
|
||||
binary.LittleEndian.PutUint16(raw[rawPtr:], uint16(sample))
|
||||
rawPtr += 2
|
||||
|
@@ -20,15 +20,23 @@ type Suppressor struct {
|
||||
prevInput float32
|
||||
prevOutput float32
|
||||
alpha float32
|
||||
|
||||
// Click detection state
|
||||
clickThreshold float32
|
||||
clickDecay float32
|
||||
recentClickEnergy float32
|
||||
}
|
||||
|
||||
// NewSuppressor creates a new noise suppressor
|
||||
func NewSuppressor() *Suppressor {
|
||||
return &Suppressor{
|
||||
enabled: false,
|
||||
threshold: 0.02, // Noise threshold level
|
||||
gainFactor: 0.8, // Gain reduction for noise
|
||||
alpha: 0.95, // High-pass filter coefficient
|
||||
enabled: false,
|
||||
threshold: 0.01, // Reduced noise threshold level for less aggressive filtering
|
||||
gainFactor: 0.9, // Less aggressive gain reduction for noise
|
||||
alpha: 0.98, // More stable high-pass filter coefficient
|
||||
clickThreshold: 0.15, // Threshold for detecting keyboard clicks
|
||||
clickDecay: 0.95, // How quickly click energy decays
|
||||
recentClickEnergy: 0.0, // Tracks recent click activity
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +68,22 @@ func (s *Suppressor) ProcessSamples(samples []int16) {
|
||||
return
|
||||
}
|
||||
|
||||
// Simple noise suppression algorithm
|
||||
// Calculate frame energy for click detection
|
||||
var frameEnergy float32 = 0.0
|
||||
for _, sample := range samples {
|
||||
floatSample := float32(sample) / 32767.0
|
||||
frameEnergy += floatSample * floatSample
|
||||
}
|
||||
frameEnergy = float32(math.Sqrt(float64(frameEnergy / float32(len(samples)))))
|
||||
|
||||
// Detect sudden energy spikes (likely keyboard clicks)
|
||||
energySpike := frameEnergy - s.recentClickEnergy
|
||||
isClick := energySpike > s.clickThreshold && frameEnergy > 0.05
|
||||
|
||||
// Update recent click energy with decay
|
||||
s.recentClickEnergy = s.recentClickEnergy*s.clickDecay + frameEnergy*(1.0-s.clickDecay)
|
||||
|
||||
// Improved noise suppression algorithm
|
||||
for i, sample := range samples {
|
||||
// Convert to float for processing
|
||||
floatSample := float32(sample) / 32767.0
|
||||
@@ -68,30 +91,35 @@ func (s *Suppressor) ProcessSamples(samples []int16) {
|
||||
// Apply high-pass filter for DC removal
|
||||
filtered := s.highPassFilter(floatSample)
|
||||
|
||||
// Calculate signal strength
|
||||
// Calculate signal strength (RMS-like)
|
||||
strength := float32(math.Abs(float64(filtered)))
|
||||
|
||||
// Apply noise gate
|
||||
if strength < s.threshold {
|
||||
// Below threshold - reduce gain
|
||||
filtered *= s.gainFactor
|
||||
// Apply noise gate with smooth transition
|
||||
var gainReduction float32 = 1.0
|
||||
|
||||
// If we detected a click, apply stronger suppression
|
||||
if isClick {
|
||||
gainReduction = s.gainFactor * 0.3 // Much stronger reduction for clicks
|
||||
} else if strength < s.threshold {
|
||||
// Normal noise gate for low-level sounds
|
||||
gainReduction = strength / s.threshold
|
||||
if gainReduction < s.gainFactor {
|
||||
gainReduction = s.gainFactor
|
||||
}
|
||||
}
|
||||
|
||||
// Apply simple spectral subtraction-like effect
|
||||
// If signal is weak, reduce it further
|
||||
if strength < s.threshold * 2 {
|
||||
filtered *= (strength / (s.threshold * 2))
|
||||
// Apply gain reduction
|
||||
processed := filtered * gainReduction
|
||||
|
||||
// Convert back to int16 with proper clipping
|
||||
processedInt := processed * 32767.0
|
||||
if processedInt > 32767 {
|
||||
processedInt = 32767
|
||||
} else if processedInt < -32767 {
|
||||
processedInt = -32767
|
||||
}
|
||||
|
||||
// Convert back to int16
|
||||
processed := filtered * 32767.0
|
||||
if processed > 32767 {
|
||||
processed = 32767
|
||||
} else if processed < -32767 {
|
||||
processed = -32767
|
||||
}
|
||||
|
||||
samples[i] = int16(processed)
|
||||
samples[i] = int16(processedInt)
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user