From 010e8d73f7217cd2e60e6c888c43647904fb075e Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Tue, 8 Mar 2022 15:10:06 +0100 Subject: [PATCH] Extended ReplayGain normalization --- README.md | 2 +- example_config.toml | 8 +++++++ go.mod | 3 ++- go.sum | 6 +++-- queue.go | 58 ++++++++++++++++++++++----------------------- 5 files changed, 43 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 0158630..b168e6f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Radio streamer ([kawa](https://github.com/Luminarys/kawa) drop-in compatible). * Use `queue.buffer_size` to specify number of seconds to buffer (by default 0, automatic per client). * Implements `queue.nr` and `/random` (to be deprecated/changed) * Supports extra encoder bitrate control settings (CBR, VBR, auto, etc.) -* Can read and apply ReplayGain tags. +* Can read and apply ReplayGain tags, or normalize audio loudness. * Can have audio sources over HTTP(s) URLs on `path` property, and supports seeking. # Future improvements diff --git a/example_config.toml b/example_config.toml index 220f648..a77a317 100644 --- a/example_config.toml +++ b/example_config.toml @@ -43,6 +43,14 @@ fallback="/tmp/in.flac" buffer_duration=0 # # Apply replaygain track tags if existent on files played. +# If ReplayGain tags are not existent, it will normalize audio with a running weighted window of 5 seconds. +# ReplayGain tag overrides can be added as properties on track blobs: +# { +# "replay_gain" : { +# "track_peak": 1.0000, +# "track_gain": -3.15821 +# } +# } replaygain=false [radio] diff --git a/go.mod b/go.mod index 61324de..b2ceaf9 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,14 @@ module git.gammaspectra.live/S.O.N.G/MeteorLight go 1.18 require ( - git.gammaspectra.live/S.O.N.G/Kirika v0.0.0-20220306150518-7aa672a6166d + git.gammaspectra.live/S.O.N.G/Kirika v0.0.0-20220308124222-54bb437c0b50 github.com/BurntSushi/toml v1.0.0 github.com/dhowden/tag v0.0.0-20201120070457-d52dcb253c63 github.com/enriquebris/goconcurrentqueue v0.6.3 ) require ( + git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308113719-afad5c6e5c28 // indirect git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48 // indirect git.gammaspectra.live/S.O.N.G/go-pus v0.0.0-20220227175608-6cc027f24dba // indirect git.gammaspectra.live/S.O.N.G/go-tta v0.2.1-0.20220226150007-096de1072bd6 // indirect diff --git a/go.sum b/go.sum index 776618b..6758e3b 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ -git.gammaspectra.live/S.O.N.G/Kirika v0.0.0-20220306150518-7aa672a6166d h1:7Vys7abOvnEeGinIsEGu0KWFZUqQxOcwARBYDAUnJcQ= -git.gammaspectra.live/S.O.N.G/Kirika v0.0.0-20220306150518-7aa672a6166d/go.mod h1:slLvZqRcR9yMu3Ety7AKzyxu87tHfUKR49ae83sCAM8= +git.gammaspectra.live/S.O.N.G/Kirika v0.0.0-20220308124222-54bb437c0b50 h1:mncq7NhkVifcjIuNZEKWQ3QtuZNopnP4MWQhAaqLeVM= +git.gammaspectra.live/S.O.N.G/Kirika v0.0.0-20220308124222-54bb437c0b50/go.mod h1:S3VhlpN5phBm/HfYqxh9Ik7ZsWj2EAO4+ZgAwX8wAk0= +git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308113719-afad5c6e5c28 h1:7YLU2eyGBX8juV445KlBxW71NjFAzbRvfotZBUP16Bs= +git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308113719-afad5c6e5c28/go.mod h1:5H4eVW9uknpn8REFr+C3ejhvXdncgm/pbGqKGC43gFY= git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48 h1:MaKiBfXQl0keyfdCi1PxGOKRTiWhIs8PqCal5GhKDi0= git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48/go.mod h1:pkWt//S9hLVEQaJDPu/cHHPk8vPpo/0+zHy0me4LIP4= git.gammaspectra.live/S.O.N.G/go-pus v0.0.0-20220227175608-6cc027f24dba h1:JEaxCVgdr3XXAuDCPAx7ttLFZaaHzTEzG+oRnVUtUKU= diff --git a/queue.go b/queue.go index 95963e7..2c80983 100644 --- a/queue.go +++ b/queue.go @@ -6,10 +6,10 @@ import ( "git.gammaspectra.live/S.O.N.G/Kirika/audio" "git.gammaspectra.live/S.O.N.G/Kirika/audio/format/guess" "git.gammaspectra.live/S.O.N.G/Kirika/audio/packetizer" + "git.gammaspectra.live/S.O.N.G/Kirika/audio/replaygain" "github.com/dhowden/tag" "io" "log" - "math" "net/http" "os" "runtime" @@ -25,18 +25,17 @@ const maxBufferSize = 10 type QueueTrackEntry struct { QueueIdentifier audio.QueueIdentifier Path string - ReplayGain struct { - Apply bool - TrackPeak float64 - TrackGain float64 - AlbumPeak float64 - AlbumGain float64 - } - Metadata struct { - Title string `json:"title"` - Album string `json:"album"` - Artist string `json:"artist"` - Art string `json:"art"` + Metadata struct { + Title string `json:"title"` + Album string `json:"album"` + Artist string `json:"artist"` + Art string `json:"art"` + ReplayGain struct { + TrackPeak float64 `json:"track_peak"` + TrackGain float64 `json:"track_gain"` + AlbumPeak float64 `json:"album_peak"` + AlbumGain float64 `json:"album_gain"` + } `json:"replay_gain,omitempty"` } reader io.ReadSeekCloser source audio.Source @@ -117,29 +116,30 @@ func (e *QueueTrackEntry) Load() error { var value interface{} var ok bool - if !e.ReplayGain.Apply { + getDb := func(strValue string) (ret float64) { + ret, _ = strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(strings.ToLower(strValue), "db")), 64) + return + } + + if e.Metadata.ReplayGain.TrackPeak == 0 { if value, ok = tags["replaygain_track_gain"]; ok { if strValue, ok = value.(string); ok { - if e.ReplayGain.TrackGain, err = strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(strValue, "dB")), 64); err == nil { - e.ReplayGain.Apply = true - } + e.Metadata.ReplayGain.TrackGain = getDb(strValue) } } if value, ok = tags["replaygain_track_peak"]; ok { if strValue, ok = value.(string); ok { - if e.ReplayGain.TrackPeak, err = strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(strValue, "dB")), 64); err == nil { - e.ReplayGain.Apply = true - } + e.Metadata.ReplayGain.TrackPeak = getDb(strValue) } } if value, ok = tags["replaygain_album_gain"]; ok { if strValue, ok = value.(string); ok { - e.ReplayGain.AlbumGain, _ = strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(strValue, "dB")), 64) + e.Metadata.ReplayGain.AlbumGain = getDb(strValue) } } if value, ok = tags["replaygain_album_peak"]; ok { if strValue, ok = value.(string); ok { - e.ReplayGain.AlbumPeak, _ = strconv.ParseFloat(strings.TrimSpace(strings.TrimSuffix(strValue, "dB")), 64) + e.Metadata.ReplayGain.AlbumPeak = getDb(strValue) } } } @@ -250,14 +250,12 @@ func (q *Queue) AddTrack(entry *QueueTrackEntry, tail bool) error { defer q.mutex.Unlock() source := entry.source - if q.config.Queue.ReplayGain && entry.ReplayGain.Apply { - const ReplayGainPreamp = 0 //in dB - volume := math.Pow(10, (entry.ReplayGain.TrackGain+ReplayGainPreamp)/20) - - //prevent clipping - volume = math.Min(volume, 1/entry.ReplayGain.TrackPeak) - - source = audio.NewVolumeFilter(float32(volume)).Process(source) + if q.config.Queue.ReplayGain { + if entry.Metadata.ReplayGain.TrackPeak != 0 { + source = replaygain.NewReplayGainFilter(entry.Metadata.ReplayGain.TrackGain, entry.Metadata.ReplayGain.TrackPeak, 0).Process(source) + } else { + source = replaygain.NewNormalizationFilter(5).Process(source) + } } if tail {