From e22a16ea5a38bc3a8a2460145c8d1d928b851763 Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 14 Mar 2023 18:08:21 -0700 Subject: [PATCH 1/2] expose MPV ReplayGain options --- player/libmpv.go | 8 ++++++++ player/player.go | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+) diff --git a/player/libmpv.go b/player/libmpv.go index 52e4cde..6ccb973 100644 --- a/player/libmpv.go +++ b/player/libmpv.go @@ -104,6 +104,14 @@ func (m libmpv) SetProperty(name string, format MPVFormat, value any) error { return toMPVError(C.mpv_set_property(m.handle, cname, C.mpv_format(format), p)) } +func (m libmpv) SetPropertyString(name, value string) error { + cname := C.CString(name) + defer C.free(unsafe.Pointer(cname)) + cvalue := C.CString(value) + defer C.free(unsafe.Pointer(cvalue)) + return toMPVError(C.mpv_set_property_string(m.handle, cname, cvalue)) +} + func (m libmpv) GetProperty(name string, format MPVFormat) (any, error) { cname := C.CString(name) defer C.free(unsafe.Pointer(cname)) diff --git a/player/player.go b/player/player.go index 4d119cb..dee4bb0 100644 --- a/player/player.go +++ b/player/player.go @@ -38,12 +38,30 @@ const ( SeekRelativePercent ) +// One of "no", "track", or "album" +type ReplayGainMode string + +const ( + ReplayGainNone ReplayGainMode = "no" + ReplayGainTrack ReplayGainMode = "track" + ReplayGainAlbum ReplayGainMode = "album" +) + +// Replay Gain options (argument to SetReplayGainOptions). +type ReplayGainOptions struct { + Mode ReplayGainMode + FallbackGain float64 + PreampGain float64 +} + // Player encapsulates the mpv instance and provides functions // to control it and to check its status. type Player struct { mpv libmpv initialized bool vol int + replayGainOpts ReplayGainOptions + haveRGainOpts bool status Status seeking bool curPlaylistPos int64 @@ -99,6 +117,9 @@ func (p *Player) Init(audioExclusive bool) error { if audioExclusive { m.SetOptionString("audio-exclusive", "yes") } + if p.haveRGainOpts { + p.SetReplayGainOptions(p.replayGainOpts) + } if p.clientName != "" { m.SetOptionString("audio-client-name", p.clientName) @@ -232,6 +253,26 @@ func (p *Player) SetVolume(vol int) error { return nil } +// Sets the ReplayGain options of the player. +// Unlike most Player functions, SetReplayGainOptions can be called +// before Init, to set the initial volume of the player on startup. +func (p *Player) SetReplayGainOptions(options ReplayGainOptions) error { + p.replayGainOpts = options + p.haveRGainOpts = true + if p.initialized { + if err := p.mpv.SetPropertyString("replaygain", string(options.Mode)); err != nil { + return err + } + if err := p.mpv.SetProperty("replaygain-preamp", MPVFormatDouble, options.PreampGain); err != nil { + return err + } + if err := p.mpv.SetProperty("replaygain-fallback", MPVFormatDouble, options.FallbackGain); err != nil { + return err + } + } + return nil +} + // Gets the current volume of the player. func (p *Player) GetVolume() int { return p.vol From fe5a6ffdf354373ae0c0cddb32977ec992d54f9c Mon Sep 17 00:00:00 2001 From: Drew Weymouth Date: Tue, 28 Mar 2023 18:13:57 -0700 Subject: [PATCH 2/2] hook up replaygain config file options --- backend/app.go | 11 +++++++++++ backend/config.go | 12 ++++++++++++ backend/playbackmanager.go | 6 ++++++ player/player.go | 13 +++++++++---- 4 files changed, 38 insertions(+), 4 deletions(-) diff --git a/backend/app.go b/backend/app.go index 47408e2..030dfee 100644 --- a/backend/app.go +++ b/backend/app.go @@ -9,6 +9,7 @@ import ( "path" "supersonic/backend/util" "supersonic/player" + "supersonic/sharedutil" "github.com/20after4/configdir" "github.com/zalando/go-keyring" @@ -51,6 +52,16 @@ func StartupApp() (*App, error) { a.Config.LocalPlayback.Volume = clamp(a.Config.LocalPlayback.Volume, 0, 100) a.Player.SetVolume(a.Config.LocalPlayback.Volume) + rgainOpts := []string{ReplayGainNone, ReplayGainAlbum, ReplayGainTrack} + if !sharedutil.StringSliceContains(rgainOpts, a.Config.ReplayGain.Mode) { + a.Config.ReplayGain.Mode = ReplayGainNone + } + a.Player.SetReplayGainOptions(player.ReplayGainOptions{ + Mode: player.ReplayGainMode(a.Config.ReplayGain.Mode), + PreventClipping: a.Config.ReplayGain.PreventClipping, + PreampGain: a.Config.ReplayGain.PreampGainDB, + }) + a.ServerManager = NewServerManager() a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling) a.LibraryManager = NewLibraryManager(a.ServerManager) diff --git a/backend/config.go b/backend/config.go index 0454854..edde562 100644 --- a/backend/config.go +++ b/backend/config.go @@ -58,6 +58,12 @@ type ScrobbleConfig struct { ThresholdPercent int } +type ReplayGainConfig struct { + Mode string + PreampGainDB float64 + PreventClipping bool +} + type Config struct { Application AppConfig Servers []*ServerConfig @@ -69,6 +75,7 @@ type Config struct { PlaylistPage PlaylistPageConfig LocalPlayback LocalPlaybackConfig Scrobbling ScrobbleConfig + ReplayGain ReplayGainConfig } func DefaultConfig() *Config { @@ -106,6 +113,11 @@ func DefaultConfig() *Config { ThresholdTimeSeconds: 240, ThresholdPercent: 50, }, + ReplayGain: ReplayGainConfig{ + Mode: ReplayGainNone, + PreampGainDB: 0.0, + PreventClipping: true, + }, } } diff --git a/backend/playbackmanager.go b/backend/playbackmanager.go index 445ddbe..1db3131 100644 --- a/backend/playbackmanager.go +++ b/backend/playbackmanager.go @@ -12,6 +12,12 @@ import ( "github.com/dweymouth/go-subsonic/subsonic" ) +const ( + ReplayGainNone = string(player.ReplayGainNone) + ReplayGainAlbum = string(player.ReplayGainAlbum) + ReplayGainTrack = string(player.ReplayGainTrack) +) + type PlaybackManager struct { ctx context.Context cancelPollPos context.CancelFunc diff --git a/player/player.go b/player/player.go index dee4bb0..7eec375 100644 --- a/player/player.go +++ b/player/player.go @@ -49,9 +49,10 @@ const ( // Replay Gain options (argument to SetReplayGainOptions). type ReplayGainOptions struct { - Mode ReplayGainMode - FallbackGain float64 - PreampGain float64 + Mode ReplayGainMode + PreampGain float64 + PreventClipping bool + // Fallback gain intentionally omitted } // Player encapsulates the mpv instance and provides functions @@ -266,7 +267,11 @@ func (p *Player) SetReplayGainOptions(options ReplayGainOptions) error { if err := p.mpv.SetProperty("replaygain-preamp", MPVFormatDouble, options.PreampGain); err != nil { return err } - if err := p.mpv.SetProperty("replaygain-fallback", MPVFormatDouble, options.FallbackGain); err != nil { + clip := "no" + if options.PreventClipping { + clip = "yes" + } + if err := p.mpv.SetPropertyString("replaygain-clip", clip); err != nil { return err } }