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