package audio import ( "errors" "golang.org/x/exp/slices" "runtime" "unsafe" ) type SourceFormat int const ( SourceFloat32 = SourceFormat(iota) SourceInt16 SourceInt32 ) type AllowedSourceTypes interface { float32 | int16 | int32 } type TypedSource[T AllowedSourceTypes] interface { Source New() TypedSource[T] IngestNative(buf []T, bitDepth int) GetBlocks() chan []T // SwapBlocks swaps current Blocks with a different one, and returns old one SwapBlocks(blocks chan []T) chan []T } func Split[T AllowedSourceTypes](s TypedSource[T], n int) (sources []Source) { if s.Locked() { return } sources = make([]Source, n) for i := range sources { sources[i] = s.New() } go func() { defer func() { for _, source := range sources { source.Close() } }() for block := range s.GetBlocks() { for _, source := range sources { source.(TypedSource[T]).IngestNative(block, s.GetBitDepth()) } } }() return } func NewTypedInterface(s Source, bitDepth, sampleRate, channels int) Source { if _, ok := s.(TypedSource[float32]); ok { return newFloat32Source(bitDepth, sampleRate, channels) } else if _, ok = s.(TypedSource[int16]); ok { return newInt16Source(bitDepth, sampleRate, channels) } else if _, ok = s.(TypedSource[int32]); ok { return newInt32Source(bitDepth, sampleRate, channels) } else { return newFloat32Source(bitDepth, sampleRate, channels) } } func NewInterface(s Source) Source { if f32Source, ok := s.(TypedSource[float32]); ok { return f32Source.New() } else if i16Source, ok := s.(TypedSource[int16]); ok { return i16Source.New() } else if i32Source, ok := s.(TypedSource[int32]); ok { return i32Source.New() } else { return s.ToFloat32().New() } } func GetBlocksInterface(s Source) interface{} { if f32Source, ok := s.(TypedSource[float32]); ok { return f32Source.GetBlocks() } else if i16Source, ok := s.(TypedSource[int16]); ok { return i16Source.GetBlocks() } else if i32Source, ok := s.(TypedSource[int32]); ok { return i32Source.GetBlocks() } else { return s.ToFloat32().GetBlocks() } } // Ingest TODO: make this using Generics when type switch is supported func Ingest(s Source, buf interface{}, bitDepth int) error { switch buf.(type) { case []int32: s.IngestInt32(buf.([]int32), bitDepth) return nil case []int16: s.IngestInt16(buf.([]int16), bitDepth) return nil case []float32: s.IngestFloat32(buf.([]float32)) return nil case []byte: bufferSlice := buf.([]byte) nsamples := len(bufferSlice) / (bitDepth / 8) switch bitDepth { case 32: s.IngestInt32(slices.Clone(unsafe.Slice((*int32)(unsafe.Pointer(unsafe.SliceData(bufferSlice))), nsamples)), bitDepth) runtime.KeepAlive(bufferSlice) return nil case 24: s.IngestInt24(unsafe.Slice((*byte)(unsafe.Pointer(unsafe.SliceData(bufferSlice))), nsamples*3), bitDepth) runtime.KeepAlive(bufferSlice) return nil case 16: s.IngestInt16(slices.Clone(unsafe.Slice((*int16)(unsafe.Pointer(unsafe.SliceData(bufferSlice))), nsamples)), bitDepth) runtime.KeepAlive(bufferSlice) return nil case 8: s.IngestInt8(unsafe.Slice((*int8)(unsafe.Pointer(unsafe.SliceData(bufferSlice))), nsamples), bitDepth) runtime.KeepAlive(bufferSlice) return nil default: return errors.New("not supported bit depth") } } return errors.New("not supported format") } func NewSource[T AllowedSourceTypes](bitDepth, sampleRate, channels int) TypedSource[T] { switch interface{}(T(0)).(type) { case float32: return newFloat32Source(bitDepth, sampleRate, channels).(TypedSource[T]) case int16: return newInt16Source(bitDepth, sampleRate, channels).(TypedSource[T]) case int32: return newInt32Source(bitDepth, sampleRate, channels).(TypedSource[T]) } return nil } type Source interface { GetBitDepth() int GetSampleRate() int GetChannels() int GetFormat() SourceFormat ToFloat32() TypedSource[float32] ToInt16() TypedSource[int16] ToInt32(bitDepth int) TypedSource[int32] IngestFloat32(buf []float32) IngestInt8(buf []int8, bitDepth int) IngestInt16(buf []int16, bitDepth int) IngestInt24(buf []byte, bitDepth int) IngestInt32(buf []int32, bitDepth int) //Split a Source into multiple ones. After calling this function, the source is locked. Split(n int) []Source Close() Unlock() Locked() bool }