Files
playback/internal/ui/waveform/waveform.go
ysandler 6165a10481 feat: unified navigation
fix: install script copying build binary
chore: updated readme
2026-01-25 21:49:34 -06:00

125 lines
2.8 KiB
Go

package waveform
import (
"time"
"github.com/charmbracelet/lipgloss"
"playback/internal/audio"
"playback/internal/ui"
)
// Model represents the waveform visualization component
type Model struct {
Width int
Height int
Samples []float64
Position float64 // 0.0 to 1.0
Duration time.Duration
}
// New creates a new waveform model
func New() Model {
return Model{
Height: 3,
}
}
// SetSamples sets the waveform samples
func (m *Model) SetSamples(data *audio.WaveformData) {
if data != nil {
m.Samples = data.Normalized()
}
}
// SetPosition sets the playback position (0.0 to 1.0)
func (m *Model) SetPosition(pos float64) {
m.Position = pos
}
// SetDuration sets the total duration
func (m *Model) SetDuration(d time.Duration) {
m.Duration = d
}
// SetSize sets the component dimensions
func (m *Model) SetSize(width, height int) {
m.Width = width
m.Height = height
}
// View renders the waveform
func (m Model) View() string {
contentWidth := m.Width - 4 // Account for border and padding
if contentWidth < 10 {
return ""
}
// Render waveform with needle position
left, needle, right := RenderWithColors(m.Samples, contentWidth, m.Position)
// Apply styles
waveformLine := lipgloss.JoinHorizontal(
lipgloss.Left,
ui.BaseStyle.Render(left),
ui.NeedleStyle.Render(needle),
ui.BaseStyle.Render(right),
)
// Time markers
startTime := "00:00"
endTime := formatDuration(m.Duration)
currentTime := formatDuration(time.Duration(m.Position * float64(m.Duration)))
timeMarkerWidth := contentWidth - len(startTime) - len(endTime)
if timeMarkerWidth < 0 {
timeMarkerWidth = 0
}
// Calculate current time position
currentTimePos := int(m.Position * float64(contentWidth))
currentTimeWidth := len(currentTime)
// Build time marker line
timeLine := ui.TimestampStyle.Render(startTime)
// Position current time
spaceBefore := currentTimePos - len(startTime) - currentTimeWidth/2
if spaceBefore < 0 {
spaceBefore = 0
}
spaceAfter := contentWidth - len(startTime) - spaceBefore - currentTimeWidth - len(endTime)
if spaceAfter < 0 {
spaceAfter = 0
}
timeLine = lipgloss.JoinHorizontal(
lipgloss.Left,
ui.TimestampStyle.Render(startTime),
lipgloss.NewStyle().Width(spaceBefore).Render(""),
ui.NeedleStyle.Render(currentTime),
lipgloss.NewStyle().Width(spaceAfter).Render(""),
ui.TimestampStyle.Render(endTime),
)
content := lipgloss.JoinVertical(
lipgloss.Left,
waveformLine,
timeLine,
)
// Apply focused border style
style := ui.WaveformFocusedStyle
return style.Width(m.Width - 2).Render(content)
}
func formatDuration(d time.Duration) string {
d = d.Round(time.Second)
m := int(d / time.Minute)
s := int((d % time.Minute) / time.Second)
return string(rune('0'+m/10)) + string(rune('0'+m%10)) + ":" +
string(rune('0'+s/10)) + string(rune('0'+s%10))
}