164 lines
3.2 KiB
Go
164 lines
3.2 KiB
Go
package filter
|
|
|
|
import (
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
|
|
"git.gammaspectra.live/S.O.N.G/Kirika/cgo"
|
|
"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 BufferFilter struct {
|
|
blockBufferSize int
|
|
}
|
|
|
|
func NewBufferFilter(blockBufferSize int) BufferFilter {
|
|
return BufferFilter{
|
|
blockBufferSize: blockBufferSize,
|
|
}
|
|
}
|
|
|
|
func (f BufferFilter) Process(source audio.Source) audio.Source {
|
|
outBlocks := make(chan []float32, f.blockBufferSize)
|
|
go func() {
|
|
defer close(outBlocks)
|
|
for block := range source.Blocks {
|
|
outBlocks <- block
|
|
}
|
|
}()
|
|
|
|
return audio.Source{
|
|
Channels: source.Channels,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type RealTimeFilter struct {
|
|
blocksPerSecond int
|
|
}
|
|
|
|
func NewRealTimeFilter(blocksPerSecond int) RealTimeFilter {
|
|
return RealTimeFilter{
|
|
blocksPerSecond: blocksPerSecond,
|
|
}
|
|
}
|
|
|
|
func (f RealTimeFilter) Process(source audio.Source) audio.Source {
|
|
outBlocks := make(chan []float32)
|
|
if source.SampleRate%f.blocksPerSecond != 0 {
|
|
log.Panicf("%d %% %d != 0", source.SampleRate, f.blocksPerSecond)
|
|
}
|
|
blockSize := (source.SampleRate / f.blocksPerSecond) * source.Channels
|
|
throttler := time.Tick(time.Second / time.Duration(f.blocksPerSecond))
|
|
|
|
go func() {
|
|
defer close(outBlocks)
|
|
var buf []float32
|
|
for block := range source.Blocks {
|
|
buf = append(buf, block...)
|
|
for len(buf) >= blockSize {
|
|
outBlocks <- buf[0:blockSize]
|
|
buf = buf[blockSize:]
|
|
<-throttler
|
|
}
|
|
}
|
|
|
|
outBlocks <- buf
|
|
}()
|
|
|
|
return audio.Source{
|
|
Channels: source.Channels,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type VolumeFilter struct {
|
|
adjustment float32
|
|
}
|
|
|
|
func NewVolumeFilter(adjustment float32) VolumeFilter {
|
|
return VolumeFilter{
|
|
adjustment: adjustment,
|
|
}
|
|
}
|
|
|
|
func (f VolumeFilter) Process(source audio.Source) audio.Source {
|
|
outBlocks := make(chan []float32)
|
|
go func() {
|
|
defer close(outBlocks)
|
|
|
|
for block := range source.Blocks {
|
|
out := make([]float32, len(block))
|
|
for i := range block {
|
|
out[i] = block[i] * f.adjustment
|
|
}
|
|
outBlocks <- out
|
|
}
|
|
|
|
}()
|
|
|
|
return audio.Source{
|
|
Channels: source.Channels,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type StereoFilter struct {
|
|
}
|
|
|
|
func (f StereoFilter) Process(source audio.Source) audio.Source {
|
|
if source.Channels == 2 { //no change
|
|
return source
|
|
}
|
|
outBlocks := make(chan []float32)
|
|
|
|
go func() {
|
|
defer close(outBlocks)
|
|
for block := range source.Blocks {
|
|
outBlocks <- cgo.MultipleChannelsToStereo(block, source.Channels)
|
|
}
|
|
}()
|
|
|
|
return audio.Source{
|
|
Channels: 2,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type MonoFilter struct {
|
|
}
|
|
|
|
func (f MonoFilter) Process(source audio.Source) audio.Source {
|
|
if source.Channels == 1 { //no change
|
|
return source
|
|
}
|
|
outBlocks := make(chan []float32)
|
|
|
|
go func() {
|
|
defer close(outBlocks)
|
|
for block := range source.Blocks {
|
|
outBlocks <- cgo.MultipleChannelsToMono(block, source.Channels)
|
|
}
|
|
}()
|
|
|
|
return audio.Source{
|
|
Channels: 1,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|