From 2ca2b50e838fb950436370aa8855a91076c2839e Mon Sep 17 00:00:00 2001 From: Caleb Allen Date: Wed, 26 Mar 2025 19:56:07 -0400 Subject: [PATCH] got bulk of player api --- Makefile | 2 +- backend/player/player.go | 126 ++++++++++++++++------------------ backend/player/player_test.go | 42 ++++++++++++ backend/player/volume.go | 30 ++++++++ 4 files changed, 134 insertions(+), 66 deletions(-) create mode 100644 backend/player/player_test.go create mode 100644 backend/player/volume.go diff --git a/Makefile b/Makefile index a802458..51f4a62 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ dev: - wails dev -tags webkit2_41 + WEBKIT_DISABLE_DMABUF_RENDERER=1 wails dev -tags webkit2_41 build: wails build diff --git a/backend/player/player.go b/backend/player/player.go index 5e5f753..a49c456 100644 --- a/backend/player/player.go +++ b/backend/player/player.go @@ -4,7 +4,6 @@ import ( "context" "fmt" "os" - "reflect" "time" "github.com/gopxl/beep" @@ -15,15 +14,15 @@ import ( ) type Player struct { - ctx context.Context - state PlayerState - currentFile os.File - format beep.Format - baseStreamer beep.Streamer - resampled beep.Streamer - control beep.Ctrl - volume effects.Volume - queue []string + ctx context.Context + state PlayerState + currentFile *os.File + format beep.Format + baseStreamer beep.Streamer + resampled beep.Streamer + control *beep.Ctrl + volume *effects.Volume + speakerStreamer beep.Streamer } type PlayerState int @@ -34,34 +33,47 @@ const ( Stopped ) -const testQueue = []string{ - "test_data/music_library_test/gnomed.mp3", - "test_data/music_library_test/01 Some Chords.mp3", - "test_data/music_library_test/03 anything.mp3", -} +var speakerSampleRate = beep.SampleRate(44100) func NewPlayer() (*Player, error) { return &Player{ state: Stopped, - baseStreamer: generatorsilence(-1), - // Default sample rate - // TODO: read this from config - fileFormat: beep.Format{}, - queue: testQueue, + baseStreamer: generators.Silence(-1), + format: beep.Format{ + SampleRate: speakerSampleRate, + }, }, nil } func (p *Player) Init(ctx context.Context) error { p.ctx = ctx - p.state = Stopped - p.streamer = generators.Silence(-1) // Initialize speaker // TODO: allow user to change buffer size and speaker sample rate - err := speaker.Init(p.speakerSampleRate, p.speakerSampleRate.N(time.Second/10)) + err := speaker.Init(p.format.SampleRate, p.format.SampleRate.N(time.Second/10)) if err != nil { return fmt.Errorf("failed to initialize speaker %w", err) } + p.updateStreamers(p.baseStreamer, p.format.SampleRate) + + return nil +} + +func (p *Player) updateStreamers(newBaseStreamer beep.Streamer, sr beep.SampleRate) error { + p.baseStreamer = newBaseStreamer + // resample file stream to match speaker + // TODO: variable resample quality + p.resampled = beep.Resample(4, sr, speakerSampleRate, p.baseStreamer) + // wrap in ctrl streamer to allow play/pause + p.control = &beep.Ctrl{Streamer: p.resampled} + // wrap in volume streamer + p.volume = &effects.Volume{ + Streamer: p.control, + Base: 2, + Volume: 0, + Silent: false, + } + p.speakerStreamer = p.volume return nil } @@ -72,40 +84,27 @@ func (p *Player) changeState(desiredState PlayerState) error { } // reads a file and creates a streamer resampled to match the speaker -func (p *Player) CreateStreamerFromFile(filePath string) (beep.Streamer, error) { +func (p *Player) LoadFile(filePath string) error { f, err := os.Open(filePath) if err != nil { - return nil, fmt.Errorf("failed to open file %w", err) + return fmt.Errorf("failed to open file %w", err) } streamer, format, err := mp3.Decode(f) - p.fileFormat = format if err != nil { - return nil, fmt.Errorf("failed to decode mp3 %w", err) + return fmt.Errorf("failed to decode mp3 %w", err) } - // resample file stream to match speaker - // TODO: variable resample quality - resampled := beep.Resample(4, p.fileFormat.SampleRate, p.speakerSampleRate, streamer) - // wrap in ctrl streamer to allow play/pause - ctrl := &beep.Ctrl{Streamer: resampled} - // wrap in volume streamer - volume := &effects.Volume{ - Streamer: ctrl, - Base: 2, - Volume: 0, - Silent: false, - } + p.updateStreamers(streamer, format.SampleRate) - return volume, nil + return nil } -func (p *Player) Play(streamer beep.Streamer) error { - p.streamer = streamer +func (p *Player) Play() error { p.state = Playing // hangs until song finishes playing done := make(chan bool) - speaker.Play(beep.Seq(p.streamer, beep.Callback(func() { + speaker.Play(beep.Seq(p.speakerStreamer, beep.Callback(func() { p.state = Paused //called when streamer finishes done <- true }))) @@ -116,26 +115,16 @@ func (p *Player) Play(streamer beep.Streamer) error { // TODO: reduce dupilcation in pause/resume functions func (p *Player) Pause() error { - ctrl := &beep.Ctrl{} - // checking if current streamer is a ctrl type, if not, wrap it in one - if (reflect.TypeOf(p.streamer) != reflect.TypeOf(beep.Ctrl{})) { - ctrl = &beep.Ctrl{Streamer: p.streamer} - } speaker.Lock() - ctrl.Paused = true + p.control.Paused = true speaker.Unlock() return nil } // TODO: reduce dupilcation in pause/resume functions func (p *Player) Resume() error { - ctrl := &beep.Ctrl{} - // checking if current streamer is a ctrl type, if not, wrap it in one - if (reflect.TypeOf(p.streamer) != reflect.TypeOf(beep.Ctrl{})) { - ctrl = &beep.Ctrl{Streamer: p.streamer} - } speaker.Lock() - ctrl.Paused = false + p.control.Paused = false speaker.Unlock() return nil } @@ -148,20 +137,27 @@ Positive Volume value means increasing volume Negative Volume value means decreasing volume */ // TODO: implement max/ min volume values -func (p *Player) SetVolume() error { - volume := &effects.Volume{ - Streamer: p.streamer, - Base: 2, - Volume: 0, - Silent: false, +func (p *Player) SetVolume(desiredVolume UserVolume) error { + if desiredVolume > MaxUserVol { + p.volume.Silent = false + p.volume.Volume = float64(MaxUserVol.ToPlayerVolume()) + } else if desiredVolume < MinUserVol { + p.volume.Volume = float64(MinUserVol.ToPlayerVolume()) + p.volume.Silent = true + } else { + p.volume.Silent = false + p.volume.Volume = float64(desiredVolume.ToPlayerVolume()) } - p.Play(volume) return nil } +func (p *Player) ChangeVolume(deltaVolume int) error { + return p.SetVolume(p.getUserVolume() + UserVolume(deltaVolume)) +} + +func (p *Player) getUserVolume() UserVolume { + return PlayerVolume(p.volume.Volume).ToUserVolume() +} func (p *Player) MuteToggle() error { - - p.streamer.Silent = !p.Streamer. - p.Play(volume) return nil } diff --git a/backend/player/player_test.go b/backend/player/player_test.go new file mode 100644 index 0000000..4faac30 --- /dev/null +++ b/backend/player/player_test.go @@ -0,0 +1,42 @@ +package player + +import ( + "context" + "testing" +) + +var testQueue = []string{ + "./test_data/music_library_test/other_music/03 PONPONPON.mp3", + "./test_data/music_library_test/01 Some Chords.mp3", + "./test_data/music_library_test/03 anything.mp3", +} + +func TestPlayer(t *testing.T) { + t.Logf("Starting test") + p, err := NewPlayer() + if err != nil { + t.Errorf("could not create player\n%s", err.Error()) + t.Failed() + } + t.Logf("initializing player") + err = p.Init(context.Background()) + if err != nil { + t.Errorf("could not initialize player\n%s", err.Error()) + t.Failed() + } + + for _, track := range testQueue { + t.Logf("loading file: %s", track) + err = p.LoadFile(track) + if err != nil { + t.Errorf("could not load file\n%s\n%s", track, err.Error()) + t.Failed() + } + err = p.Play() + if err != nil { + t.Errorf("could not play file\n%s\n%s", track, err.Error()) + t.Failed() + } + } + +} diff --git a/backend/player/volume.go b/backend/player/volume.go new file mode 100644 index 0000000..1d516a9 --- /dev/null +++ b/backend/player/volume.go @@ -0,0 +1,30 @@ +package player + +type UserVolume int +type PlayerVolume float64 + +const MinUserVol UserVolume = 0 +const MaxUserVol UserVolume = 100 + +const MinPlayerVol PlayerVolume = -10 +const MaxPlayerVol PlayerVolume = 10 + +func (oldVol UserVolume) ToPlayerVolume() PlayerVolume { + var newVol PlayerVolume + + if oldVol >= MinUserVol && oldVol <= MaxUserVol { + ratio := PlayerVolume(oldVol-MinUserVol) / PlayerVolume(MaxUserVol-MinUserVol) + newVol = ratio*(MaxPlayerVol-MinPlayerVol) + MinPlayerVol + } + return newVol +} + +func (oldVolFloat PlayerVolume) ToUserVolume() UserVolume { + var newVol UserVolume + + if oldVolFloat >= MinPlayerVol && oldVolFloat <= MaxPlayerVol { + ratio := (oldVolFloat - MinPlayerVol) / (MaxPlayerVol - MinPlayerVol) + newVol = UserVolume(ratio*PlayerVolume(MaxUserVol-MinUserVol)) + MinUserVol + } + return newVol +}