//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 }