package filter import ( "git.gammaspectra.live/S.O.N.G/Kirika/audio" "git.gammaspectra.live/S.O.N.G/Kirika/vector" "log" "time" ) type Filter interface { Process(source audio.Source) audio.Source } func NewFilterChain(source audio.Source, filters ...Filter) audio.Source { for _, filter := range filters { source = filter.Process(source) } return source } type SourceFormatFilter struct { Format audio.SourceFormat } func (f SourceFormatFilter) Process(source audio.Source) audio.Source { switch f.Format { case audio.SourceFloat32: return source.ToFloat32() case audio.SourceInt16: return source.ToInt16() case audio.SourceInt32: if int16Source, ok := source.(audio.TypedSource[int16]); ok { return source.ToInt32(int16Source.GetBitDepth()) } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { return int32Source } else { return source.ToInt32(source.GetBitDepth()) } } //TODO: panic? return source } type BufferFilter struct { blockBufferSize int } func NewBufferFilter(blockBufferSize int) BufferFilter { return BufferFilter{ blockBufferSize: blockBufferSize, } } func bufferFilterHelper[T audio.AllowedSourceTypes](source audio.TypedSource[T], blockBufferSize int) audio.TypedSource[T] { outBlocks := make(chan []T, blockBufferSize) go func() { defer close(outBlocks) for block := range source.GetBlocks() { outBlocks <- block } }() newSource := audio.NewSource[T](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) close(newSource.SwapBlocks(outBlocks)) return newSource } func (f BufferFilter) Process(source audio.Source) audio.Source { if floatSource, ok := source.(audio.TypedSource[float32]); ok { return bufferFilterHelper(floatSource, f.blockBufferSize) } else if int16Source, ok := source.(audio.TypedSource[int16]); ok { return bufferFilterHelper(int16Source, f.blockBufferSize) } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { return bufferFilterHelper(int32Source, f.blockBufferSize) } return source } type BlockSizeFilter struct { blockSize int } func NewBlockSizeFilter(blockSize int) BlockSizeFilter { return BlockSizeFilter{ blockSize: blockSize, } } func blockSizeFilterHelper[T audio.AllowedSourceTypes](source audio.TypedSource[T], blockSize int) audio.TypedSource[T] { bs := blockSize * source.GetChannels() outSource := audio.NewSource[T](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() var buffer []T buf := buffer for block := range source.GetBlocks() { buf = append(buf, block...) for len(buf) >= bs { outSource.IngestNative(buf[:bs], source.GetBitDepth()) buf = buf[bs:] } if len(buf) == 0 { //reuse resources buf = buffer[:0] } } if len(buf) > 0 { outSource.IngestNative(buf, source.GetBitDepth()) } }() return outSource } func (f BlockSizeFilter) Process(source audio.Source) audio.Source { if floatSource, ok := source.(audio.TypedSource[float32]); ok { return blockSizeFilterHelper(floatSource, f.blockSize) } else if int16Source, ok := source.(audio.TypedSource[int16]); ok { return blockSizeFilterHelper(int16Source, f.blockSize) } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { return blockSizeFilterHelper(int32Source, f.blockSize) } return nil } type RealTimeFilter struct { blocksPerSecond int } func NewRealTimeFilter(blocksPerSecond int) RealTimeFilter { return RealTimeFilter{ blocksPerSecond: blocksPerSecond, } } func realTimeFilterHelper[T audio.AllowedSourceTypes](source audio.TypedSource[T], blocksPerSecond int) audio.TypedSource[T] { blockSize := source.GetSampleRate() / blocksPerSecond throttler := time.Tick(time.Second / time.Duration(blocksPerSecond)) outSource := audio.NewSource[T](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() for block := range NewBlockSizeFilter(blockSize).Process(source).(audio.TypedSource[T]).GetBlocks() { outSource.IngestNative(block, source.GetBitDepth()) <-throttler } }() return outSource } func (f RealTimeFilter) Process(source audio.Source) audio.Source { if source.GetSampleRate()%f.blocksPerSecond != 0 { log.Panicf("%d %% %d != 0", source.GetSampleRate(), f.blocksPerSecond) } if floatSource, ok := source.(audio.TypedSource[float32]); ok { return realTimeFilterHelper(floatSource, f.blocksPerSecond) } else if int16Source, ok := source.(audio.TypedSource[int16]); ok { return realTimeFilterHelper(int16Source, f.blocksPerSecond) } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { return realTimeFilterHelper(int32Source, f.blocksPerSecond) } return nil } type VolumeFilter struct { adjustment float64 } func NewVolumeFilter(adjustment float64) VolumeFilter { return VolumeFilter{ adjustment: adjustment, } } func (f VolumeFilter) Process(source audio.Source) audio.Source { if f.adjustment == 1. { return source } if int32Source, ok := source.(audio.TypedSource[int32]); ok { bpsMultiplier := int64((1 << (source.GetBitDepth() - 1)) - 1) adjustment := int64(f.adjustment * float64(bpsMultiplier)) outSource := audio.NewSource[int32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() for block := range int32Source.GetBlocks() { out := make([]int32, len(block)) for i := range block { out[i] = int32((int64(block[i]) * adjustment) / bpsMultiplier) } outSource.IngestNative(out, source.GetBitDepth()) } }() return outSource } else if int16Source, ok := source.(audio.TypedSource[int16]); ok { bpsMultiplier := int32((1 << (source.GetBitDepth() - 1)) - 1) adjustment := int32(f.adjustment * float64(bpsMultiplier)) outSource := audio.NewSource[int16](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() for block := range int16Source.GetBlocks() { out := make([]int16, len(block)) for i := range block { out[i] = int16((int32(block[i]) * adjustment) / bpsMultiplier) } outSource.IngestNative(out, source.GetBitDepth()) } }() return outSource } else { adjustment := float32(f.adjustment) outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() for block := range source.ToFloat32().GetBlocks() { out := make([]float32, len(block)) for i := range block { out[i] = block[i] * adjustment } outSource.IngestNative(out, source.GetBitDepth()) } }() return outSource } } type StereoFilter struct { } func (f StereoFilter) Process(source audio.Source) audio.Source { if source.GetChannels() == 2 { //no change return source } outSource := audio.NewTypedInterface(source, source.GetBitDepth(), source.GetSampleRate(), 2) go func() { defer outSource.Close() if _, ok := source.(audio.TypedSource[int32]); ok { for block := range source.ToInt32(source.GetBitDepth()).GetBlocks() { outSource.IngestInt32(vector.MultipleChannelsToStereoInt32(block, source.GetChannels()), source.GetBitDepth()) } } else if _, ok = source.(audio.TypedSource[int16]); ok { for block := range source.ToInt32(source.GetBitDepth()).GetBlocks() { outSource.IngestInt32(vector.MultipleChannelsToStereoInt32(block, source.GetChannels()), source.GetBitDepth()) } } else { for block := range source.ToFloat32().GetBlocks() { outSource.IngestFloat32(vector.MultipleChannelsToStereoFloat32(block, source.GetChannels())) } } }() return outSource } type MonoFilter struct { } func (f MonoFilter) Process(source audio.Source) audio.Source { if source.GetChannels() == 1 { //no change return source } outSource := audio.NewTypedInterface(source, source.GetBitDepth(), source.GetSampleRate(), 1) go func() { defer outSource.Close() if _, ok := source.(audio.TypedSource[int32]); ok { for block := range source.ToInt32(source.GetBitDepth()).GetBlocks() { outSource.IngestInt32(vector.MultipleChannelsToMonoInt32(block, source.GetChannels()), source.GetBitDepth()) } } else if _, ok = source.(audio.TypedSource[int16]); ok { for block := range source.ToInt32(source.GetBitDepth()).GetBlocks() { outSource.IngestInt32(vector.MultipleChannelsToMonoInt32(block, source.GetChannels()), source.GetBitDepth()) } } else { for block := range source.ToFloat32().GetBlocks() { outSource.IngestFloat32(vector.MultipleChannelsToMonoFloat32(block, source.GetChannels())) } } }() return outSource }