feat(tui): add interactive 5-band per-user equalizer

This commit is contained in:
Jose Luis Montañes Ojados
2026-01-17 20:25:58 +01:00
parent 0010bc6cf7
commit 711eb148df
5 changed files with 485 additions and 3 deletions

View File

@@ -120,6 +120,9 @@ type Model struct {
showUserView bool
viewUser *UserNode
pokeID uint16 // Target ID for pending poke
// Interactive EQ
eqBandIdx int // 0-4
}
// addLog adds a message to the log panel
@@ -820,6 +823,28 @@ func (m *Model) handleUserViewKeys(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
newVol = 0.0
}
m.audioPlayer.SetUserVolume(u.ID, newVol)
case "right", "l":
// Increase Gain for selected band
current := m.audioPlayer.GetUserGain(u.ID, m.eqBandIdx)
m.audioPlayer.SetUserGain(u.ID, m.eqBandIdx, current+1.0)
case "left", "h":
// Decrease Gain
current := m.audioPlayer.GetUserGain(u.ID, m.eqBandIdx)
m.audioPlayer.SetUserGain(u.ID, m.eqBandIdx, current-1.0)
case "up", "k":
m.eqBandIdx--
if m.eqBandIdx < 0 {
m.eqBandIdx = 4
}
case "down", "j":
m.eqBandIdx++
if m.eqBandIdx > 4 {
m.eqBandIdx = 0
}
}
return m, nil
}
@@ -1412,14 +1437,85 @@ func (m *Model) renderUserView() string {
"--- Audio Settings ---",
fmt.Sprintf("%s %d%%", labelStyle.Render("Volume:"), int(vol*100)),
fmt.Sprintf("%s %s", labelStyle.Render("Local Mute:"), muteStr),
}
// EQ Visualization
var eqGraph []string
if m.audioPlayer != nil {
bands := m.audioPlayer.GetEQBands(u.ID)
if len(bands) > 0 {
eqGraph = append(eqGraph, "", "--- Interactive Equalizer ---", "")
// Render bars for 5 bands
// 0: Bass, 1: Low-Mid, 2: Mid, 3: High-Mid, 4: High
labels := []string{"100Hz", "350Hz", "1kHz", "3kHz", "8kHz"}
for i, val := range bands {
if i >= len(labels) {
break
}
// Get current gain setting
gain := m.audioPlayer.GetUserGain(u.ID, i)
// Scale 0.0-1.0 to bars (width 20)
const maxBars = 20
bars := int(val * maxBars)
if bars > maxBars {
bars = maxBars
}
// Bar characters: █ ▇ ▆ ▅ ▄ ▃ ▂
barStr := ""
if bars > 0 {
barStr = strings.Repeat("█", bars)
}
// Colorize based on intensity
barStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("39")) // Blue default
if val > 0.8 {
barStyle = barStyle.Foreground(lipgloss.Color("196")) // Red clipping
} else if val > 0.5 {
barStyle = barStyle.Foreground(lipgloss.Color("208")) // Orange high
} else if val > 0.2 {
barStyle = barStyle.Foreground(lipgloss.Color("46")) // Green normal
}
// Selection Indicator
selector := " "
labelStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("240")).Width(6)
gainStyle := lipgloss.NewStyle().Foreground(lipgloss.Color("245")).Width(6)
if i == m.eqBandIdx {
selector = "->"
labelStyle = labelStyle.Foreground(lipgloss.Color("226")).Bold(true) // Yellow for selected
gainStyle = gainStyle.Foreground(lipgloss.Color("226"))
}
gainStr := fmt.Sprintf("%+3.0fdB", gain)
line := fmt.Sprintf("%s %s %s | %s",
selector,
labelStyle.Render(labels[i]),
gainStyle.Render(gainStr),
barStyle.Render(fmt.Sprintf("%-20s", barStr)))
eqGraph = append(eqGraph, line)
}
}
}
info = append(info, eqGraph...)
info = append(info,
"",
"--- Menu ---",
"1. Poke",
"2. Toggle Local Mute",
"+/-: Adjust Volume",
"Arrows: Adjust EQ",
"",
"(Press ESC to close)",
}
)
return lipgloss.JoinVertical(lipgloss.Left, info...)
}