Added ReplayGain 2.0 track/album calculator
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2022-03-08 12:13:55 +01:00
parent 6729f7ef38
commit 4f8f274a09
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
6 changed files with 230 additions and 1 deletions

View file

@ -13,7 +13,7 @@ steps:
image: golang:1.18rc1-bullseye
commands:
- DEBIAN_FRONTEND=noninteractive apt update
- DEBIAN_FRONTEND=noninteractive apt install -y git build-essential autoconf automake libtool libflac-dev libopus-dev libopusfile-dev libsamplerate0-dev libmp3lame-dev
- DEBIAN_FRONTEND=noninteractive apt install -y git build-essential autoconf automake libtool libflac-dev libopus-dev libopusfile-dev libsamplerate0-dev libmp3lame-dev libebur128-dev
- git clone --depth 1 https://github.com/xiph/libopusenc.git && cd libopusenc && ./autogen.sh && ./configure --prefix /usr && make && make install && cd ..
- git clone --depth 1 https://github.com/mstorsjo/fdk-aac.git && cd fdk-aac && ./autogen.sh && ./configure --prefix /usr && make -j$(nproc) && make install && cd ..
- go test -cover -coverpkg=git.gammaspectra.live/S.O.N.G/Kirika/hasher,git.gammaspectra.live/S.O.N.G/Kirika/audio,git.gammaspectra.live/S.O.N.G/Kirika/audio/format,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/mp3,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus,git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta -v

View file

