Kirika/audio/replaygain/filter_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

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
}