From 09f3cf3b563ae510c778913f1d1015c7dda12d6a Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Fri, 22 Jul 2022 11:39:58 +0200 Subject: [PATCH] Use generics to implement TypedSource[float32|int16|int32] --- README.md | 6 +- audio/filter/filter.go | 54 +- audio/filter/resample_filter_cgo.go | 2 +- audio/filter/resample_filter_nocgo.go | 2 +- audio/format/aac/libfdk-aac.go | 4 +- audio/format/alac/libalac.go | 2 +- audio/format/analyzer.go | 2 +- audio/format/flac/goflac.go | 2 +- audio/format/flac/libflac.go | 2 +- audio/format/mp3/mp3.go | 2 +- audio/format/mp3/mp3_nocgo.go | 2 +- audio/format/opus/opus.go | 2 +- audio/format/tta/tta.go | 36 +- audio/format/vorbis/vorbis.go | 2 +- audio/queue/queue.go | 303 +++------- audio/replaygain/filter_normalization.go | 2 +- audio/replaygain/normalization.go | 36 +- audio/sink.go | 32 +- audio/source.go | 669 ++++------------------- audio/source_float32.go | 132 +++++ audio/source_int16.go | 181 ++++++ audio/source_int32.go | 184 +++++++ 22 files changed, 778 insertions(+), 881 deletions(-) create mode 100644 audio/source_float32.go create mode 100644 audio/source_int16.go create mode 100644 audio/source_int32.go diff --git a/README.md b/README.md index e053d14..7062e51 100644 --- a/README.md +++ b/README.md @@ -16,12 +16,12 @@ Collection of audio utilities for decoding/encoding/processing files and streams | Codec | Containers | Decoder | Decoder Sample Format | Encoder | Notes | |:----------:|:----------------------------------------------------------------------------------------:|:-------:|:---------------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | **FLAC** | [FLAC](https://xiph.org/flac/format.html), [Ogg](https://xiph.org/flac/ogg_mapping.html) | ✅ | `int32` | ✅ | Adjustable encoding compression level and block size.
Decoding/encoding by [libFLAC](https://github.com/xiph/flac) via [goflac](https://git.gammaspectra.live/S.O.N.G/goflac).
If [goflac](https://git.gammaspectra.live/S.O.N.G/goflac) codec is disabled, [mewkiz/flac](https://github.com/mewkiz/flac) decoder will be used. | -| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | `int16, int32` | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). | +| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | `int32` | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). | | **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | `2ch. int16` | ✅ | Adjustable encoding bitrate and mode.
Decoding via [minimp3](https://github.com/kvark128/minimp3), encoding by [LAME](https://lame.sourceforge.io/) via [go-lame](https://github.com/viert/go-lame). | | **Opus** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | `int16` | ✅ | Adjustable encoding bitrate.
Decoding/encoding by [libopus](https://github.com/xiph/opus) via [go-pus](https://git.gammaspectra.live/S.O.N.G/go-pus). Linked Ogg streams of different channel count are not supported. | | **Vorbis** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | `float32` | ❌ | Decoding by [jfreymuth/vorbis](https://github.com/jfreymuth/vorbis) via [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis). | | **AAC** | [ADTS](https://wiki.multimedia.cx/index.php/ADTS), ADIF*, MP4** | ✅ | `int16` | ✅ | Adjustable encoding bitrate and mode (LC, HEv1, HEv2).
Decoding/encoding by [FDK-AAC](https://github.com/mstorsjo/fdk-aac) via [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac).
If [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac) codec is disabled, [VisualOn AAC encoder](https://github.com/gen2brain/aac-go) will be used for limited encoding support.
*ADIF only supported on encoding.
**MP4 only supported on encoding, and fragmented MP4 currently. | -| **ALAC** | MP4* | ✅ | `int16, int32` | ✅ | Decoding/encoding by [libalac](https://git.gammaspectra.live/S.O.N.G/alac) via [go-alac](https://git.gammaspectra.live/S.O.N.G/go-alac).
Disabled by default.
*MP4 encoding only supported on fragmented MP4 currently. | +| **ALAC** | MP4* | ✅ | `int32` | ✅ | Decoding/encoding by [libalac](https://git.gammaspectra.live/S.O.N.G/alac) via [go-alac](https://git.gammaspectra.live/S.O.N.G/go-alac).
Disabled by default.
*MP4 encoding only supported on fragmented MP4 currently. | ## Container packetizers supported @@ -116,7 +116,7 @@ Some tests cannot be completed in this mode. | Codec | Containers | Decoder | Decoder Sample Format | Encoder | Notes | |:----------:|:------------------------------------------------------------:|:-------:|:---------------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------| | **FLAC** | [FLAC](https://xiph.org/flac/format.html) | ✅ | `int32` | ❌ | Decoding by [mewkiz/flac](https://github.com/mewkiz/flac). | -| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | `int16, int32` | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). | +| **TTA** | [TTA](https://www.tausoft.org/en/true_audio_codec_format/) | ✅ | `int32` | ✅ | Decoding/encoding via [S.O.N.G/go-tta](https://git.gammaspectra.live/S.O.N.G/go-tta). | | **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | `2ch. int16` | ❌ | Decoding via [hajimehoshi/go-mp3](https://github.com/hajimehoshi/go-mp3). | | **Opus** | - | ❌ | - | ❌ | | | **Vorbis** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | `float32` | ❌ | Decoding by [jfreymuth/vorbis](https://github.com/jfreymuth/vorbis) via [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis). | diff --git a/audio/filter/filter.go b/audio/filter/filter.go index 1ba2801..290e15d 100644 --- a/audio/filter/filter.go +++ b/audio/filter/filter.go @@ -29,9 +29,9 @@ func (f SourceFormatFilter) Process(source audio.Source) audio.Source { case audio.SourceInt16: return source.ToInt16() case audio.SourceInt32: - if int16Source, ok := source.(*audio.Int16Source); ok { + if int16Source, ok := source.(audio.TypedSource[int16]); ok { return source.ToInt32(int16Source.GetBitDepth()) - } else if int32Source, ok := source.(*audio.Int32Source); ok { + } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { return int32Source } else { return source.ToInt32(32) @@ -53,7 +53,7 @@ func NewBufferFilter(blockBufferSize int) BufferFilter { } func (f BufferFilter) Process(source audio.Source) audio.Source { - if floatSource, ok := source.(*audio.Float32Source); ok { + if floatSource, ok := source.(audio.TypedSource[float32]); ok { outBlocks := make(chan []float32, f.blockBufferSize) go func() { defer close(outBlocks) @@ -62,12 +62,10 @@ func (f BufferFilter) Process(source audio.Source) audio.Source { } }() - return &audio.Float32Source{ - Channels: source.GetChannels(), - SampleRate: source.GetSampleRate(), - Blocks: outBlocks, - } - } else if int16Source, ok := source.(*audio.Int16Source); ok { + newSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) + close(newSource.SwapBlocks(outBlocks)) + return newSource + } else if int16Source, ok := source.(audio.TypedSource[int16]); ok { outBlocks := make(chan []int16, f.blockBufferSize) go func() { defer close(outBlocks) @@ -76,13 +74,10 @@ func (f BufferFilter) Process(source audio.Source) audio.Source { } }() - return &audio.Int16Source{ - BitDepth: source.GetBitDepth(), - Channels: source.GetChannels(), - SampleRate: source.GetSampleRate(), - Blocks: outBlocks, - } - } else if int32Source, ok := source.(*audio.Int32Source); ok { + newSource := audio.NewSource[int16](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) + close(newSource.SwapBlocks(outBlocks)) + return newSource + } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { outBlocks := make(chan []int32, f.blockBufferSize) go func() { defer close(outBlocks) @@ -91,12 +86,9 @@ func (f BufferFilter) Process(source audio.Source) audio.Source { } }() - return &audio.Int32Source{ - BitDepth: source.GetBitDepth(), - Channels: source.GetChannels(), - SampleRate: source.GetSampleRate(), - Blocks: outBlocks, - } + newSource := audio.NewSource[int32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) + close(newSource.SwapBlocks(outBlocks)) + return newSource } return source @@ -119,8 +111,8 @@ func (f RealTimeFilter) Process(source audio.Source) audio.Source { blockSize := (source.GetSampleRate() / f.blocksPerSecond) * source.GetChannels() throttler := time.Tick(time.Second / time.Duration(f.blocksPerSecond)) - if floatSource, ok := source.(*audio.Float32Source); ok { - outSource := audio.NewFloat32Source(source.GetSampleRate(), source.GetChannels()) + if floatSource, ok := source.(audio.TypedSource[float32]); ok { + outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() var buf []float32 @@ -137,12 +129,12 @@ func (f RealTimeFilter) Process(source audio.Source) audio.Source { }() return outSource - } else if int16Source, ok := source.(*audio.Int16Source); ok { + } else if int16Source, ok := source.(audio.TypedSource[int16]); ok { bitDepth := source.GetBitDepth() if bitDepth > 16 { bitDepth = 16 } - outSource := audio.NewInt16Source(bitDepth, source.GetSampleRate(), source.GetChannels()) + outSource := audio.NewSource[int16](bitDepth, source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() var buf []int16 @@ -159,8 +151,8 @@ func (f RealTimeFilter) Process(source audio.Source) audio.Source { }() return outSource - } else if int32Source, ok := source.(*audio.Int32Source); ok { - outSource := audio.NewInt32Source(source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) + } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { + outSource := audio.NewSource[int32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() var buf []int32 @@ -197,7 +189,7 @@ func (f VolumeFilter) Process(source audio.Source) audio.Source { return source } - outSource := audio.NewFloat32Source(source.GetSampleRate(), source.GetChannels()) + outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() @@ -223,7 +215,7 @@ func (f StereoFilter) Process(source audio.Source) audio.Source { } //TODO: make this for int16 and int32 - outSource := audio.NewFloat32Source(source.GetSampleRate(), 2) + outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), 2) go func() { defer outSource.Close() @@ -244,7 +236,7 @@ func (f MonoFilter) Process(source audio.Source) audio.Source { } //TODO: make this for int16 and int32 - outSource := audio.NewFloat32Source(source.GetSampleRate(), 1) + outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), 1) go func() { defer outSource.Close() diff --git a/audio/filter/resample_filter_cgo.go b/audio/filter/resample_filter_cgo.go index 8e57f19..49982b6 100644 --- a/audio/filter/resample_filter_cgo.go +++ b/audio/filter/resample_filter_cgo.go @@ -20,7 +20,7 @@ func (f ResampleFilter) Process(source audio.Source) audio.Source { return source } - outSource := audio.NewFloat32Source(f.sampleRate, source.GetChannels()) + outSource := audio.NewSource[float32](source.GetBitDepth(), f.sampleRate, source.GetChannels()) go func() { defer outSource.Close() diff --git a/audio/filter/resample_filter_nocgo.go b/audio/filter/resample_filter_nocgo.go index 6f8c4f8..4f1cea1 100644 --- a/audio/filter/resample_filter_nocgo.go +++ b/audio/filter/resample_filter_nocgo.go @@ -20,7 +20,7 @@ func (f ResampleFilter) Process(source audio.Source) audio.Source { return source } - outSource := audio.NewFloat32Source(f.sampleRate, source.GetChannels()) + outSource := audio.NewSource[float32](source.GetBitDepth(), f.sampleRate, source.GetChannels()) go func() { defer outSource.Close() diff --git a/audio/format/aac/libfdk-aac.go b/audio/format/aac/libfdk-aac.go index 07eea6b..99c3a3e 100644 --- a/audio/format/aac/libfdk-aac.go +++ b/audio/format/aac/libfdk-aac.go @@ -135,7 +135,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - source := audio.NewInt16Source(16, decoder.SampleRate(), decoder.NumChannels()) + source := audio.NewSource[int16](16, decoder.SampleRate(), decoder.NumChannels()) go func() { defer source.Close() @@ -181,7 +181,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - source := audio.NewInt16Source(16, decoder.SampleRate(), decoder.NumChannels()) + source := audio.NewSource[int16](16, decoder.SampleRate(), decoder.NumChannels()) go func() { defer source.Close() diff --git a/audio/format/alac/libalac.go b/audio/format/alac/libalac.go index c30a853..e685c0e 100644 --- a/audio/format/alac/libalac.go +++ b/audio/format/alac/libalac.go @@ -43,7 +43,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, errors.New("could not decode") } - source := audio.NewInt32Source(decoder.GetBitDepth(), decoder.GetSampleRate(), decoder.GetChannels()) + source := audio.NewSource[int32](decoder.GetBitDepth(), decoder.GetSampleRate(), decoder.GetChannels()) go func() { defer source.Close() diff --git a/audio/format/analyzer.go b/audio/format/analyzer.go index 4d6248c..b893ef1 100644 --- a/audio/format/analyzer.go +++ b/audio/format/analyzer.go @@ -37,7 +37,7 @@ func NewAnalyzerChannel(source audio.Source, err error) (audio.Source, AnalyzerC go func() { defer close(channel) - if _, ok := sources[1].(*audio.Float32Source); ok { + if _, ok := sources[1].(audio.TypedSource[float32]); ok { for block := range sources[1].ToInt32(32).GetBlocks() { channel <- &AnalyzerPacket{ Samples: block, diff --git a/audio/format/flac/goflac.go b/audio/format/flac/goflac.go index 8aef35c..043d800 100644 --- a/audio/format/flac/goflac.go +++ b/audio/format/flac/goflac.go @@ -31,7 +31,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - source := audio.NewInt32Source(int(decoder.Info.BitsPerSample), int(decoder.Info.SampleRate), int(decoder.Info.NChannels)) + source := audio.NewSource[int32](int(decoder.Info.BitsPerSample), int(decoder.Info.SampleRate), int(decoder.Info.NChannels)) go func() { defer source.Close() diff --git a/audio/format/flac/libflac.go b/audio/format/flac/libflac.go index f65de9a..acfadf6 100644 --- a/audio/format/flac/libflac.go +++ b/audio/format/flac/libflac.go @@ -42,7 +42,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { } } - source := audio.NewInt32Source(decoder.Depth, decoder.Rate, decoder.Channels) + source := audio.NewSource[int32](decoder.Depth, decoder.Rate, decoder.Channels) go func() { defer source.Close() diff --git a/audio/format/mp3/mp3.go b/audio/format/mp3/mp3.go index 5af8eea..dbf9fb7 100644 --- a/audio/format/mp3/mp3.go +++ b/audio/format/mp3/mp3.go @@ -35,7 +35,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - source := audio.NewInt16Source(16, decoder.SampleRate(), 2) + source := audio.NewSource[int16](16, decoder.SampleRate(), 2) go func() { defer source.Close() diff --git a/audio/format/mp3/mp3_nocgo.go b/audio/format/mp3/mp3_nocgo.go index bd3502b..a8b3b7c 100644 --- a/audio/format/mp3/mp3_nocgo.go +++ b/audio/format/mp3/mp3_nocgo.go @@ -33,7 +33,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - source := audio.NewInt16Source(16, decoder.SampleRate(), 2) + source := audio.NewSource[int16](16, decoder.SampleRate(), 2) go func() { defer source.Close() diff --git a/audio/format/opus/opus.go b/audio/format/opus/opus.go index 900cd2d..2f9b2b8 100644 --- a/audio/format/opus/opus.go +++ b/audio/format/opus/opus.go @@ -46,7 +46,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { } channelCount := info.ChannelCount - source := audio.NewInt16Source(16, FixedSampleRate, channelCount) + source := audio.NewSource[int16](16, FixedSampleRate, channelCount) go func() { defer stream.Close() diff --git a/audio/format/tta/tta.go b/audio/format/tta/tta.go index f98812a..ff807b7 100644 --- a/audio/format/tta/tta.go +++ b/audio/format/tta/tta.go @@ -80,20 +80,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - var source audio.Source - switch info.Bps { - case 8: - //TODO - source = audio.NewInt16Source(8, int(info.Sps), int(info.Nch)) - case 16: - source = audio.NewInt16Source(16, int(info.Sps), int(info.Nch)) - case 24: - //TODO: Use Int24 - source = audio.NewInt32Source(24, int(info.Sps), int(info.Nch)) - case 32: - source = audio.NewInt32Source(32, int(info.Sps), int(info.Nch)) - - } + source := audio.NewSource[int32](int(info.Bps), int(info.Sps), int(info.Nch)) go func() { defer source.Close() @@ -107,26 +94,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { break } - bufferSlice := buffer[:writeLen] - - bitDepth := int(info.Bps) - - nsamples := len(bufferSlice) / (bitDepth / 8) - - switch bitDepth { - case 32: - out := make([]int32, nsamples) - copy(out, unsafe.Slice((*int32)(unsafe.Pointer(&bufferSlice[0])), nsamples)) - source.IngestInt32(out, bitDepth) - case 24: - source.IngestInt24(unsafe.Slice((*byte)(unsafe.Pointer(&bufferSlice[0])), nsamples*3), bitDepth) - case 16: - out := make([]int16, nsamples) - copy(out, unsafe.Slice((*int16)(unsafe.Pointer(&bufferSlice[0])), nsamples)) - source.IngestInt16(out, bitDepth) - case 8: - source.IngestInt8(unsafe.Slice((*int8)(unsafe.Pointer(&bufferSlice[0])), nsamples), bitDepth) - } + source.IngestInt32(vector.BytesToInt32(buffer[:writeLen], int(info.Bps)), int(info.Bps)) } }() diff --git a/audio/format/vorbis/vorbis.go b/audio/format/vorbis/vorbis.go index 9778419..44ccc30 100644 --- a/audio/format/vorbis/vorbis.go +++ b/audio/format/vorbis/vorbis.go @@ -30,7 +30,7 @@ func (f Format) Open(r io.ReadSeekCloser) (audio.Source, error) { return nil, err } - source := audio.NewFloat32Source(reader.Channels(), reader.SampleRate()) + source := audio.NewSource[float32](16, reader.Channels(), reader.SampleRate()) go func() { defer source.Close() diff --git a/audio/queue/queue.go b/audio/queue/queue.go index ed17aaa..f86d0f6 100644 --- a/audio/queue/queue.go +++ b/audio/queue/queue.go @@ -22,9 +22,7 @@ type QueueEntry struct { type Queue struct { queue []*QueueEntry - float32Output *audio.Float32Source - int16Output *audio.Int16Source - int32Output *audio.Int32Source + output audio.Source interrupt chan bool interruptDepth int64 closed bool @@ -44,252 +42,124 @@ func NewQueue(format audio.SourceFormat, bitDepth, sampleRate, channels int) *Qu switch format { case audio.SourceFloat32: - q.float32Output = audio.NewFloat32Source(sampleRate, channels) + q.output = audio.NewSource[float32](bitDepth, sampleRate, channels) + queueLoopStart[float32](q) case audio.SourceInt16: - q.int16Output = audio.NewInt16Source(bitDepth, sampleRate, channels) + q.output = audio.NewSource[int16](bitDepth, sampleRate, channels) + queueLoopStart[int16](q) case audio.SourceInt32: - q.int32Output = audio.NewInt32Source(bitDepth, sampleRate, channels) + q.output = audio.NewSource[int32](bitDepth, sampleRate, channels) + queueLoopStart[int32](q) default: log.Panicf("not found source format %d", int(format)) } - - q.start() return q } -func (q *Queue) spliceSources(input audio.Source) (output audio.Source, cancel chan bool) { +func spliceHelper[T audio.AllowedSourceTypes](input audio.TypedSource[T]) (output audio.TypedSource[T], cancel chan bool) { cancel = make(chan bool, 1) + output = audio.NewSource[T](input.GetBitDepth(), input.GetSampleRate(), input.GetChannels()) - switch q.GetSource().GetFormat() { - case audio.SourceFloat32: - output = audio.NewFloat32Source(input.GetSampleRate(), input.GetChannels()) + bitDepth := input.GetBitDepth() - sourceChannel := input.ToFloat32().GetBlocks() - go func() { - defer output.Close() - L1: - for { - select { - case <-cancel: + sourceChannel := input.GetBlocks() + go func() { + defer output.Close() + L1: + for { + select { + case <-cancel: + break L1 + case block, more := <-sourceChannel: + if !more { + //no more blocks! break L1 - case block, more := <-sourceChannel: - if !more { - //no more blocks! - break L1 - } else { - output.IngestFloat32(block) - } + } else { + output.IngestNative(block, bitDepth) } } - - input.Unlock() - - //sink remaining - go audio.NewNullSink().Process(input) - }() - case audio.SourceInt16: - bitDepth := input.GetBitDepth() - if bitDepth > 16 { - bitDepth = 16 } - output = audio.NewInt16Source(bitDepth, input.GetSampleRate(), input.GetChannels()) - sourceChannel := input.ToInt16().GetBlocks() - go func() { - defer output.Close() - L2: - for { - select { - case <-cancel: - break L2 - case block, more := <-sourceChannel: - if !more { - //no more blocks! - break L2 - } else { - output.IngestInt16(block, bitDepth) - } - } - } + input.Unlock() - input.Unlock() - - //sink remaining - go audio.NewNullSink().Process(input) - }() - case audio.SourceInt32: - output = audio.NewInt32Source(input.GetBitDepth(), input.GetSampleRate(), input.GetChannels()) - - sourceChannel := input.ToInt32(input.GetBitDepth()).GetBlocks() - go func() { - defer output.Close() - L3: - for { - select { - case <-cancel: - break L3 - case block, more := <-sourceChannel: - if !more { - //no more blocks! - break L3 - } else { - output.IngestInt32(block, output.GetBitDepth()) - } - } - } - - input.Unlock() - - //sink remaining - go audio.NewNullSink().Process(input) - }() - default: - log.Panicf("not found source format %d", int(input.GetFormat())) - } + //sink remaining + go audio.NewNullSink().Process(input) + }() return } -func (q *Queue) start() { +func (q *Queue) spliceSources(input audio.Source) (audio.Source, chan bool) { + + switch q.GetSource().GetFormat() { + case audio.SourceFloat32: + return spliceHelper(input.ToFloat32()) + case audio.SourceInt16: + return spliceHelper(input.ToInt16()) + case audio.SourceInt32: + return spliceHelper(input.ToInt32(input.GetBitDepth())) + default: + log.Panicf("not found source format %d", int(input.GetFormat())) + } + + return nil, nil +} + +func queueLoopStart[T audio.AllowedSourceTypes](q *Queue) { q.wg.Add(1) go func() { defer q.wg.Done() var current *QueueEntry - - if q.float32Output != nil { - L1: - for { - q.lock.RLock() - if q.closed { - q.float32Output.Close() - break L1 - } - if len(q.queue) == 0 { //no more entries, wait for interrupt - q.lock.RUnlock() - <-q.interrupt - atomic.AddInt64(&q.interruptDepth, -1) - continue - } - current = q.queue[0] + var currentBlocks chan []T + L1: + for { + q.lock.RLock() + if q.closed { + q.output.Close() + break L1 + } + if len(q.queue) == 0 { //no more entries, wait for interrupt q.lock.RUnlock() + <-q.interrupt + atomic.AddInt64(&q.interruptDepth, -1) + continue + } + if current == nil || current.Identifier != q.queue[0].Identifier { + current = q.queue[0] + currentBlocks = current.Source.(audio.TypedSource[T]).GetBlocks() + } + q.lock.RUnlock() - F1: - for { - select { - case <-q.interrupt: - atomic.AddInt64(&q.interruptDepth, -1) - //force recheck + F1: + for { + select { + case <-q.interrupt: + atomic.AddInt64(&q.interruptDepth, -1) + //force recheck + break F1 + case block, more := <-currentBlocks: + if !more { + //no more blocks! skip + if current.EndCallback != nil { + current.EndCallback(q, current) + } + q.Remove(current.Identifier) break F1 - case block, more := <-current.Source.(*audio.Float32Source).Blocks: - if !more { - //no more blocks! skip - if current.EndCallback != nil { - current.EndCallback(q, current) - } - q.Remove(current.Identifier) - break F1 - } else { - if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 { - current.StartCallback(q, current) - } - current.ReadSamples += len(block) / current.Source.GetChannels() - q.float32Output.IngestFloat32(block) + } else { + if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 { + current.StartCallback(q, current) } + current.ReadSamples += len(block) / current.Source.GetChannels() + q.output.(audio.TypedSource[T]).IngestNative(block, current.Source.GetBitDepth()) } } - } - } else if q.int16Output != nil { - L2: - for { - q.lock.RLock() - if q.closed { - q.int16Output.Close() - break L2 - } - if len(q.queue) == 0 { //no more entries, wait for interrupt - q.lock.RUnlock() - <-q.interrupt - atomic.AddInt64(&q.interruptDepth, -1) - continue - } - current = q.queue[0] - q.lock.RUnlock() - F2: - for { - select { - case <-q.interrupt: - atomic.AddInt64(&q.interruptDepth, -1) - //force recheck - break F2 - case block, more := <-current.Source.(*audio.Int16Source).Blocks: - if !more { - //no more blocks! skip - if current.EndCallback != nil { - current.EndCallback(q, current) - } - q.Remove(current.Identifier) - break F2 - } else { - if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 { - current.StartCallback(q, current) - } - current.ReadSamples += len(block) / current.Source.GetChannels() - q.int16Output.IngestInt16(block, current.Source.GetBitDepth()) - } - } - } - - } - } else if q.int32Output != nil { - L3: - for { - q.lock.RLock() - if q.closed { - q.int32Output.Close() - break L3 - } - if len(q.queue) == 0 { //no more entries, wait for interrupt - q.lock.RUnlock() - <-q.interrupt - atomic.AddInt64(&q.interruptDepth, -1) - continue - } - current = q.queue[0] - q.lock.RUnlock() - - F3: - for { - select { - case <-q.interrupt: - atomic.AddInt64(&q.interruptDepth, -1) - //force recheck - break F3 - case block, more := <-current.Source.(*audio.Int32Source).Blocks: - if !more { - //no more blocks! skip - if current.EndCallback != nil { - current.EndCallback(q, current) - } - q.Remove(current.Identifier) - break F3 - } else { - if current.StartCallback != nil && current.ReadSamples == 0 && len(block) > 0 { - current.StartCallback(q, current) - } - current.ReadSamples += len(block) / current.Source.GetChannels() - q.int32Output.IngestInt32(block, current.Source.GetBitDepth()) - } - } - } - - } } }() - } + func (q *Queue) getFilterChain(source audio.Source) audio.Source { if q.GetChannels() == 1 { return filter.NewFilterChain(source, filter.MonoFilter{}, filter.NewResampleFilter(q.GetSampleRate(), filter.QualityFastest, 0), filter.SourceFormatFilter{ @@ -441,14 +311,7 @@ func (q *Queue) GetQueue() (entries []*QueueEntry) { } func (q *Queue) GetSource() audio.Source { - if q.float32Output != nil { - return q.float32Output - } else if q.int16Output != nil { - return q.int16Output - } else if q.int32Output != nil { - return q.int32Output - } - return nil + return q.output } func (q *Queue) GetSampleRate() int { diff --git a/audio/replaygain/filter_normalization.go b/audio/replaygain/filter_normalization.go index d746f8c..a7eab73 100644 --- a/audio/replaygain/filter_normalization.go +++ b/audio/replaygain/filter_normalization.go @@ -21,7 +21,7 @@ func NewNormalizationFilter(delayInSeconds int) NormalizationFilter { } func (f NormalizationFilter) Process(source audio.Source) audio.Source { - outSource := audio.NewFloat32Source(source.GetSampleRate(), source.GetChannels()) + outSource := audio.NewSource[float32](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer outSource.Close() diff --git a/audio/replaygain/normalization.go b/audio/replaygain/normalization.go index 6fa4cb8..41e6139 100644 --- a/audio/replaygain/normalization.go +++ b/audio/replaygain/normalization.go @@ -20,16 +20,24 @@ func GetTrackReplayGain(source audio.Source) (gain, peak float64, err error) { } defer state.Close() - if _, ok := source.(*audio.Int16Source); ok || source.GetBitDepth() == 16 { + if _, ok := source.(audio.TypedSource[int16]); ok { for block := range source.ToInt16().GetBlocks() { if err = state.AddShort(block); err != nil { return } } - } else if int32Source, ok := source.(*audio.Int32Source); ok { - for block := range int32Source.ToInt32(32).GetBlocks() { - if err = state.AddInt(block); err != nil { - return + } else if int32Source, ok := source.(audio.TypedSource[int32]); ok { + if source.GetBitDepth() == 16 { + for block := range source.ToInt16().GetBlocks() { + if err = state.AddShort(block); err != nil { + return + } + } + } else { + for block := range int32Source.ToInt32(32).GetBlocks() { + if err = state.AddInt(block); err != nil { + return + } } } } else { @@ -85,16 +93,24 @@ func GetAlbumReplayGain(sources []audio.Source) (albumGain, albumPeak float64, t defer wg.Done() var err error - if _, ok := source.(*audio.Int16Source); ok || source.GetBitDepth() == 16 { + if _, ok := source.(audio.TypedSource[int16]); ok { for block := range source.ToInt16().GetBlocks() { if err = state.AddShort(block); err != nil { return } } - } else if int32Source, ok := source.(*audio.Int32Source); ok { - for block := range int32Source.ToInt32(32).GetBlocks() { - if err = state.AddInt(block); err != nil { - return + } else if _, ok := source.(audio.TypedSource[int32]); ok { + if source.GetBitDepth() == 16 { + for block := range source.ToInt16().GetBlocks() { + if err = state.AddShort(block); err != nil { + return + } + } + } else { + for block := range source.ToInt32(32).GetBlocks() { + if err = state.AddInt(block); err != nil { + return + } } } } else { diff --git a/audio/sink.go b/audio/sink.go index 83808e2..12e760e 100644 --- a/audio/sink.go +++ b/audio/sink.go @@ -18,16 +18,16 @@ func NewNullSink() *NullSink { } func (n *NullSink) Process(source Source) { - if float32Source, ok := source.(*Float32Source); ok { - for range float32Source.GetBlocks() { + if f32Source, ok := source.(TypedSource[float32]); ok { + for range f32Source.GetBlocks() { } - } else if int16Source, ok := source.(*Int16Source); ok { - for range int16Source.GetBlocks() { + } else if i16Source, ok := source.(TypedSource[int16]); ok { + for range i16Source.GetBlocks() { } - } else if int32Source, ok := source.(*Int32Source); ok { - for range int32Source.GetBlocks() { + } else if i32Source, ok := source.(TypedSource[int32]); ok { + for range i32Source.GetBlocks() { } } else { @@ -58,16 +58,28 @@ func (n *ForwardSink) GetSamplesRead() int64 { return atomic.LoadInt64(&n.SamplesRead) } -func (n *ForwardSink) Process(source Source) { - processor := NewFloat32Source(source.GetSampleRate(), source.GetChannels()) +func forwardSinkProcess[T AllowedSourceTypes](n *ForwardSink, source TypedSource[T]) { + processor := NewSource[T](source.GetBitDepth(), source.GetSampleRate(), source.GetChannels()) go func() { defer processor.Close() - for block := range source.ToFloat32().GetBlocks() { + for block := range source.GetBlocks() { atomic.AddInt64((*int64)(&n.Duration), int64((time.Second*time.Duration(len(block)/source.GetChannels()))/time.Duration(source.GetSampleRate()))) atomic.AddInt64(&n.SamplesRead, int64(len(block)/source.GetChannels())) - processor.IngestFloat32(block) + processor.IngestNative(block, source.GetBitDepth()) } }() n.Target.Process(processor) } + +func (n *ForwardSink) Process(source Source) { + if f32Source, ok := source.(TypedSource[float32]); ok { + forwardSinkProcess(n, f32Source) + } else if i16Source, ok := source.(TypedSource[int16]); ok { + forwardSinkProcess(n, i16Source) + } else if i32Source, ok := source.(TypedSource[int32]); ok { + forwardSinkProcess(n, i32Source) + } else { + forwardSinkProcess(n, source.ToFloat32()) + } +} diff --git a/audio/source.go b/audio/source.go index 34d8334..1e2dae4 100644 --- a/audio/source.go +++ b/audio/source.go @@ -1,564 +1,10 @@ package audio import ( - "git.gammaspectra.live/S.O.N.G/Kirika/vector" + "errors" + "unsafe" ) -type Float32Source struct { - SampleRate int - Channels int - Blocks chan []float32 - locked bool -} - -func NewFloat32Source(sampleRate, channels int) *Float32Source { - return &Float32Source{ - SampleRate: sampleRate, - Channels: channels, - Blocks: make(chan []float32), - } -} - -func (s *Float32Source) Split(n int) (sources []Source) { - if s.Locked() { - return - } - sources = make([]Source, n) - for i := range sources { - copiedSource := *s - copiedSource.Blocks = make(chan []float32, cap(s.Blocks)) - sources[i] = &copiedSource - } - - go func() { - defer func() { - for _, source := range sources { - source.Close() - } - }() - - for block := range s.GetBlocks() { - for _, source := range sources { - source.IngestFloat32(block) - } - } - }() - - return -} - -func (s *Float32Source) GetBlocks() chan []float32 { - if s.Locked() { - return nil - } - s.locked = true - return s.Blocks -} - -func (s *Float32Source) Close() { - close(s.Blocks) -} - -func (s *Float32Source) GetSampleRate() int { - return s.SampleRate -} - -func (s *Float32Source) GetChannels() int { - return s.Channels -} - -func (s *Float32Source) GetBitDepth() int { - return 0 -} - -func (s *Float32Source) GetFormat() SourceFormat { - return SourceFloat32 -} - -func (s *Float32Source) Locked() bool { - return s.locked -} - -func (s *Float32Source) Unlock() { - s.locked = false -} - -func (s *Float32Source) ToFloat32() *Float32Source { - return s -} - -func (s *Float32Source) ToInt16() *Int16Source { - if s.Locked() { - return nil - } - source := &Int16Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - BitDepth: 16, - Blocks: make(chan []int16), - } - - go func() { - defer source.Close() - for block := range s.GetBlocks() { - source.Blocks <- vector.Float32ToInt16(block) - } - }() - - return source -} - -func (s *Float32Source) ToInt32(bitDepth int) *Int32Source { - if s.Locked() { - return nil - } - source := &Int32Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - BitDepth: bitDepth, - Blocks: make(chan []int32), - } - - go func() { - defer source.Close() - for block := range s.GetBlocks() { - source.Blocks <- vector.Float32ToInt32(block, bitDepth) - } - }() - - return source -} - -func (s *Float32Source) IngestFloat32(buf []float32) { - s.Blocks <- buf -} - -func (s *Float32Source) IngestInt8(buf []int8, bitDepth int) { - s.Blocks <- vector.Int8ToFloat32(buf, bitDepth) -} - -func (s *Float32Source) IngestInt16(buf []int16, bitDepth int) { - s.Blocks <- vector.Int16ToFloat32(buf, bitDepth) -} - -func (s *Float32Source) IngestInt24(buf []byte, bitDepth int) { - s.Blocks <- vector.Int24ToFloat32(buf, bitDepth) -} - -func (s *Float32Source) IngestInt32(buf []int32, bitDepth int) { - s.Blocks <- vector.Int32ToFloat32(buf, bitDepth) -} - -func (s *Float32Source) GetBlocksInterface() interface{} { - return s.GetBlocks() -} - -type Int16Source struct { - BitDepth int - SampleRate int - Channels int - Blocks chan []int16 - locked bool -} - -func NewInt16Source(bitDepth, sampleRate, channels int) *Int16Source { - if bitDepth <= 0 || bitDepth > 16 { - return nil - } - return &Int16Source{ - BitDepth: bitDepth, - SampleRate: sampleRate, - Channels: channels, - Blocks: make(chan []int16), - } -} - -func (s *Int16Source) Split(n int) (sources []Source) { - if s.Locked() { - return - } - sources = make([]Source, n) - for i := range sources { - copiedSource := *s - copiedSource.Blocks = make(chan []int16, cap(s.Blocks)) - sources[i] = &copiedSource - } - - go func() { - defer func() { - for _, source := range sources { - source.Close() - } - }() - - for block := range s.GetBlocks() { - for _, source := range sources { - source.IngestInt16(block, s.BitDepth) - } - } - }() - - return -} - -func (s *Int16Source) GetBlocks() chan []int16 { - if s.Locked() { - return nil - } - s.locked = true - return s.Blocks -} - -func (s *Int16Source) Close() { - close(s.Blocks) -} - -func (s *Int16Source) GetSampleRate() int { - return s.SampleRate -} - -func (s *Int16Source) GetChannels() int { - return s.Channels -} - -func (s *Int16Source) GetBitDepth() int { - return s.BitDepth -} - -func (s *Int16Source) GetFormat() SourceFormat { - return SourceInt16 -} - -func (s *Int16Source) Locked() bool { - return s.locked -} - -func (s *Int16Source) Unlock() { - s.locked = false -} - -func (s *Int16Source) ToFloat32() *Float32Source { - if s.Locked() { - return nil - } - - source := &Float32Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - Blocks: make(chan []float32), - } - - go func() { - defer source.Close() - for block := range s.GetBlocks() { - source.Blocks <- vector.Int16ToFloat32(block, s.BitDepth) - } - }() - - return source -} - -func (s *Int16Source) ToInt16() *Int16Source { - return s -} - -func (s *Int16Source) ToInt32(bitDepth int) *Int32Source { - if s.Locked() { - return nil - } - - source := &Int32Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - BitDepth: bitDepth, - Blocks: make(chan []int32), - } - - go func() { - diff := s.BitDepth - bitDepth - - if diff >= 0 { - defer source.Close() - for block := range s.GetBlocks() { - buf := make([]int32, len(block)) - for i := range block { - buf[i] = int32(block[i]) >> diff - } - source.Blocks <- buf - } - } else { - diff = -diff - defer source.Close() - for block := range s.GetBlocks() { - buf := make([]int32, len(block)) - for i := range block { - buf[i] = int32(block[i]) << diff - } - source.Blocks <- buf - } - } - }() - - return source -} - -func (s *Int16Source) IngestFloat32(buf []float32) { - s.Blocks <- vector.Float32ToInt16(buf) -} - -func (s *Int16Source) IngestInt8(buf []int8, bitDepth int) { - block := make([]int16, len(buf)) - for i := range buf { - block[i] = int16(buf[i]) << (s.BitDepth - bitDepth) - } - s.Blocks <- block -} - -func (s *Int16Source) IngestInt16(buf []int16, bitDepth int) { - if bitDepth == s.BitDepth { - s.Blocks <- buf - } else { - block := make([]int16, len(buf)) - for i := range buf { - block[i] = buf[i] << (s.BitDepth - bitDepth) - } - s.Blocks <- block - } -} - -func (s *Int16Source) IngestInt24(buf []byte, bitDepth int) { - block := make([]int16, len(buf)/3) - - for i := 0; i < len(buf); i += 3 { - sample := uint32(buf[i]) - sample += uint32(buf[i+1]) << 8 - sample += uint32(buf[i+2]) << 16 - - //sign extend - block[i/3] = int16(int32(sample<<8) >> (bitDepth - s.BitDepth + 8)) - } - s.Blocks <- block -} - -func (s *Int16Source) IngestInt32(buf []int32, bitDepth int) { - block := make([]int16, len(buf)) - for i := range buf { - block[i] = int16(buf[i] >> (bitDepth - s.BitDepth)) - } - s.Blocks <- block -} - -func (s *Int16Source) GetBlocksInterface() interface{} { - return s.GetBlocks() -} - -type Int32Source struct { - BitDepth int - SampleRate int - Channels int - Blocks chan []int32 - locked bool -} - -func NewInt32Source(bitDepth, sampleRate, channels int) *Int32Source { - if bitDepth <= 0 || bitDepth > 32 { - return nil - } - return &Int32Source{ - BitDepth: bitDepth, - SampleRate: sampleRate, - Channels: channels, - Blocks: make(chan []int32), - } -} - -func (s *Int32Source) Split(n int) (sources []Source) { - if s.Locked() { - return - } - sources = make([]Source, n) - for i := range sources { - copiedSource := *s - copiedSource.Blocks = make(chan []int32, cap(s.Blocks)) - sources[i] = &copiedSource - } - - go func() { - defer func() { - for _, source := range sources { - source.Close() - } - }() - - for block := range s.GetBlocks() { - for _, source := range sources { - source.IngestInt32(block, s.BitDepth) - } - } - }() - - return -} - -func (s *Int32Source) GetBlocks() chan []int32 { - if s.Locked() { - return nil - } - s.locked = true - return s.Blocks -} - -func (s *Int32Source) Unlock() { - s.locked = false -} - -func (s *Int32Source) Close() { - close(s.Blocks) -} - -func (s *Int32Source) GetSampleRate() int { - return s.SampleRate -} - -func (s *Int32Source) GetChannels() int { - return s.Channels -} - -func (s *Int32Source) GetBitDepth() int { - return s.BitDepth -} - -func (s *Int32Source) GetFormat() SourceFormat { - return SourceInt32 -} - -func (s *Int32Source) Locked() bool { - return s.locked -} - -func (s *Int32Source) ToInt16() *Int16Source { - if s.Locked() { - return nil - } - - source := &Int16Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - BitDepth: 16, - Blocks: make(chan []int16), - } - - go func() { - defer source.Close() - - for block := range s.GetBlocks() { - source.IngestInt16(vector.Int32ToInt16(block, s.BitDepth), 16) - } - }() - - return source -} - -func (s *Int32Source) ToInt32(bitDepth int) *Int32Source { - if s.BitDepth == bitDepth { - return s - } - - source := &Int32Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - BitDepth: bitDepth, - Blocks: make(chan []int32), - } - - go func() { - diff := s.BitDepth - bitDepth - defer source.Close() - - if diff >= 0 { - for block := range s.GetBlocks() { - buf := make([]int32, len(block)) - for i := range block { - buf[i] = block[i] >> diff - } - source.IngestInt32(buf, bitDepth) - } - } else { - diff = -diff - for block := range s.GetBlocks() { - buf := make([]int32, len(block)) - for i := range block { - buf[i] = block[i] << diff - } - source.IngestInt32(buf, bitDepth) - } - } - }() - - return source -} - -func (s *Int32Source) ToFloat32() *Float32Source { - if s.Locked() { - return nil - } - - source := &Float32Source{ - SampleRate: s.SampleRate, - Channels: s.Channels, - Blocks: make(chan []float32), - } - - go func() { - defer source.Close() - for block := range s.GetBlocks() { - source.Blocks <- vector.Int32ToFloat32(block, s.BitDepth) - } - }() - - return source -} - -func (s *Int32Source) IngestFloat32(buf []float32) { - s.Blocks <- vector.Float32ToInt32(buf, s.BitDepth) -} - -func (s *Int32Source) IngestInt8(buf []int8, bitDepth int) { - block := make([]int32, len(buf)) - for i := range buf { - block[i] = int32(buf[i]) << (s.BitDepth - bitDepth) - } - s.Blocks <- block -} - -func (s *Int32Source) IngestInt16(buf []int16, bitDepth int) { - block := make([]int32, len(buf)) - for i := range buf { - block[i] = int32(buf[i]) << (s.BitDepth - bitDepth) - } - s.Blocks <- block -} - -func (s *Int32Source) IngestInt24(buf []byte, bitDepth int) { - s.Blocks <- vector.BytesToInt32(buf, bitDepth) -} - -func (s *Int32Source) IngestInt32(buf []int32, bitDepth int) { - if bitDepth == s.BitDepth { - s.Blocks <- buf - } else { - block := make([]int32, len(buf)) - for i := range buf { - block[i] = buf[i] << (s.BitDepth - bitDepth) - } - s.Blocks <- block - } -} - -func (s *Int32Source) GetBlocksInterface() interface{} { - return s.GetBlocks() -} - type SourceFormat int const ( @@ -568,21 +14,124 @@ const ( 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 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: + out := make([]int32, nsamples) + copy(out, unsafe.Slice((*int32)(unsafe.Pointer(&bufferSlice)), nsamples)) + s.IngestInt32(out, bitDepth) + return nil + case 24: + s.IngestInt24(unsafe.Slice((*byte)(unsafe.Pointer(&bufferSlice)), nsamples*3), bitDepth) + return nil + case 16: + out := make([]int16, nsamples) + copy(out, unsafe.Slice((*int16)(unsafe.Pointer(&bufferSlice)), nsamples)) + s.IngestInt16(out, bitDepth) + return nil + case 8: + s.IngestInt8(unsafe.Slice((*int8)(unsafe.Pointer(&bufferSlice)), nsamples), bitDepth) + 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() *Float32Source - ToInt16() *Int16Source - ToInt32(bitDepth int) *Int32Source + 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) - GetBlocksInterface() interface{} //Split a Source into multiple ones. After calling this function, the source is locked. Split(n int) []Source diff --git a/audio/source_float32.go b/audio/source_float32.go new file mode 100644 index 0000000..482bf1f --- /dev/null +++ b/audio/source_float32.go @@ -0,0 +1,132 @@ +package audio + +import ( + "git.gammaspectra.live/S.O.N.G/Kirika/vector" +) + +type float32Source struct { + BitDepth int + SampleRate int + Channels int + Blocks chan []float32 + locked bool +} + +func newFloat32Source(bitDepth, sampleRate, channels int) TypedSource[float32] { + return &float32Source{ + BitDepth: bitDepth, + SampleRate: sampleRate, + Channels: channels, + Blocks: make(chan []float32), + } +} + +func (s *float32Source) Split(n int) []Source { + return Split[float32](s, n) +} + +func (s *float32Source) New() TypedSource[float32] { + return newFloat32Source(s.BitDepth, s.SampleRate, s.Channels) +} + +func (s *float32Source) GetBlocks() chan []float32 { + if s.Locked() { + return nil + } + s.locked = true + return s.Blocks +} + +func (s *float32Source) Close() { + close(s.Blocks) +} + +func (s *float32Source) GetSampleRate() int { + return s.SampleRate +} + +func (s *float32Source) GetChannels() int { + return s.Channels +} + +func (s *float32Source) GetBitDepth() int { + return s.BitDepth +} + +func (s *float32Source) Locked() bool { + return s.locked +} + +func (s *float32Source) Unlock() { + s.locked = false +} + +func (s *float32Source) SwapBlocks(blocks chan []float32) chan []float32 { + oldBlocks := s.Blocks + s.Blocks = blocks + return oldBlocks +} + +func (s *float32Source) GetFormat() SourceFormat { + return SourceFloat32 +} + +func (s *float32Source) ToFloat32() TypedSource[float32] { + return s +} + +func (s *float32Source) ToInt16() TypedSource[int16] { + if s.Locked() { + return nil + } + source := newInt16Source(16, s.SampleRate, s.Channels) + + go func() { + defer source.Close() + for block := range s.GetBlocks() { + source.IngestInt16(vector.Float32ToInt16(block), 16) + } + }() + + return source +} + +func (s *float32Source) ToInt32(bitDepth int) TypedSource[int32] { + if s.Locked() { + return nil + } + source := newInt32Source(bitDepth, s.SampleRate, s.Channels) + + go func() { + defer source.Close() + for block := range s.GetBlocks() { + source.IngestInt32(vector.Float32ToInt32(block, bitDepth), bitDepth) + } + }() + + return source +} + +func (s *float32Source) IngestFloat32(buf []float32) { + s.Blocks <- buf +} + +func (s *float32Source) IngestInt8(buf []int8, bitDepth int) { + s.Blocks <- vector.Int8ToFloat32(buf, bitDepth) +} + +func (s *float32Source) IngestInt16(buf []int16, bitDepth int) { + s.Blocks <- vector.Int16ToFloat32(buf, bitDepth) +} + +func (s *float32Source) IngestInt24(buf []byte, bitDepth int) { + s.Blocks <- vector.Int24ToFloat32(buf, bitDepth) +} + +func (s *float32Source) IngestInt32(buf []int32, bitDepth int) { + s.Blocks <- vector.Int32ToFloat32(buf, bitDepth) +} + +func (s *float32Source) IngestNative(buf []float32, bitDepth int) { + s.IngestFloat32(buf) +} diff --git a/audio/source_int16.go b/audio/source_int16.go new file mode 100644 index 0000000..85f8a0c --- /dev/null +++ b/audio/source_int16.go @@ -0,0 +1,181 @@ +package audio + +import ( + "git.gammaspectra.live/S.O.N.G/Kirika/vector" +) + +type int16Source struct { + BitDepth int + SampleRate int + Channels int + Blocks chan []int16 + locked bool +} + +func newInt16Source(bitDepth, sampleRate, channels int) TypedSource[int16] { + if bitDepth <= 0 || bitDepth > 16 { + return nil + } + return &int16Source{ + BitDepth: bitDepth, + SampleRate: sampleRate, + Channels: channels, + Blocks: make(chan []int16), + } +} + +func (s *int16Source) Split(n int) []Source { + return Split[int16](s, n) +} + +func (s *int16Source) New() TypedSource[int16] { + return newInt16Source(s.BitDepth, s.SampleRate, s.Channels) +} + +func (s *int16Source) GetBlocks() chan []int16 { + if s.Locked() { + return nil + } + s.locked = true + return s.Blocks +} + +func (s *int16Source) Close() { + close(s.Blocks) +} + +func (s *int16Source) GetSampleRate() int { + return s.SampleRate +} + +func (s *int16Source) GetChannels() int { + return s.Channels +} + +func (s *int16Source) GetBitDepth() int { + return s.BitDepth +} + +func (s *int16Source) Locked() bool { + return s.locked +} + +func (s *int16Source) Unlock() { + s.locked = false +} + +func (s *int16Source) SwapBlocks(blocks chan []int16) chan []int16 { + oldBlocks := s.Blocks + s.Blocks = blocks + return oldBlocks +} + +func (s *int16Source) GetFormat() SourceFormat { + return SourceInt16 +} + +func (s *int16Source) ToFloat32() TypedSource[float32] { + if s.Locked() { + return nil + } + + source := newFloat32Source(s.BitDepth, s.SampleRate, s.Channels) + + go func() { + defer source.Close() + for block := range s.GetBlocks() { + source.IngestFloat32(vector.Int16ToFloat32(block, s.BitDepth)) + } + }() + + return source +} + +func (s *int16Source) ToInt16() TypedSource[int16] { + return s +} + +func (s *int16Source) ToInt32(bitDepth int) TypedSource[int32] { + if s.Locked() { + return nil + } + + source := newInt32Source(bitDepth, s.SampleRate, s.Channels) + + go func() { + diff := s.BitDepth - bitDepth + + if diff >= 0 { + defer source.Close() + for block := range s.GetBlocks() { + buf := make([]int32, len(block)) + for i := range block { + buf[i] = int32(block[i]) >> diff + } + source.IngestInt32(buf, bitDepth) + } + } else { + diff = -diff + defer source.Close() + for block := range s.GetBlocks() { + buf := make([]int32, len(block)) + for i := range block { + buf[i] = int32(block[i]) << diff + } + source.IngestInt32(buf, bitDepth) + } + } + }() + + return source +} + +func (s *int16Source) IngestFloat32(buf []float32) { + s.Blocks <- vector.Float32ToInt16(buf) +} + +func (s *int16Source) IngestInt8(buf []int8, bitDepth int) { + block := make([]int16, len(buf)) + for i := range buf { + block[i] = int16(buf[i]) << (s.BitDepth - bitDepth) + } + s.Blocks <- block +} + +func (s *int16Source) IngestInt16(buf []int16, bitDepth int) { + if bitDepth == s.BitDepth { + s.Blocks <- buf + } else { + block := make([]int16, len(buf)) + for i := range buf { + block[i] = buf[i] << (s.BitDepth - bitDepth) + } + s.Blocks <- block + } +} + +func (s *int16Source) IngestInt24(buf []byte, bitDepth int) { + block := make([]int16, len(buf)/3) + + for i := 0; i < len(buf); i += 3 { + sample := uint32(buf[i]) + sample += uint32(buf[i+1]) << 8 + sample += uint32(buf[i+2]) << 16 + + //sign extend + block[i/3] = int16(int32(sample<<8) >> (bitDepth - s.BitDepth + 8)) + } + s.Blocks <- block +} + +func (s *int16Source) IngestInt32(buf []int32, bitDepth int) { + block := make([]int16, len(buf)) + for i := range buf { + block[i] = int16(buf[i] >> (bitDepth - s.BitDepth)) + } + s.Blocks <- block +} + +func (s *int16Source) IngestNative(buf []int16, bitDepth int) { + s.IngestInt16(buf, bitDepth) +} diff --git a/audio/source_int32.go b/audio/source_int32.go new file mode 100644 index 0000000..69f418c --- /dev/null +++ b/audio/source_int32.go @@ -0,0 +1,184 @@ +package audio + +import ( + "git.gammaspectra.live/S.O.N.G/Kirika/vector" +) + +type int32Source struct { + BitDepth int + SampleRate int + Channels int + Blocks chan []int32 + locked bool +} + +func newInt32Source(bitDepth, sampleRate, channels int) TypedSource[int32] { + if bitDepth <= 0 || bitDepth > 32 { + return nil + } + return &int32Source{ + BitDepth: bitDepth, + SampleRate: sampleRate, + Channels: channels, + Blocks: make(chan []int32), + } +} + +func (s *int32Source) Split(n int) []Source { + return Split[int32](s, n) +} + +func (s *int32Source) New() TypedSource[int32] { + return newInt32Source(s.BitDepth, s.SampleRate, s.Channels) +} + +func (s *int32Source) GetBlocks() chan []int32 { + if s.Locked() { + return nil + } + s.locked = true + return s.Blocks +} + +func (s *int32Source) Close() { + close(s.Blocks) +} + +func (s *int32Source) GetSampleRate() int { + return s.SampleRate +} + +func (s *int32Source) GetChannels() int { + return s.Channels +} + +func (s *int32Source) GetBitDepth() int { + return s.BitDepth +} + +func (s *int32Source) Locked() bool { + return s.locked +} + +func (s *int32Source) Unlock() { + s.locked = false +} + +func (s *int32Source) SwapBlocks(blocks chan []int32) chan []int32 { + oldBlocks := s.Blocks + s.Blocks = blocks + return oldBlocks +} + +func (s *int32Source) GetFormat() SourceFormat { + return SourceInt32 +} + +func (s *int32Source) ToInt16() TypedSource[int16] { + if s.Locked() { + return nil + } + + source := newInt16Source(16, s.SampleRate, s.Channels) + + go func() { + defer source.Close() + + for block := range s.GetBlocks() { + source.IngestInt16(vector.Int32ToInt16(block, s.BitDepth), 16) + } + }() + + return source +} + +func (s *int32Source) ToInt32(bitDepth int) TypedSource[int32] { + if s.BitDepth == bitDepth { + return s + } + + source := newInt32Source(bitDepth, s.SampleRate, s.Channels) + + go func() { + diff := s.BitDepth - bitDepth + defer source.Close() + + if diff >= 0 { + for block := range s.GetBlocks() { + buf := make([]int32, len(block)) + for i := range block { + buf[i] = block[i] >> diff + } + source.IngestInt32(buf, bitDepth) + } + } else { + diff = -diff + for block := range s.GetBlocks() { + buf := make([]int32, len(block)) + for i := range block { + buf[i] = block[i] << diff + } + source.IngestInt32(buf, bitDepth) + } + } + }() + + return source +} + +func (s *int32Source) ToFloat32() TypedSource[float32] { + if s.Locked() { + return nil + } + + source := newFloat32Source(s.BitDepth, s.SampleRate, s.Channels) + + go func() { + defer source.Close() + for block := range s.GetBlocks() { + source.IngestFloat32(vector.Int32ToFloat32(block, s.BitDepth)) + } + }() + + return source +} + +func (s *int32Source) IngestFloat32(buf []float32) { + s.Blocks <- vector.Float32ToInt32(buf, s.BitDepth) +} + +func (s *int32Source) IngestInt8(buf []int8, bitDepth int) { + block := make([]int32, len(buf)) + for i := range buf { + block[i] = int32(buf[i]) << (s.BitDepth - bitDepth) + } + s.Blocks <- block +} + +func (s *int32Source) IngestInt16(buf []int16, bitDepth int) { + block := make([]int32, len(buf)) + for i := range buf { + block[i] = int32(buf[i]) << (s.BitDepth - bitDepth) + } + s.Blocks <- block +} + +func (s *int32Source) IngestInt24(buf []byte, bitDepth int) { + s.Blocks <- vector.BytesToInt32(buf, bitDepth) +} + +func (s *int32Source) IngestInt32(buf []int32, bitDepth int) { + if bitDepth == s.BitDepth { + s.Blocks <- buf + } else { + block := make([]int32, len(buf)) + for i := range buf { + block[i] = buf[i] << (s.BitDepth - bitDepth) + } + s.Blocks <- block + } +} + +func (s *int32Source) IngestNative(buf []int32, bitDepth int) { + s.IngestInt32(buf, bitDepth) +}