diff --git a/pkg/commands.go b/pkg/commands.go index 76218c2..66bd251 100644 --- a/pkg/commands.go +++ b/pkg/commands.go @@ -21,3 +21,13 @@ func newTrackPauseCmd(isPaused bool) tea.Cmd { return trackPauseMsg{isPaused} } } + +type trackVolumeMsg struct { + volume float64 +} + +func newTrackVolumeCmd(volume float64) tea.Cmd { + return func() tea.Msg { + return trackVolumeMsg{volume} + } +} diff --git a/pkg/model.go b/pkg/model.go index c94cd3d..f39e1c7 100644 --- a/pkg/model.go +++ b/pkg/model.go @@ -21,6 +21,7 @@ type Model struct { currentlyPlaying int TrackPlayer + TrackPlayerEffects trackIndex trackPlayerView @@ -34,8 +35,9 @@ func NewModel(args ModelArgs) Model { cursor: 0, currentlyPlaying: 0, - trackIndex: ti, - TrackPlayer: TrackPlayer{}, + trackIndex: ti, + TrackPlayer: TrackPlayer{}, + TrackPlayerEffects: newTrackPlayerEffects(), trackPlayerView: tpv, } @@ -59,16 +61,42 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { return m, tea.Quit case "enter": t := m.trackPlayerView.trackList.SelectedItem().(track) - cmds = append(cmds, newTrackChangeCmd(t)) + stream, format, err := t.GetStream() check(err) + speaker.Init(format.SampleRate, format.SampleRate.N(time.Second/10)) - m.TrackPlayer.Play(&stream) + + m.TrackPlayer.Play(&stream, &m.TrackPlayerEffects) + cmds = append(cmds, newTrackChangeCmd(t)) case " ": - pauseState := m.TrackPlayer.TogglePause() - cmds = append(cmds, newTrackPauseCmd(pauseState)) + if m.TrackPlayer.playerCtrl != nil { + s := m.TrackPlayer.TogglePause() + cmds = append(cmds, newTrackPauseCmd(s)) + } + case "-", "=": + v := &m.TrackPlayerEffects.volume + + if msg.String() == "-" { + *v -= 0.1 + if *v < minVolume { + *v = minVolume + } + } else { + *v += 0.1 + if *v > maxVolume { + *v = maxVolume + } + } + + if m.TrackPlayer.playerVol != nil { + m.TrackPlayer.SetVolume(*v) + } + + cmds = append(cmds, newTrackVolumeCmd(*v)) } } + var cmd tea.Cmd m.trackPlayerView, cmd = m.trackPlayerView.Update(msg) cmds = append(cmds, cmd) diff --git a/pkg/status_bar.go b/pkg/status_bar.go index 05ccb04..1be0e66 100644 --- a/pkg/status_bar.go +++ b/pkg/status_bar.go @@ -1,6 +1,8 @@ package gomus import ( + "fmt" + tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" ) @@ -14,18 +16,18 @@ var ( Inherit(statusBarStyle). Foreground(lipgloss.Color("#FFFDF5")). Background(lipgloss.Color("#FF5F87")). - Padding(0, 1). - MarginRight(1) + Padding(0, 1) statusText = lipgloss.NewStyle().Inherit(statusBarStyle) ) type statusBar struct { - isPaused bool - currentTrack track + isPaused bool + currentTrack track + currentVolume float64 } -func (s statusBar) Init() tea.Cmd { +func (s *statusBar) Init() tea.Cmd { return nil } @@ -39,16 +41,22 @@ func (s statusBar) View() string { statusKey = statusStyle.Render("") } + v := MapFloatBetween(s.currentVolume, minVolume, maxVolume, 0, 100) + vs := fmt.Sprintf("vol %d", int(v)) + statusVal := statusText.Copy(). - Width(termWidth - w(statusKey)). + Width(termWidth - w(statusKey) - w(vs) - 2). Render(s.currentTrack.fullName()) + statusVol := statusStyle.Copy().Align(lipgloss.Right).Width(w(vs) + 2).Render(vs) + bar := lipgloss.JoinHorizontal(lipgloss.Top, statusKey, statusVal, + statusVol, ) - return statusBarStyle.Width(termWidth).Render(bar) + return statusBarStyle.Render(bar) } func (s statusBar) Update(msg tea.Msg) (statusBar, tea.Cmd) { @@ -57,6 +65,8 @@ func (s statusBar) Update(msg tea.Msg) (statusBar, tea.Cmd) { s.currentTrack = msg.nextTrack case trackPauseMsg: s.isPaused = msg.isPaused + case trackVolumeMsg: + s.currentVolume = msg.volume } return s, nil diff --git a/pkg/track_player.go b/pkg/track_player.go index 4c60db0..7168396 100644 --- a/pkg/track_player.go +++ b/pkg/track_player.go @@ -2,24 +2,43 @@ package gomus import ( "github.com/faiface/beep" + "github.com/faiface/beep/effects" "github.com/faiface/beep/speaker" ) +const ( + base = 2 + minVolume = -5 + maxVolume = 1 + startVolume = -2 +) + +type TrackPlayerEffects struct { + volume float64 +} + +func newTrackPlayerEffects() TrackPlayerEffects { + return TrackPlayerEffects{volume: startVolume} +} + type TrackPlayer struct { streamer *beep.StreamSeekCloser playerCtrl *beep.Ctrl + playerVol *effects.Volume } -func (t *TrackPlayer) Play(streamer *beep.StreamSeekCloser) { +func (t *TrackPlayer) Play(streamer *beep.StreamSeekCloser, trackEffects *TrackPlayerEffects) { if t.streamer != nil { t.Close() } ctrl := &beep.Ctrl{Streamer: beep.Loop(-1, *streamer), Paused: false} - speaker.Play(ctrl) + volume := &effects.Volume{Streamer: ctrl, Base: base, Volume: trackEffects.volume, Silent: false} + speaker.Play(volume) - t.playerCtrl = ctrl t.streamer = streamer + t.playerCtrl = ctrl + t.playerVol = volume } func (t *TrackPlayer) TogglePause() bool { @@ -30,6 +49,12 @@ func (t *TrackPlayer) TogglePause() bool { return newState } +func (t *TrackPlayer) SetVolume(volume float64) { + speaker.Lock() + (*t.playerVol).Volume = volume + speaker.Unlock() +} + func (t *TrackPlayer) Close() { (*t.streamer).Close() } diff --git a/pkg/track_player_view.go b/pkg/track_player_view.go index d8ff308..87e2cb7 100644 --- a/pkg/track_player_view.go +++ b/pkg/track_player_view.go @@ -15,13 +15,16 @@ func newTrackPlayerView(tracks []track) trackPlayerView { c := mapList(tracks, func(t track) list.Item { return t }) + l := list.New(c, newTrackListDelegate(), 0, 0) l.SetShowTitle(false) l.SetShowStatusBar(false) l.SetShowHelp(false) l.SetFilteringEnabled(false) - return trackPlayerView{trackList: l} + s := statusBar{currentVolume: startVolume} + + return trackPlayerView{trackList: l, statusBar: s} } func (v trackPlayerView) Init() tea.Cmd {