DataHoarder
e2639f51ee
All checks were successful
continuous-integration/drone/push Build is passing
217 lines
4.7 KiB
Go
217 lines
4.7 KiB
Go
package audio
|
|
|
|
/*
|
|
#cgo CFLAGS: -I"${SRCDIR}/../cgo" -march=native -Ofast -std=c99
|
|
#include "audio.h"
|
|
*/
|
|
import "C"
|
|
import (
|
|
"github.com/dh1tw/gosamplerate"
|
|
"log"
|
|
"time"
|
|
)
|
|
|
|
type Filter interface {
|
|
Process(source Source) Source
|
|
}
|
|
|
|
func NewFilterChain(source Source, filters ...Filter) 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 Source) Source {
|
|
outBlocks := make(chan []float32, f.blockBufferSize)
|
|
go func() {
|
|
defer close(outBlocks)
|
|
for block := range source.Blocks {
|
|
outBlocks <- block
|
|
}
|
|
}()
|
|
|
|
return Source{
|
|
Channels: source.Channels,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type RealTimeFilter struct {
|
|
}
|
|
|
|
func (f RealTimeFilter) Process(source Source) Source {
|
|
outBlocks := make(chan []float32)
|
|
const blocksPerSecond = 10
|
|
if source.SampleRate%blocksPerSecond != 0 {
|
|
log.Panicf("%d %% %d != 0", source.SampleRate, blocksPerSecond)
|
|
}
|
|
blockSize := (source.SampleRate / blocksPerSecond) * source.Channels
|
|
throttler := time.Tick(time.Second / 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 Source{
|
|
Channels: source.Channels,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type StereoFilter struct {
|
|
}
|
|
|
|
func (f StereoFilter) Process(source Source) Source {
|
|
if source.Channels == 2 { //no change
|
|
return source
|
|
}
|
|
outBlocks := make(chan []float32)
|
|
|
|
go func() {
|
|
defer close(outBlocks)
|
|
for block := range source.Blocks {
|
|
//bring any number of channels to stereo, using downmix formulas when necessary
|
|
buf := make([]float32, (len(block)/source.Channels)*2)
|
|
C.audio_multiple_channels_to_stereo((*C.float)(&block[0]), C.size_t(len(block)), (*C.float)(&buf[0]), C.int(source.Channels))
|
|
outBlocks <- buf
|
|
}
|
|
}()
|
|
|
|
return Source{
|
|
Channels: 2,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type MonoFilter struct {
|
|
}
|
|
|
|
func (f MonoFilter) Process(source Source) Source {
|
|
if source.Channels == 1 { //no change
|
|
return source
|
|
}
|
|
outBlocks := make(chan []float32)
|
|
|
|
go func() {
|
|
defer close(outBlocks)
|
|
for block := range source.Blocks {
|
|
//bring any number of channels to mono, equally weighted, reusing buffer backwards
|
|
C.audio_multiple_channels_to_mono((*C.float)(&block[0]), C.size_t(len(block)), C.int(source.Channels))
|
|
outBlocks <- block[0:(len(block) / source.Channels)]
|
|
}
|
|
}()
|
|
|
|
return Source{
|
|
Channels: 1,
|
|
SampleRate: source.SampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|
|
|
|
type ResampleFilter struct {
|
|
sampleRate int
|
|
quality ResampleQuality
|
|
blockSize int
|
|
}
|
|
|
|
type ResampleQuality int
|
|
|
|
const (
|
|
BandlimitedBest ResampleQuality = gosamplerate.SRC_SINC_BEST_QUALITY
|
|
BandlimitedMedium ResampleQuality = gosamplerate.SRC_SINC_MEDIUM_QUALITY
|
|
BandlimitedFastest ResampleQuality = gosamplerate.SRC_SINC_FASTEST
|
|
ZeroOrderHold ResampleQuality = gosamplerate.SRC_ZERO_ORDER_HOLD
|
|
Linear ResampleQuality = gosamplerate.SRC_LINEAR
|
|
)
|
|
|
|
func NewResampleFilter(sampleRate int, quality ResampleQuality, blockSize int) ResampleFilter {
|
|
if blockSize == 0 {
|
|
blockSize = 1024 * 64
|
|
}
|
|
return ResampleFilter{
|
|
sampleRate: sampleRate,
|
|
quality: quality,
|
|
blockSize: blockSize,
|
|
}
|
|
}
|
|
|
|
func (f ResampleFilter) Process(source Source) Source {
|
|
if source.SampleRate == f.sampleRate { //no change
|
|
return source
|
|
}
|
|
|
|
outBlocks := make(chan []float32)
|
|
|
|
go func() {
|
|
defer close(outBlocks)
|
|
|
|
blockSize := f.blockSize * source.Channels
|
|
samplerateConverter, err := gosamplerate.New(int(f.quality), source.Channels, blockSize)
|
|
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
defer gosamplerate.Delete(samplerateConverter)
|
|
|
|
ratio := float64(f.sampleRate) / float64(source.SampleRate)
|
|
|
|
for block := range source.Blocks {
|
|
for len(block) >= blockSize {
|
|
b, err := samplerateConverter.Process(block[0:blockSize], ratio, false)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
if len(b) > 0 {
|
|
outBlocks <- b
|
|
}
|
|
block = block[blockSize:]
|
|
}
|
|
b, err := samplerateConverter.Process(block, ratio, false)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
if len(b) > 0 {
|
|
outBlocks <- b
|
|
}
|
|
}
|
|
b, err := samplerateConverter.Process([]float32{}, ratio, true)
|
|
if err != nil {
|
|
log.Panic(err)
|
|
}
|
|
if len(b) > 0 {
|
|
outBlocks <- b
|
|
}
|
|
}()
|
|
|
|
return Source{
|
|
Channels: source.Channels,
|
|
SampleRate: f.sampleRate,
|
|
Blocks: outBlocks,
|
|
}
|
|
}
|