Kirika/audio/replaygain/normalization.go
DataHoarder 09f3cf3b56
All checks were successful
continuous-integration/drone/push Build is passing
Use generics to implement TypedSource[float32|int16|int32]
2022-07-22 12:07:01 +02:00

163 lines
3.6 KiB
Go

//go:build cgo
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.GetChannels(), source.GetSampleRate(), libebur128.LoudnessGlobalMomentary|libebur128.SamplePeak)
if state == nil {
err = errors.New("could not initialize state")
return
}
defer state.Close()
if _, ok := source.(audio.TypedSource[int16]); ok {
for block := range source.ToInt16().GetBlocks() {
if err = state.AddShort(block); err != nil {
return
}
}
} else if int32Source, ok := source.(audio.TypedSource[int32]); ok {
if source.GetBitDepth() == 16 {
for block := range source.ToInt16().GetBlocks() {
if err = state.AddShort(block); err != nil {
return
}
}
} else {
for block := range int32Source.ToInt32(32).GetBlocks() {
if err = state.AddInt(block); err != nil {
return
}
}
}
} else {
for block := range source.ToFloat32().GetBlocks() {
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.GetChannels(), source.GetSampleRate(), 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()
var err error
if _, ok := source.(audio.TypedSource[int16]); ok {
for block := range source.ToInt16().GetBlocks() {
if err = state.AddShort(block); err != nil {
return
}
}
} else if _, ok := source.(audio.TypedSource[int32]); ok {
if source.GetBitDepth() == 16 {
for block := range source.ToInt16().GetBlocks() {
if err = state.AddShort(block); err != nil {
return
}
}
} else {
for block := range source.ToInt32(32).GetBlocks() {
if err = state.AddInt(block); err != nil {
return
}
}
}
} else {
for block := range source.ToFloat32().GetBlocks() {
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
}