Merge pull request #103 from dweymouth/feature/replay-gain

Add ReplayGain support
This commit is contained in:
Drew Weymouth
2023-03-28 18:24:02 -07:00
committed by GitHub
5 changed files with 83 additions and 0 deletions
+11
View File
@@ -9,6 +9,7 @@ import (
"path" "path"
"supersonic/backend/util" "supersonic/backend/util"
"supersonic/player" "supersonic/player"
"supersonic/sharedutil"
"github.com/20after4/configdir" "github.com/20after4/configdir"
"github.com/zalando/go-keyring" "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.Config.LocalPlayback.Volume = clamp(a.Config.LocalPlayback.Volume, 0, 100)
a.Player.SetVolume(a.Config.LocalPlayback.Volume) 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.ServerManager = NewServerManager()
a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling) a.PlaybackManager = NewPlaybackManager(a.bgrndCtx, a.ServerManager, a.Player, &a.Config.Scrobbling)
a.LibraryManager = NewLibraryManager(a.ServerManager) a.LibraryManager = NewLibraryManager(a.ServerManager)
+12
View File
@@ -59,6 +59,12 @@ type ScrobbleConfig struct {
ThresholdPercent int ThresholdPercent int
} }
type ReplayGainConfig struct {
Mode string
PreampGainDB float64
PreventClipping bool
}
type Config struct { type Config struct {
Application AppConfig Application AppConfig
Servers []*ServerConfig Servers []*ServerConfig
@@ -70,6 +76,7 @@ type Config struct {
PlaylistPage PlaylistPageConfig PlaylistPage PlaylistPageConfig
LocalPlayback LocalPlaybackConfig LocalPlayback LocalPlaybackConfig
Scrobbling ScrobbleConfig Scrobbling ScrobbleConfig
ReplayGain ReplayGainConfig
} }
func DefaultConfig() *Config { func DefaultConfig() *Config {
@@ -108,6 +115,11 @@ func DefaultConfig() *Config {
ThresholdTimeSeconds: 240, ThresholdTimeSeconds: 240,
ThresholdPercent: 50, ThresholdPercent: 50,
}, },
ReplayGain: ReplayGainConfig{
Mode: ReplayGainNone,
PreampGainDB: 0.0,
PreventClipping: true,
},
} }
} }
+6
View File
@@ -12,6 +12,12 @@ import (
"github.com/dweymouth/go-subsonic/subsonic" "github.com/dweymouth/go-subsonic/subsonic"
) )
const (
ReplayGainNone = string(player.ReplayGainNone)
ReplayGainAlbum = string(player.ReplayGainAlbum)
ReplayGainTrack = string(player.ReplayGainTrack)
)
type PlaybackManager struct { type PlaybackManager struct {
ctx context.Context ctx context.Context
cancelPollPos context.CancelFunc cancelPollPos context.CancelFunc
+8
View File
@@ -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)) 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) { func (m libmpv) GetProperty(name string, format MPVFormat) (any, error) {
cname := C.CString(name) cname := C.CString(name)
defer C.free(unsafe.Pointer(cname)) defer C.free(unsafe.Pointer(cname))
+46
View File
@@ -38,12 +38,31 @@ const (
SeekRelativePercent 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
PreampGain float64
PreventClipping bool
// Fallback gain intentionally omitted
}
// Player encapsulates the mpv instance and provides functions // Player encapsulates the mpv instance and provides functions
// to control it and to check its status. // to control it and to check its status.
type Player struct { type Player struct {
mpv libmpv mpv libmpv
initialized bool initialized bool
vol int vol int
replayGainOpts ReplayGainOptions
haveRGainOpts bool
status Status status Status
seeking bool seeking bool
curPlaylistPos int64 curPlaylistPos int64
@@ -102,6 +121,9 @@ func (p *Player) Init(audioExclusive bool, maxCacheMB int) error {
if audioExclusive { if audioExclusive {
m.SetOptionString("audio-exclusive", "yes") m.SetOptionString("audio-exclusive", "yes")
} }
if p.haveRGainOpts {
p.SetReplayGainOptions(p.replayGainOpts)
}
if p.clientName != "" { if p.clientName != "" {
m.SetOptionString("audio-client-name", p.clientName) m.SetOptionString("audio-client-name", p.clientName)
@@ -235,6 +257,30 @@ func (p *Player) SetVolume(vol int) error {
return nil 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
}
clip := "no"
if options.PreventClipping {
clip = "yes"
}
if err := p.mpv.SetPropertyString("replaygain-clip", clip); err != nil {
return err
}
}
return nil
}
// Gets the current volume of the player. // Gets the current volume of the player.
func (p *Player) GetVolume() int { func (p *Player) GetVolume() int {
return p.vol return p.vol