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)) }