DataHoarder
09f3cf3b56
All checks were successful
continuous-integration/drone/push Build is passing
114 lines
2.5 KiB
Go
114 lines
2.5 KiB
Go
//go:build cgo
|
|
|
|
package replaygain
|
|
|
|
import (
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
|
libebur128 "git.gammaspectra.live/S.O.N.G/go-ebur128"
|
|
"math"
|
|
"time"
|
|
)
|
|
|
|
// NormalizationFilter Normalizes running audio source
|
|
type NormalizationFilter struct {
|
|
delay int
|
|
}
|
|
|
|
func NewNormalizationFilter(delayInSeconds int) NormalizationFilter {
|
|
return NormalizationFilter{
|
|
delay: delayInSeconds,
|
|
}
|
|
}
|
|
|
|
func (f NormalizationFilter) Process(source audio.Source) audio.Source {
|
|
outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels())
|
|
go func() {
|
|
defer outSource.Close()
|
|
|
|
state := libebur128.NewState(source.GetChannels(), source.GetSampleRate(), libebur128.LoudnessShortTerm|libebur128.SamplePeak)
|
|
if state == nil {
|
|
return
|
|
}
|
|
defer state.Close()
|
|
|
|
if state.SetMaxWindow(time.Second*time.Duration(f.delay)) != nil {
|
|
return
|
|
}
|
|
|
|
var sampleBuffer []float32
|
|
var adjustment float32 = 1.0
|
|
|
|
//TODO: do this for int16 and int32
|
|
for block := range source.ToFloat32().GetBlocks() {
|
|
if state.AddFloat(block) != nil {
|
|
return
|
|
}
|
|
|
|
sampleBuffer = append(sampleBuffer, block...)
|
|
|
|
loudness, _ := state.GetLoudnessWindow(time.Second * time.Duration(f.delay))
|
|
peakSlice, _ := state.GetPreviousSamplePeak()
|
|
|
|
var peak float64
|
|
for _, p := range peakSlice {
|
|
if p > peak {
|
|
peak = p
|
|
}
|
|
}
|
|
|
|
gain := referenceLevel - loudness
|
|
if gain > 52 {
|
|
gain = 52
|
|
} else if gain < -52 {
|
|
gain = -52
|
|
}
|
|
|
|
volume := math.Pow(10, (gain)/20)
|
|
|
|
nsamples := source.GetSampleRate() * source.GetChannels() * f.delay
|
|
|
|
ratio := float32(math.Min(1, float64(len(block))/float64(nsamples)))
|
|
|
|
adjustment = adjustment*(1-ratio) + float32(volume)*ratio
|
|
|
|
if adjustment > float32(1/peak) {
|
|
adjustment = float32(1 / peak)
|
|
}
|
|
|
|
if len(sampleBuffer) > nsamples {
|
|
adjustment = adjustment*(1-ratio) + float32(volume)*ratio
|
|
|
|
if adjustment > float32(1/peak) {
|
|
adjustment = float32(1 / peak)
|
|
}
|
|
|
|
size := len(sampleBuffer) - nsamples
|
|
out := make([]float32, size)
|
|
for i, e := range sampleBuffer[:size] {
|
|
out[i] = e * adjustment
|
|
}
|
|
outSource.IngestFloat32(out)
|
|
sampleBuffer = sampleBuffer[size:]
|
|
} else {
|
|
adjustment = float32(volume)
|
|
|
|
if adjustment > float32(1/peak) {
|
|
adjustment = float32(1 / peak)
|
|
}
|
|
}
|
|
}
|
|
|
|
//flush
|
|
if len(sampleBuffer) > 0 {
|
|
out := make([]float32, len(sampleBuffer))
|
|
for i := range sampleBuffer {
|
|
out[i] = sampleBuffer[i] * adjustment
|
|
}
|
|
outSource.IngestFloat32(out)
|
|
}
|
|
|
|
}()
|
|
|
|
return outSource
|
|
}
|