DataHoarder
a7fb82f3d3
All checks were successful
continuous-integration/drone/push Build is passing
301 lines
8.5 KiB
Go
301 lines
8.5 KiB
Go
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
|
|
}
|