From 8fc218ac2d785a96015cb356f7413b01923f8070 Mon Sep 17 00:00:00 2001
From: strNophix <nvdpoel01@gmail.com>
Date: Tue, 22 Mar 2022 00:22:23 +0100
Subject: [PATCH] Added volume controls

---
 pkg/commands.go          | 10 ++++++++++
 pkg/model.go             | 40 ++++++++++++++++++++++++++++++++++------
 pkg/status_bar.go        | 24 +++++++++++++++++-------
 pkg/track_player.go      | 31 ++++++++++++++++++++++++++++---
 pkg/track_player_view.go |  5 ++++-
 5 files changed, 93 insertions(+), 17 deletions(-)

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 {