feat(tui): add interactive 5-band per-user equalizer
This commit is contained in:
@@ -30,6 +30,9 @@ type Player struct {
|
||||
// map[SenderID] -> AudioQueue
|
||||
userBuffers map[uint16][]int16
|
||||
|
||||
// User EQs (DSP Filters)
|
||||
userEQs map[uint16]*EQChain
|
||||
|
||||
// User settings
|
||||
userSettings map[uint16]*UserSettings
|
||||
bufferMu sync.Mutex
|
||||
@@ -108,6 +111,7 @@ func NewPlayer() (*Player, error) {
|
||||
muted: false,
|
||||
stopChan: make(chan struct{}),
|
||||
userBuffers: make(map[uint16][]int16),
|
||||
userEQs: make(map[uint16]*EQChain),
|
||||
userSettings: make(map[uint16]*UserSettings),
|
||||
}, nil
|
||||
}
|
||||
@@ -171,6 +175,53 @@ func (p *Player) PlayPCM(senderID uint16, samples []int16) {
|
||||
return
|
||||
}
|
||||
|
||||
// Apply EQ Filters if gains are non-zero
|
||||
p.ensureEQ(senderID)
|
||||
|
||||
// Check if any band has gain != 0
|
||||
hasActiveEQ := false
|
||||
if settings, ok := p.userSettings[senderID]; ok && len(settings.Gains) == 5 {
|
||||
for _, g := range settings.Gains {
|
||||
if g != 0 {
|
||||
hasActiveEQ = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Apply filters if needed
|
||||
// Note: We should probably process always if we want smooth transitions,
|
||||
// but for optimization we skip if all 0.
|
||||
// However, skipping might cause clicks if we jump from filtered to non-filtered state abruptly.
|
||||
// For "Pro" audio, always process. For TUI app, let's process if active.
|
||||
if hasActiveEQ {
|
||||
if eq, ok := p.userEQs[senderID]; ok {
|
||||
// Update gains from settings
|
||||
// (Ideally we only do this on change, but doing it here ensures sync)
|
||||
gains := p.userSettings[senderID].Gains
|
||||
for i, g := range gains {
|
||||
eq.SetGain(i, g)
|
||||
}
|
||||
|
||||
// Process in-place (conceptually) - actually implementation creates new slice
|
||||
samples = eq.Process(samples)
|
||||
}
|
||||
} else {
|
||||
// Even if not active, we might want to reset filters if they were active before?
|
||||
// Or just leave them alone.
|
||||
}
|
||||
|
||||
// Calculate EQ bands for visualization
|
||||
// We do this BEFORE appending to buffer to ensure we have visual feedback even if buffer is full/lagging
|
||||
// This is a "fire and forget" calculation for UI
|
||||
bands := CalculateEQBands(samples, 48000)
|
||||
|
||||
// Update user settings with new bands
|
||||
if _, ok := p.userSettings[senderID]; !ok {
|
||||
p.userSettings[senderID] = &UserSettings{Volume: 1.0, Muted: false}
|
||||
}
|
||||
p.userSettings[senderID].EQBands = bands
|
||||
|
||||
// Append to user's specific buffer
|
||||
// This ensures sequential playback for the same user
|
||||
p.userBuffers[senderID] = append(p.userBuffers[senderID], samples...)
|
||||
@@ -249,6 +300,66 @@ func (p *Player) GetUserSettings(clientID uint16) (float32, bool) {
|
||||
return 1.0, false
|
||||
}
|
||||
|
||||
// GetEQBands returns the current 5-band EQ values for a user (0.0-1.0)
|
||||
func (p *Player) GetEQBands(clientID uint16) []float64 {
|
||||
p.bufferMu.Lock()
|
||||
defer p.bufferMu.Unlock()
|
||||
|
||||
if settings, ok := p.userSettings[clientID]; ok {
|
||||
return settings.EQBands
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// SetUserGain sets the EQ gain for a specific band (0-4) and user.
|
||||
// Gain is in dB (e.g. -12.0 to +12.0)
|
||||
func (p *Player) SetUserGain(clientID uint16, bandIdx int, gainDb float64) {
|
||||
p.bufferMu.Lock()
|
||||
defer p.bufferMu.Unlock()
|
||||
|
||||
p.ensureUserSettings(clientID)
|
||||
|
||||
// Ensure Gains slice exists
|
||||
if len(p.userSettings[clientID].Gains) != 5 {
|
||||
p.userSettings[clientID].Gains = make([]float64, 5)
|
||||
}
|
||||
|
||||
if bandIdx >= 0 && bandIdx < 5 {
|
||||
p.userSettings[clientID].Gains[bandIdx] = gainDb
|
||||
}
|
||||
}
|
||||
|
||||
// GetUserGain returns the gain for a band
|
||||
func (p *Player) GetUserGain(clientID uint16, bandIdx int) float64 {
|
||||
p.bufferMu.Lock()
|
||||
defer p.bufferMu.Unlock()
|
||||
|
||||
if settings, ok := p.userSettings[clientID]; ok {
|
||||
if len(settings.Gains) > bandIdx {
|
||||
return settings.Gains[bandIdx]
|
||||
}
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
func (p *Player) ensureUserSettings(clientID uint16) {
|
||||
if _, ok := p.userSettings[clientID]; !ok {
|
||||
p.userSettings[clientID] = &UserSettings{
|
||||
Volume: 1.0,
|
||||
Muted: false,
|
||||
Gains: make([]float64, 5),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) ensureEQ(clientID uint16) {
|
||||
if _, ok := p.userEQs[clientID]; !ok {
|
||||
// New EQ chain
|
||||
// Assume 48000 Hz, would be better to pass actual stream rate
|
||||
p.userEQs[clientID] = NewEQChain(48000)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *Player) playbackLoop() {
|
||||
ticker := time.NewTicker(10 * time.Millisecond)
|
||||
defer ticker.Stop()
|
||||
|
||||
Reference in New Issue
Block a user