@ -4,6 +4,7 @@ import (
"bytes"
"crypto/sha256"
"encoding/hex"
"fmt"
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/aac"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/flac"
@ -12,6 +13,7 @@ import (
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/opus"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/format/tta"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/packetizer"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/replaygain"
"git.gammaspectra.live/S.O.N.G/Kirika/hasher"
"io"
"os"
@ -84,6 +86,111 @@ func TestVorbisDecode(t *testing.T) {
doTest(".vorbis", t)
}
func TestReplayGain24(t *testing.T) {
t.Parallel()
fp, err := os.Open(TestSingleSample24)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
source, err := flac.NewFormat().Open(fp)
if err != nil {
t.Error(err)
return
}
gain, peak, err := replaygain.GetTrackReplayGain(source)
if err != nil {
t.Error(err)
return
}
expect := "-11.590850 dB, 1.000000"
result := fmt.Sprintf("%f dB, %f", gain, peak)
if result != expect {
t.Errorf("Wrong ReplayGain %s != %s", result, expect)
}
}
func TestReplayGain16(t *testing.T) {
t.Parallel()
fp, err := os.Open(TestSingleSample16)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
source, err := flac.NewFormat().Open(fp)
if err != nil {
t.Error(err)
return
}
gain, peak, err := replaygain.GetTrackReplayGain(source)
if err != nil {
t.Error(err)
return
}
expect := "-7.748202 dB, 0.942137"
result := fmt.Sprintf("%f dB, %f", gain, peak)
if result != expect {
t.Errorf("Wrong ReplayGain %s != %s", result, expect)
}
}
func TestAlbumReplayGain(t *testing.T) {
t.Parallel()
location := TestSampleLocations[1]
entries, err := os.ReadDir(location)
if err != nil {
t.Error(err)
return
}
var sources []audio.Source
for _, f := range entries {
if path.Ext(f.Name()) == ".flac" {
fullPath := path.Join(location, f.Name())
fp, err := os.Open(fullPath)
if err != nil {
t.Error(err)
return
}
defer fp.Close()
decoders, err := guess.GetDecoders(fp, fullPath)
if err != nil {
t.Error(err)
return
}
source, err := guess.Open(fp, decoders)
if err != nil {
t.Error(err)
return
}
sources = append(sources, source)
}
}
albumGain, albumPeak, trackGains, trackPeaks, err := replaygain.GetAlbumReplayGain(sources)
if err != nil {
t.Error(err)
return
}
expect := "-8.539780 dB, 0.970916, [-7.748202 -9.020742 -9.192557 -8.015611 -8.303189 -9.071835 -9.356090 -8.114885 -7.760444 -7.317174 -8.339798 -8.906274 -8.001073], [0.942137 0.970916 0.970916 0.970916 0.942106 0.970916 0.970916 0.965606 0.970916 0.970916 0.942137 0.970916 0.942137]"
result := fmt.Sprintf("%f dB, %f, %f, %f", albumGain, albumPeak, trackGains, trackPeaks)
if result != expect {
t.Errorf("Wrong ReplayGain %s != %s", result, expect)
}
}
func TestHasher24(t *testing.T) {
t.Parallel()

View file

@ -9,6 +9,7 @@ Collection of audio utilities for decoding/encoding files and streams.
* Audio downmixing to stereo/mono
* Multi-codec decoder and encoder
* Multi-format packetizers
* ReplayGain 2.0 track/album calculator
## Codecs supported
@ -73,4 +74,9 @@ sudo apt install libmp3lame-dev
### [libsamplerate](https://github.com/libsndfile/libsamplerate) (required by [gosamplerate](https://github.com/dh1tw/gosamplerate))
```shell
sudo apt install libsamplerate0-dev
```
### [libebur128](https://github.com/jiixyj/libebur128) (required by [go-ebur128](https://git.gammaspectra.live/S.O.N.G/go-ebur128))
```shell
sudo apt install libebur128-dev
```

View file

@ -0,0 +1,113 @@
package replaygain
import (
"errors"
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
libebur128 "git.gammaspectra.live/S.O.N.G/go-ebur128"
"sync"
)
const referenceLevel = -18.0
//GetTrackReplayGain calculates track ReplayGain 2.0
func GetTrackReplayGain(source audio.Source) (gain, peak float64, err error) {
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
if state == nil {
err = errors.New("could not initialize state")
return
}
defer state.Close()
for block := range source.Blocks {
if err = state.AddFloat(block); err != nil {
return
}
}
var loudness float64
var peakSlice []float64
if loudness, err = state.GetLoudnessGlobal(); err != nil {
return
}
if peakSlice, err = state.GetSamplePeak(); err != nil {
return
}
for _, p := range peakSlice {
if p > peak {
peak = p
}
}
gain = referenceLevel - loudness
return
}
//GetAlbumReplayGain calculates album and tracks ReplayGain 2.0
func GetAlbumReplayGain(sources []audio.Source) (albumGain, albumPeak float64, trackGains []float64, trackPeaks []float64, err error) {
var states []*libebur128.State
var wg sync.WaitGroup
defer func() {
wg.Wait()
for _, state := range states {
state.Close()
}
}()
for _, source := range sources {
state := libebur128.NewState(source.Channels, source.SampleRate, libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
if state == nil {
err = errors.New("could not initialize state")
return
}
states = append(states, state)
wg.Add(1)
go func(source audio.Source) {
defer wg.Done()
for block := range source.Blocks {
if err = state.AddFloat(block); err != nil {
return
}
}
}(source)
}
wg.Wait()
var albumLoudness float64
var peakSlice []float64
var trackLoudness float64
if albumLoudness, err = libebur128.GetLoudnessGlobalMultiple(states); err != nil {
return
}
for _, state := range states {
if trackLoudness, err = state.GetLoudnessGlobal(); err != nil {
return
}
if peakSlice, err = state.GetSamplePeak(); err != nil {
return
}
var tPeak float64
for _, p := range peakSlice {
if p > albumPeak {
albumPeak = p
}
if p > tPeak {
tPeak = p
}
}
trackGains = append(trackGains, referenceLevel-trackLoudness)
trackPeaks = append(trackPeaks, tPeak)
}
albumGain = referenceLevel - albumLoudness
return
}

1
go.mod
View file

@ -3,6 +3,7 @@ module git.gammaspectra.live/S.O.N.G/Kirika
go 1.18
require (
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308105126-3299585e6a9a
git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220228131722-e9cb84c52f48
git.gammaspectra.live/S.O.N.G/go-pus v0.0.0-20220227175608-6cc027f24dba
git.gammaspectra.live/S.O.N.G/go-tta v0.2.1-0.20220226150007-096de1072bd6

2
go.sum
View file

@ -1,3 +1,5 @@
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308105126-3299585e6a9a h1:cEkgIB5RSBFcyeB/VhpHiuYof2V/nMU5js46UroYs4c=
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220308105126-3299585e6a9a/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=