Kirika/audio/stream.go
2022-02-22 10:35:08 +01:00

182 lines
4.2 KiB
Go

package audio
/*
#cgo CFLAGS: -I"${SRCDIR}/../cgo" -march=native -Ofast -std=c99
#include "audio.h"
*/
import "C"
import (
"fmt"
"github.com/dh1tw/gosamplerate"
"log"
)
type Stream struct {
source chan []float32
channels int
sampleRate float64
samplesProcessed int
buffer []float32
blockSize int
}
func NewStream(source chan []float32, channels int, sampleRate float64, blockSize int) *Stream {
return &Stream{
source: source,
channels: channels,
sampleRate: sampleRate,
blockSize: blockSize,
}
}
func (s *Stream) GetChannels() int {
return s.channels
}
func (s *Stream) GetSampleRate() float64 {
return s.sampleRate
}
func (s *Stream) GetSamplesProcessed() int {
return s.samplesProcessed
}
func (s *Stream) GetAsChannel() chan float32 {
newChannel := make(chan float32)
go func() {
defer close(newChannel)
for {
v, more := s.Get()
if !more {
return
}
newChannel <- v
s.samplesProcessed++
}
}()
return newChannel
}
func (s *Stream) GetAsBlockChannel() chan []float32 {
newChannel := make(chan []float32)
go func() {
defer close(newChannel)
for buf := range s.source {
s.buffer = append(s.buffer, buf...)
for len(s.buffer) >= s.blockSize*s.channels {
newChannel <- s.buffer[0 : s.blockSize*s.channels]
s.samplesProcessed += s.blockSize * s.channels
s.buffer = s.buffer[s.blockSize*s.channels:]
}
}
if len(s.buffer) > 0 {
newChannel <- s.buffer
s.samplesProcessed += len(s.buffer)
}
s.buffer = nil
}()
return newChannel
}
func (s *Stream) Get() (float32, bool) {
var more bool
if len(s.buffer) == 0 {
s.buffer, more = <-s.source
if !more {
return 0, false
}
}
f := s.buffer[0]
s.buffer = s.buffer[1:]
s.samplesProcessed++
return f, true
}
func (s *Stream) AdvanceSeconds(seconds float64) bool {
stopAt := s.secondsIndex(seconds)
for i := 0; i < stopAt; i++ {
_, more := s.Get()
if !more {
return false
}
}
return true
}
func (s *Stream) secondsIndex(seconds float64) int {
return int(seconds * s.sampleRate)
}
const (
RESAMPLER_QUALITY_BANDLIMITED_BEST = gosamplerate.SRC_SINC_BEST_QUALITY
RESAMPLER_QUALITY_BANDLIMITED_MEDIUM = gosamplerate.SRC_SINC_MEDIUM_QUALITY
RESAMPLER_QUALITY_BANDLIMITED_FASTEST = gosamplerate.SRC_SINC_FASTEST
RESAMPLER_QUALITY_HOLD = gosamplerate.SRC_ZERO_ORDER_HOLD
RESAMPLER_QUALITY_LINEAR = gosamplerate.SRC_LINEAR
)
func (s *Stream) DoResample(channels int, sampleRate float64, quality int) (*Stream, error) {
if channels != 1 && channels != 2 && s.channels != channels {
return nil, fmt.Errorf("cannot convert from %d channels to %d", s.channels, channels)
}
if channels == s.channels && sampleRate == s.sampleRate {
return s, nil
}
samplerateConverter, err := gosamplerate.New(quality, channels, s.blockSize*s.channels)
if err != nil {
return nil, err
}
newChannel := make(chan []float32)
go func() {
defer gosamplerate.Delete(samplerateConverter)
defer close(newChannel)
ratio := sampleRate / s.sampleRate
for buf := range s.GetAsBlockChannel() {
if channels != s.channels && channels == 1 {
if channels == 1 {
//bring any number of channels to mono, equally weighted, reusing buffer backwards
C.audio_multiple_channels_to_mono((*C.float)(&buf[0]), C.size_t(len(buf)), C.int(s.channels))
buf = buf[0:(len(buf) / s.channels)]
} else if channels == 2 {
//bring any number of channels to stereo, using downmix formulas when necessary
out := make([]float32, (len(buf)/s.channels)*2)
C.audio_multiple_channels_to_stereo((*C.float)(&buf[0]), C.size_t(len(buf)), (*C.float)(&out[0]), C.int(s.channels))
buf = out
}
}
for len(buf) > 0 {
n := len(buf)
if n > s.blockSize*channels {
n = s.blockSize * channels
}
b, err := samplerateConverter.Process(buf[0:n], ratio, false)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
newChannel <- b
}
buf = buf[n:]
}
}
b, err := samplerateConverter.Process([]float32{}, ratio, true)
if err != nil {
log.Panic(err)
}
if len(b) > 0 {
newChannel <- b
}
}()
return NewStream(newChannel, channels, sampleRate, s.blockSize), nil
}