Extended ReplayGain normalization
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
DataHoarder 2022-03-08 15:10:06 +01:00
parent 8927e7b1a6
commit 010e8d73f7
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
5 changed files with 43 additions and 34 deletions

View file

@ -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

View file

@ -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]

3
go.mod
View file

@ -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

6
go.sum
View file

@ -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=

View file

@ -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 {