125 lines
2.8 KiB
Go
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))
|
|
}
|