Merge pull request #103 from dweymouth/feature/replay-gain
Add ReplayGain support
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user