Reduce dependencies, vendor resampler part of github.com/oov/audio
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
DataHoarder 2022-11-30 08:09:16 +01:00
parent f56867ca73
commit 6fde234fe6
Signed by: DataHoarder
SSH key fingerprint: SHA256:OLTRf6Fl87G52SiR7sWLGNzlJt4WOX+tfI2yxo0z7xk
9 changed files with 952 additions and 21 deletions

View file

@ -17,7 +17,7 @@ Collection of audio utilities for decoding/encoding/processing files and streams
|:----------:|:----------------------------------------------------------------------------------------:|:-------:|:---------------:|:-------------:|:-------:|:---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| **FLAC** | [FLAC](https://xiph.org/flac/format.html), [Ogg](https://xiph.org/flac/ogg_mapping.html) | ✅ | ✅ | `int32` | ✅ | Adjustable encoding compression level and block size.<br/>Decoding/encoding by [libFLAC](https://github.com/xiph/flac) via [goflac](https://git.gammaspectra.live/S.O.N.G/goflac).<br/>If [goflac](https://git.gammaspectra.live/S.O.N.G/goflac) codec is disabled, [flacgo](https://git.gammaspectra.live/S.O.N.G/flacgo) decoder/encoder will be used. |
| **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) | ✅ | ❌ | `int16` | ✅ | Adjustable encoding bitrate and mode.<br/>Decoding via [minimp3](https://github.com/kvark128/minimp3), encoding by [LAME](https://lame.sourceforge.io/) via [go-lame](https://github.com/viert/go-lame). |
| **MP3** | [MP3](http://mpgedit.org/mpgedit/mpeg_format/MP3Format.html) | ✅ | ❌ | `int16` | ✅ | Adjustable encoding bitrate and mode.<br/>Decoding via [S.O.N.G/minimp3](https://git.gammaspectra.live/S.O.N.G/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) | ✅ | ❌ | `float32` | ✅ | Adjustable encoding bitrate.<br/>Decoding/encoding by [libopus](https://github.com/xiph/opus) via [go-pus](https://git.gammaspectra.live/S.O.N.G/go-pus).<br/>Linked Ogg streams of different channel count are not supported. |
| **Vorbis** | [Ogg](https://www.xiph.org/ogg/doc/framing.html) | ✅ | ❌ | `float32` | ✅ | Decoding/encoding by [libvorbis](https://github.com/xiph/vorbis) via [go-vorbis](https://git.gammaspectra.live/S.O.N.G/go-vorbis).<br/>If [go-vorbis](https://git.gammaspectra.live/S.O.N.G/go-vorbis) is disabled, [jfreymuth/vorbis](https://github.com/jfreymuth/vorbis) via [jfreymuth/oggvorbis](https://github.com/jfreymuth/oggvorbis) decoder will be used.<br/>Linked Ogg streams of different channel count are not supported. |
| **AAC** | [ADTS](https://wiki.multimedia.cx/index.php/ADTS), ADIF*, MP4** | ✅ | ❌ | `int16` | ✅ | Adjustable encoding bitrate and mode (LC, HEv1, HEv2).<br/>Decoding/encoding by [FDK-AAC](https://github.com/mstorsjo/fdk-aac) via [go-fdkaac](https://git.gammaspectra.live/S.O.N.G/go-fdkaac).<br/>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.<br/>*ADIF only supported on encoding.<br/>**MP4 only supported on encoding, and fragmented MP4 currently. |
@ -248,10 +248,6 @@ Subdependencies that are not cgo-based are denoted in cursive.
| [S.O.N.G/minimp3](https://git.gammaspectra.live/S.O.N.G/minimp3) | Go | [MIT](https://git.gammaspectra.live/S.O.N.G/minimp3/src/branch/master/LICENSE.txt) | |
| [lieff/minimp3](https://github.com/lieff/minimp3) | C | [CC0 1.0](https://github.com/lieff/minimp3/blob/master/LICENSE) | Subdependency and included as part of _S.O.N.G/minimp3_. |
| [S.O.N.G/flacgo](https://git.gammaspectra.live/S.O.N.G/flacgo) | Go | [The Unlicense](https://git.gammaspectra.live/S.O.N.G/flacgoblob/master/LICENSE) | |
| [oov/audio](https://github.com/oov/audio) | Go | [MIT](https://github.com/oov/audio/blob/master/LICENSE) | Used only for its audio resampler. |
| _[go-audio/audio](https://github.com/go-audio/audio)_ | Go | [Apache 2.0](https://github.com/go-audio/audio/blob/master/LICENSE) | Subdependency of _S.O.N.G/flacgo_. Only used on tests there. |
| _[go-audio/wav](https://github.com/go-audio/wav)_ | Go | [Apache 2.0](https://github.com/go-audio/wav/blob/master/LICENSE) | Subdependency of _S.O.N.G/flacgo_. Only used on tests there. |
| _[go-audio/riff](https://github.com/go-audio/riff)_ | Go | [Apache 2.0](https://github.com/go-audio/riff/blob/master/LICENSE) | Subdependency of _go-audio/wav_. Only used on tests there. |
| _[icza/bitio](https://github.com/icza/bitio)_ | Go | [Apache 2.0](https://github.com/icza/bitio/blob/master/LICENSE-APACHE) or [LGPL v2.1](https://github.com/icza/bitio/blob/master/LICENSE-LGPL-v2.1) | Subdependency of _S.O.N.G/flacgo_. |
| [sssgun/mp3](https://github.com/sssgun/mp3) | Go | [MIT](https://github.com/sssgun/mp3/blob/master/LICENSE) | |
| [viert/go-lame](https://github.com/viert/go-lame) | Go | [MIT](https://github.com/viert/go-lame/blob/master/LICENSE) | |

View file

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2013 Masanobu YOSHIOKA
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View file

@ -0,0 +1,3 @@
# resampler_nocgo
Taken from [github.com/oov/audio](https://github.com/oov/audio/tree/8e73d0e7f9fdaa878160a5134a2c6993dbee286d/resampler) implementation

View file

@ -0,0 +1,189 @@
// Copyright (C) 2007-2008 Jean-Marc Valin
// Copyright (C) 2008 Thorvald Natvig
// Copyright (C) 2013 Oov
//
// Arbitrary resampling code
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. The name of the author may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
package resampler
type kaiserTable struct {
table []float64
oversample int
}
type quality struct {
baseLength int
oversample int
downsampleBandwidth float64
upsampleBandwidth float64
table *kaiserTable
}
var (
kaiser12 = kaiserTable{
table: []float64{
0.99859849, 1.00000000, 0.99859849, 0.99440475, 0.98745105, 0.97779076,
0.96549770, 0.95066529, 0.93340547, 0.91384741, 0.89213598, 0.86843014,
0.84290116, 0.81573067, 0.78710866, 0.75723148, 0.72629970, 0.69451601,
0.66208321, 0.62920216, 0.59606986, 0.56287762, 0.52980938, 0.49704014,
0.46473455, 0.43304576, 0.40211431, 0.37206735, 0.34301800, 0.31506490,
0.28829195, 0.26276832, 0.23854851, 0.21567274, 0.19416736, 0.17404546,
0.15530766, 0.13794294, 0.12192957, 0.10723616, 0.09382272, 0.08164178,
0.07063950, 0.06075685, 0.05193064, 0.04409466, 0.03718069, 0.03111947,
0.02584161, 0.02127838, 0.01736250, 0.01402878, 0.01121463, 0.00886058,
0.00691064, 0.00531256, 0.00401805, 0.00298291, 0.00216702, 0.00153438,
0.00105297, 0.00069463, 0.00043489, 0.00025272, 0.00013031, 0.0000527734,
0.00001000, 0.00000000},
oversample: 64,
}
kaiser10 = kaiserTable{
table: []float64{
0.99537781, 1.00000000, 0.99537781, 0.98162644, 0.95908712, 0.92831446,
0.89005583, 0.84522401, 0.79486424, 0.74011713, 0.68217934, 0.62226347,
0.56155915, 0.50119680, 0.44221549, 0.38553619, 0.33194107, 0.28205962,
0.23636152, 0.19515633, 0.15859932, 0.12670280, 0.09935205, 0.07632451,
0.05731132, 0.04193980, 0.02979584, 0.02044510, 0.01345224, 0.00839739,
0.00488951, 0.00257636, 0.00115101, 0.00035515, 0.00000000, 0.00000000},
oversample: 32,
}
kaiser8 = kaiserTable{
table: []float64{
0.99635258, 1.00000000, 0.99635258, 0.98548012, 0.96759014, 0.94302200,
0.91223751, 0.87580811, 0.83439927, 0.78875245, 0.73966538, 0.68797126,
0.63451750, 0.58014482, 0.52566725, 0.47185369, 0.41941150, 0.36897272,
0.32108304, 0.27619388, 0.23465776, 0.19672670, 0.16255380, 0.13219758,
0.10562887, 0.08273982, 0.06335451, 0.04724088, 0.03412321, 0.02369490,
0.01563093, 0.00959968, 0.00527363, 0.00233883, 0.00050000, 0.00000000},
oversample: 32,
}
kaiser6 = kaiserTable{
table: []float64{
0.99733006, 1.00000000, 0.99733006, 0.98935595, 0.97618418, 0.95799003,
0.93501423, 0.90755855, 0.87598009, 0.84068475, 0.80211977, 0.76076565,
0.71712752, 0.67172623, 0.62508937, 0.57774224, 0.53019925, 0.48295561,
0.43647969, 0.39120616, 0.34752997, 0.30580127, 0.26632152, 0.22934058,
0.19505503, 0.16360756, 0.13508755, 0.10953262, 0.08693120, 0.06722600,
0.05031820, 0.03607231, 0.02432151, 0.01487334, 0.00752000, 0.00000000},
oversample: 32,
}
qualityMap = []quality{
// Q0
quality{
baseLength: 8,
oversample: 4,
downsampleBandwidth: 0.830,
upsampleBandwidth: 0.860,
table: &kaiser6,
},
// Q1
quality{
baseLength: 16,
oversample: 4,
downsampleBandwidth: 0.850,
upsampleBandwidth: 0.880,
table: &kaiser6,
},
// Q2
quality{
baseLength: 32,
oversample: 4,
downsampleBandwidth: 0.882,
upsampleBandwidth: 0.910,
table: &kaiser6,
},
// Q3
quality{
baseLength: 48,
oversample: 8,
downsampleBandwidth: 0.895,
upsampleBandwidth: 0.917,
table: &kaiser8,
},
// Q4
quality{
baseLength: 64,
oversample: 8,
downsampleBandwidth: 0.921,
upsampleBandwidth: 0.940,
table: &kaiser8,
},
// Q5
quality{
baseLength: 80,
oversample: 16,
downsampleBandwidth: 0.922,
upsampleBandwidth: 0.940,
table: &kaiser10,
},
// Q6
quality{
baseLength: 96,
oversample: 16,
downsampleBandwidth: 0.940,
upsampleBandwidth: 0.945,
table: &kaiser10,
},
// Q7
quality{
baseLength: 128,
oversample: 16,
downsampleBandwidth: 0.950,
upsampleBandwidth: 0.950,
table: &kaiser10,
},
// Q8
quality{
baseLength: 160,
oversample: 16,
downsampleBandwidth: 0.960,
upsampleBandwidth: 0.960,
table: &kaiser10,
},
// Q9
quality{
baseLength: 192,
oversample: 32,
downsampleBandwidth: 0.968,
upsampleBandwidth: 0.968,
table: &kaiser12,
},
// Q10
quality{
baseLength: 256,
oversample: 32,
downsampleBandwidth: 0.975,
upsampleBandwidth: 0.975,
table: &kaiser12,
},
}
)

View file

@ -0,0 +1,544 @@
// Copyright (C) 2007-2008 Jean-Marc Valin
// Copyright (C) 2008 Thorvald Natvig
// Copyright (C) 2013 Oov
//
// Arbitrary resampling code
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright notice,
// this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. The name of the author may not be used to endorse or promote products
// derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
// OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
// Package resampler implements audio resampler.
//
// This is a port of the Opus-tools( http://git.xiph.org/?p=opus-tools.git ) audio resampler to the pure Go.
package resampler
import (
"math"
)
const bufferSize = 160
type channelState struct {
lastSample int
sampFracNum int
magicSamples int
mem []float64
}
type Resampler struct {
numRate int
denRate int
quality *quality
filtLen int
intAdvance int
fracAdvance int
cutoff float64
oversample int
initialised bool
started bool
skipZeros bool
channels []channelState
sincTable []float64
resampler func(channelIndex int, in []float64, out []float64) int
}
func Resample64(in []float64, inSampleRate int, out []float64, outSampleRate int, quality int) (read int, written int) {
return NewWithSkipZeros(1, inSampleRate, outSampleRate, quality).ProcessFloat64(0, in, out)
}
func Resample32(in []float32, inSampleRate int, out []float32, outSampleRate int, quality int) (read int, written int) {
return NewWithSkipZeros(1, inSampleRate, outSampleRate, quality).ProcessFloat32(0, in, out)
}
func New(channels int, inSampleRate, outSampleRate int, quality int) *Resampler {
if channels < 1 {
panic("you must have at least one channel")
}
r := &Resampler{
cutoff: 1.0,
channels: make([]channelState, channels),
}
r.setQuality(quality)
r.setSampleRate(inSampleRate, outSampleRate)
r.updateFilter()
r.initialised = true
return r
}
func NewWithSkipZeros(channels int, inSampleRate, outSampleRate int, quality int) *Resampler {
r := New(channels, inSampleRate, outSampleRate, quality)
r.skipZeros = true
return r
}
// cannot change quality on running in now implementation.
func (r *Resampler) setQuality(q int) {
if q < 0 || q > 10 {
panic("invalid quality value")
}
if r.quality == &qualityMap[q] {
return
}
r.quality = &qualityMap[q]
if r.initialised {
r.updateFilter()
}
}
func imin(a, b int) int {
if a < b {
return a
} else {
return b
}
}
// cannot change sample on running in now implementation.
func (r *Resampler) setSampleRate(in int, out int) {
ratioNum := in
ratioDen := out
// FIXME: This is terribly inefficient, but who cares (at least for now)?
for fact := 2; fact <= imin(ratioNum, ratioDen); fact++ {
for (ratioNum%fact == 0) && (ratioDen%fact == 0) {
ratioNum /= fact
ratioDen /= fact
}
}
if r.numRate == ratioNum && r.denRate == ratioDen {
return
}
if r.denRate > 0 {
for i := range r.channels {
ch := &r.channels[i]
ch.sampFracNum = ch.sampFracNum * ratioDen / r.denRate
// Safety net
if ch.sampFracNum >= ratioDen {
ch.sampFracNum = ratioDen - 1
}
}
}
r.numRate = ratioNum
r.denRate = ratioDen
if r.initialised {
r.updateFilter()
}
}
func (r *Resampler) ProcessFloat64(channelIndex int, in []float64, out []float64) (read int, written int) {
if r.skipZeros {
for i := range r.channels {
r.channels[i].lastSample = r.InputLatency()
}
r.skipZeros = false
}
ch := &r.channels[channelIndex]
x := ch.mem
filtOffs := r.filtLen - 1
iLen, oLen, xLen := len(in), len(out), len(x)-filtOffs
read, written = iLen, oLen
if ch.magicSamples != 0 {
oLen -= r.magic(channelIndex, out)
}
if ch.magicSamples == 0 {
for iLen != 0 && oLen != 0 {
ichunk, ochunk := imin(xLen, iLen), 0
if in != nil {
copy(x[filtOffs:], in[:ichunk])
} else {
for j := filtOffs; j < ichunk+filtOffs; j++ {
x[j] = 0
}
}
ichunk, ochunk = r.processNative(channelIndex, ichunk, out)
iLen -= ichunk
oLen -= ochunk
out = out[ochunk:]
if in != nil {
in = in[ichunk:]
}
}
}
read -= iLen
written -= oLen
return
}
func (r *Resampler) ProcessFloat32(channelIndex int, in []float32, out []float32) (read int, written int) {
const stackSize = 1024
var stack [stackSize]float64
if r.skipZeros {
for i := range r.channels {
r.channels[i].lastSample = r.InputLatency()
}
r.skipZeros = false
}
ch := &r.channels[channelIndex]
x := ch.mem
filtOffs := r.filtLen - 1
iLen, oLen := len(in), len(out)
xLen, yLen := len(x)-filtOffs, stackSize
read, written = iLen, oLen
if ch.magicSamples != 0 {
m := r.magic(channelIndex, stack[:imin(yLen, oLen)])
oLen -= m
for i, s := range stack[:m] {
out[i] = float32(s)
}
out = out[m:]
}
if ch.magicSamples == 0 {
for iLen != 0 && oLen != 0 {
ichunk, ochunk := imin(xLen, iLen), imin(yLen, oLen)
if in != nil {
for i, s := range in[:ichunk] {
x[filtOffs+i] = float64(s)
}
} else {
for j := filtOffs; j < ichunk+filtOffs; j++ {
x[j] = 0
}
}
ichunk, ochunk = r.processNative(channelIndex, ichunk, stack[:ochunk])
iLen -= ichunk
oLen -= ochunk
for i, s := range stack[:ochunk] {
out[i] = float32(s)
}
out = out[ochunk:]
if in != nil {
in = in[ichunk:]
}
}
}
read -= iLen
written -= oLen
return
}
func (r *Resampler) processNative(channelIndex int, inLen int, out []float64) (inLenRet int, outLenRet int) {
ch := &r.channels[channelIndex]
r.started = true
outLenRet = r.resampler(channelIndex, ch.mem[:inLen], out)
if ch.lastSample < inLen {
inLenRet = ch.lastSample
} else {
inLenRet = inLen
}
ch.lastSample -= inLenRet
copy(ch.mem, ch.mem[inLenRet:inLenRet+r.filtLen-1])
return
}
func (r *Resampler) magic(channelIndex int, out []float64) (outWritten int) {
ch := &r.channels[channelIndex]
n := r.filtLen - 1
inLen, outLen := r.processNative(channelIndex, ch.magicSamples, out)
ch.magicSamples -= inLen
// If we couldn't process all "magic" input samples, save the rest for next time
if ch.magicSamples != 0 {
copy(ch.mem[n:n+ch.magicSamples], ch.mem[n+inLen:])
}
return outLen
}
func computeFunc(x float64, windowFunc *kaiserTable) float64 {
y := x * float64(windowFunc.oversample)
ind := int(math.Floor(y))
frac := y - float64(ind)
fracx2 := frac * frac
fracx3 := fracx2 * frac
fracx2mul0_5 := 0.5 * fracx2
fracx3mul0_16 := 0.1666666667 * fracx3
// Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc
i3 := -0.1666666667*frac + fracx3mul0_16
i2 := frac + fracx2mul0_5 - 0.5*fracx3
// interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;
i0 := -0.3333333333*frac + fracx2mul0_5 - fracx3mul0_16
// Just to make sure we don't have rounding problems
i1 := 1.0 - i3 - i2 - i0
return i0*windowFunc.table[ind] + i1*windowFunc.table[ind+1] + i2*windowFunc.table[ind+2] + i3*windowFunc.table[ind+3]
}
// The slow way of computing a sinc for the table. Should improve that some day
func sinc(cutoff float64, x float64, n float64, windowFunc *kaiserTable) float64 {
xabs := math.Abs(x)
if xabs < 1e-6 {
return cutoff
} else if xabs > 0.5*n {
return 0
}
// FIXME: Can it really be any slower than this?
xx := x * cutoff * math.Pi
return cutoff * math.Sin(xx) / xx * computeFunc(2.0*xabs/n, windowFunc)
}
func (r *Resampler) resamplerBasicDirect(channelIndex int, in []float64, out []float64) int {
ch := &r.channels[channelIndex]
n := r.filtLen
outSample := 0
lastSample := ch.lastSample
sampFracNum := ch.sampFracNum
sincTable := r.sincTable
intAdvance := r.intAdvance
fracAdvance := r.fracAdvance
denRate := r.denRate
for lastSample < len(in) && outSample < len(out) {
sinct := sincTable[sampFracNum*n : sampFracNum*n+n]
var sum float64
for j, s := range in[lastSample : lastSample+n] {
sum += sinct[j] * s
}
out[outSample] = sum
outSample++
lastSample += intAdvance
sampFracNum += fracAdvance
if sampFracNum >= denRate {
sampFracNum -= denRate
lastSample++
}
}
ch.lastSample = lastSample
ch.sampFracNum = sampFracNum
return outSample
}
func cubicCoef(frac float64) (float64, float64, float64, float64) {
fracx2 := frac * frac
fracx3 := fracx2 * frac
fracx2mul0_5 := 0.5 * fracx2
fracx3mul0_16 := 0.1666666667 * fracx3
// Compute interpolation coefficients. I'm not sure whether this corresponds to cubic interpolation but I know it's MMSE-optimal on a sinc
i0 := -0.1666666667*frac + fracx3mul0_16
i1 := frac + fracx2mul0_5 - 0.5*fracx3
// interp[2] = 1.f - 0.5f*frac - frac*frac + 0.5f*frac*frac*frac;
i3 := -0.3333333333*frac + fracx2mul0_5 - fracx3mul0_16
// Just to make sure we don't have rounding problems
i2 := 1.0 - i0 - i1 - i3
return i0, i1, i2, i3
}
func (r *Resampler) resamplerBasicInterpolate(channelIndex int, in []float64, out []float64) int {
ch := &r.channels[channelIndex]
n := r.filtLen
outSample := 0
lastSample := ch.lastSample
sampFracNum := ch.sampFracNum
intAdvance := r.intAdvance
fracAdvance := r.fracAdvance
denRate := r.denRate
for lastSample < len(in) && outSample < len(out) {
offset := sampFracNum * r.oversample / r.denRate
frac := float64((sampFracNum*r.oversample)%r.denRate) / float64(r.denRate)
var accum0, accum1, accum2, accum3 float64
for j, s := range in[lastSample : lastSample+n] {
t := 4 + (j+1)*r.oversample - offset
accum0 += s * r.sincTable[t-2]
accum1 += s * r.sincTable[t-1]
accum2 += s * r.sincTable[t]
accum3 += s * r.sincTable[t+1]
}
i0, i1, i2, i3 := cubicCoef(frac)
out[outSample] = i0*accum0 + i1*accum1 + i2*accum2 + i3*accum3
outSample++
lastSample += intAdvance
sampFracNum += fracAdvance
if sampFracNum >= denRate {
sampFracNum -= denRate
lastSample++
}
}
ch.lastSample = lastSample
ch.sampFracNum = sampFracNum
return outSample
}
func (r *Resampler) updateFilter() {
oldLength := r.filtLen
r.oversample = r.quality.oversample
r.filtLen = r.quality.baseLength
if r.numRate > r.denRate {
// down-sampling
r.cutoff = r.quality.downsampleBandwidth * float64(r.denRate) / float64(r.numRate)
// FIXME: divide the numerator and denominator by a certain amount if they're too large
r.filtLen = r.filtLen * r.numRate / r.denRate
// Round up to make sure we have a multiple of 8
r.filtLen = ((r.filtLen - 1) & (^int(0x7))) + 8
if r.denRate<<1 < r.numRate {
r.oversample >>= 1
}
if r.denRate<<2 < r.numRate {
r.oversample >>= 1
}
if r.denRate<<3 < r.numRate {
r.oversample >>= 1
}
if r.denRate<<4 < r.numRate {
r.oversample >>= 1
}
if r.oversample < 1 {
r.oversample = 1
}
} else {
// up-sampling
r.cutoff = r.quality.upsampleBandwidth
}
// Choose the resampling type that requires the least amount of memory
if r.denRate <= 16*(r.oversample+8) {
if r.sincTable == nil || len(r.sincTable) < r.filtLen*r.denRate {
r.sincTable = make([]float64, r.filtLen*r.denRate)
}
for i := 0; i < r.denRate; i++ {
for j := 0; j < r.filtLen; j++ {
r.sincTable[i*r.filtLen+j] = sinc(
r.cutoff,
float64(j-(r.filtLen>>1)+1)-float64(i)/float64(r.denRate),
float64(r.filtLen),
r.quality.table,
)
}
}
r.resampler = r.resamplerBasicDirect
} else {
if r.sincTable == nil || len(r.sincTable) < r.filtLen*r.oversample+8 {
r.sincTable = make([]float64, r.filtLen*r.oversample+8)
}
for i := -4; i < r.oversample*r.filtLen+4; i++ {
r.sincTable[i+4] = sinc(
r.cutoff,
float64(i)/float64(r.oversample)-float64(r.filtLen>>1),
float64(r.filtLen),
r.quality.table,
)
}
r.resampler = r.resamplerBasicInterpolate
}
r.intAdvance = r.numRate / r.denRate
r.fracAdvance = r.numRate % r.denRate
// Here's the place where we update the filter memory to take into account
// the change in filter length. It's probably the messiest part of the code
// due to handling of lots of corner cases.
switch {
case r.channels[0].mem == nil || !r.started:
size := r.filtLen - 1 + bufferSize
for i := range r.channels {
r.channels[i].mem = make([]float64, size)
}
case r.filtLen > oldLength:
panic("not implemented")
// Increase the filter length
// oldAllocSize := len(r.mem) / len(r.channels)
// if r.filtLen-1+r.bufferSize > oldAllocSize {
// m := make([]float64, (r.filtLen-1+r.bufferSize)*len(r.channels))
// copy(m, r.mem)
// r.mem = m
// }
for i := len(r.channels) - 1; i >= 0; i-- {
// spx_int32_t j;
// spx_uint32_t olen = old_length;
// /*if (st->magic_samples[i])*/
// {
// /* Try and remove the magic samples as if nothing had happened */
//
// /* FIXME: This is wrong but for now we need it to avoid going over the array bounds */
// olen = old_length + 2*st->magic_samples[i];
// for (j=old_length-2+st->magic_samples[i];j>=0;j--)
// st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]] = st->mem[i*old_alloc_size+j];
// for (j=0;j<st->magic_samples[i];j++)
// st->mem[i*st->mem_alloc_size+j] = 0;
// st->magic_samples[i] = 0;
// }
// if (st->filt_len > olen)
// {
// /* If the new filter length is still bigger than the "augmented" length */
// /* Copy data going backward */
// for (j=0;j<olen-1;j++)
// st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = st->mem[i*st->mem_alloc_size+(olen-2-j)];
// /* Then put zeros for lack of anything better */
// for (;j<st->filt_len-1;j++)
// st->mem[i*st->mem_alloc_size+(st->filt_len-2-j)] = 0;
// /* Adjust last_sample */
// st->last_sample[i] += (st->filt_len - olen)/2;
// } else {
// /* Put back some of the magic! */
// st->magic_samples[i] = (olen - st->filt_len)/2;
// for (j=0;j<st->filt_len-1+st->magic_samples[i];j++)
// st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]];
// }
}
case r.filtLen < oldLength:
panic("not implemented")
// spx_uint32_t i;
// /* Reduce filter length, this a bit tricky. We need to store some of the memory as "magic"
// samples so they can be used directly as input the next time(s) */
// for (i=0;i<st->nb_channels;i++)
// {
// spx_uint32_t j;
// spx_uint32_t old_magic = st->magic_samples[i];
// st->magic_samples[i] = (old_length - st->filt_len)/2;
// /* We must copy some of the memory that's no longer used */
// /* Copy data going backward */
// for (j=0;j<st->filt_len-1+st->magic_samples[i]+old_magic;j++)
// st->mem[i*st->mem_alloc_size+j] = st->mem[i*st->mem_alloc_size+j+st->magic_samples[i]];
// st->magic_samples[i] += old_magic;
// }
}
}
func (r *Resampler) InputLatency() int {
return r.filtLen >> 1
}
func (r *Resampler) OutputLatency() int {
return ((r.filtLen>>1)*r.denRate + (r.numRate >> 1)) / r.numRate
}

View file

@ -0,0 +1,182 @@
package resampler
import (
"fmt"
"math"
"testing"
)
var (
wave = []float64{
//-1 to 1 (16 samples)
-1, -0.75, -0.5, -0.25, 0, 0.25, 0.5, 0.75, 1, 1, 1, 1, 1, 1, 1, 1,
//1 to -1 (16 samples)
1, 0.75, 0.5, 0.25, 0, -0.25, -0.5, -0.75, -1, -1, -1, -1, -1, -1, -1, -1,
}
)
func resample64(t *testing.T, inSamplerate, outSamplerate, q int) []float64 {
r := New(1, inSamplerate, outSamplerate, q)
t.Log("samplerate:", "in:", inSamplerate, "out:", outSamplerate)
t.Log("latency:", "in:", r.InputLatency(), "out:", r.OutputLatency())
in := make([]float64, inSamplerate/100)
out := make([]float64, outSamplerate/100)
copy(in, wave)
read, written := r.ProcessFloat64(0, in, out)
t.Log("read:", read)
t.Log("written:", written)
s := ""
out = out[r.OutputLatency():][:(32*outSamplerate)/inSamplerate]
for _, v := range out {
s += fmt.Sprintf("%0.3f ", v)
}
t.Log("wave:", s)
return out
}
func resample32(t *testing.T, inSamplerate, outSamplerate, q int) []float32 {
r := New(1, inSamplerate, outSamplerate, q)
t.Log("samplerate:", "in:", inSamplerate, "out:", outSamplerate)
t.Log("latency:", "in:", r.InputLatency(), "out:", r.OutputLatency())
in := make([]float32, inSamplerate/100)
out := make([]float32, outSamplerate/100)
for i, s := range wave {
in[i] = float32(s)
}
read, written := r.ProcessFloat32(0, in, out)
t.Log("read:", read)
t.Log("written:", written)
s := ""
out = out[r.OutputLatency():][:(32*outSamplerate)/inSamplerate]
for _, v := range out {
s += fmt.Sprintf("%0.3f ", v)
}
t.Log("wave:", s)
return out
}
func TestSame64(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample64(t, 48000, 48000, q)
if math.Abs(out[8]-1.0) > 0.1 {
t.Fail()
}
if math.Abs(out[24] - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestSame32(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample32(t, 48000, 48000, q)
if math.Abs(float64(out[8])-1.0) > 0.1 {
t.Fail()
}
if math.Abs(float64(out[24]) - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestDirectDownSampling64(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample64(t, 48000, 24000, q)
if math.Abs(out[4]-1.0) > 0.1 {
t.Fail()
}
if math.Abs(out[12] - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestDirectDownSampling32(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample32(t, 48000, 24000, q)
if math.Abs(float64(out[4])-1.0) > 0.1 {
t.Fail()
}
if math.Abs(float64(out[12]) - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestDirectUpSampling64(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample64(t, 24000, 48000, q)
if math.Abs(out[16]-1.0) > 0.1 {
t.Fail()
}
if math.Abs(out[48] - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestDirectUpSampling32(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample32(t, 24000, 48000, q)
if math.Abs(float64(out[16])-1.0) > 0.1 {
t.Fail()
}
if math.Abs(float64(out[48]) - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestInterpolateDownSampling64(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample64(t, 48000, 23999, q)
if math.Abs(out[4]-1.0) > 0.1 {
t.Fail()
}
if math.Abs(out[12] - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestInterpolateDownSampling32(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample32(t, 48000, 23999, q)
if math.Abs(float64(out[4])-1.0) > 0.1 {
t.Fail()
}
if math.Abs(float64(out[12]) - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestInterpolateUpSampling64(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample64(t, 23999, 48000, q)
if math.Abs(out[16]-1.0) > 0.1 {
t.Fail()
}
if math.Abs(out[48] - -1.0) > 0.1 {
t.Fail()
}
}
}
func TestInterpolateUpSampling32(t *testing.T) {
for q := 0; q < 11; q++ {
out := resample32(t, 23999, 48000, q)
if math.Abs(float64(out[16])-1.0) > 0.1 {
t.Fail()
}
if math.Abs(float64(out[48]) - -1.0) > 0.1 {
t.Fail()
}
}
}

View file

@ -4,7 +4,7 @@ package filter
import (
"git.gammaspectra.live/S.O.N.G/Kirika/audio"
"github.com/oov/audio/resampler"
"git.gammaspectra.live/S.O.N.G/Kirika/audio/filter/internal/resampler"
"log"
)

9
go.mod
View file

@ -3,7 +3,7 @@ module git.gammaspectra.live/S.O.N.G/Kirika
go 1.19
require (
git.gammaspectra.live/S.O.N.G/flacgo v0.0.0-20220726151057-28f458bc5391
git.gammaspectra.live/S.O.N.G/flacgo v0.0.0-20221130070259-2ac188033aab
git.gammaspectra.live/S.O.N.G/go-alac v0.0.0-20220421115623-d0b3bfe57e0f
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220720163421-db0c1911921d
git.gammaspectra.live/S.O.N.G/go-fdkaac v0.0.0-20220910135048-823922bd661a
@ -19,17 +19,16 @@ require (
github.com/icza/bitio v1.1.0
github.com/jfreymuth/oggvorbis v1.0.4
github.com/minio/sha256-simd v1.0.0
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38
github.com/sssgun/mp3 v0.0.0-20170810093403-85f2ec632081
github.com/viert/go-lame v0.0.0-20201108052322-bb552596b11d
golang.org/x/exp v0.0.0-20221106115401-f9659909a136
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9
)
require (
github.com/jfreymuth/vorbis v1.0.2 // indirect
github.com/klauspost/cpuid v1.3.1 // indirect
github.com/klauspost/cpuid/v2 v2.1.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.1 // indirect
github.com/mewkiz/pkg v0.0.0-20220820102221-bbbca16e2a6c // indirect
github.com/youpy/go-wav v0.3.2 // indirect
golang.org/x/sys v0.1.0 // indirect
golang.org/x/sys v0.2.0 // indirect
)

18
go.sum
View file

@ -1,5 +1,5 @@
git.gammaspectra.live/S.O.N.G/flacgo v0.0.0-20220726151057-28f458bc5391 h1:us3yKKsnMe0FZVHRSFZCw113ddiNrZgKf5M5PNr3SQ4=
git.gammaspectra.live/S.O.N.G/flacgo v0.0.0-20220726151057-28f458bc5391/go.mod h1:ZVHB/7Vrs9xxK1j98+SJ5TRYBc7Q9dIUaNJHEmysZcI=
git.gammaspectra.live/S.O.N.G/flacgo v0.0.0-20221130070259-2ac188033aab h1:1jKDhlDQK6hzR6KulALSYZTPgiCthL8UqufYXCRSuRo=
git.gammaspectra.live/S.O.N.G/flacgo v0.0.0-20221130070259-2ac188033aab/go.mod h1:sYjxqsHurCJUzQQiSUNZbjW8st2fiD3kLxO08gBqmW8=
git.gammaspectra.live/S.O.N.G/go-alac v0.0.0-20220421115623-d0b3bfe57e0f h1:CxN7zlk5FdAieyRKQSbwBGBsvQ2cDF8JVCODZpzcRkA=
git.gammaspectra.live/S.O.N.G/go-alac v0.0.0-20220421115623-d0b3bfe57e0f/go.mod h1:f1+h7KOnuM9zcEQp7ri4UaVvgX4m1NFFIXgReIyjGMA=
git.gammaspectra.live/S.O.N.G/go-ebur128 v0.0.0-20220720163421-db0c1911921d h1:3M0GZgm2H1tBkDxCwvQdRvnYhF0/velekO9uhVZsbP0=
@ -43,14 +43,12 @@ github.com/jszwec/csvutil v1.5.1/go.mod h1:Rpu7Uu9giO9subDyMCIQfHVDuLrcaC36UA4Yc
github.com/klauspost/cpuid v1.3.1 h1:5JNjFYYQrZeKRJ0734q51WCEEn2huer72Dc7K+R/b6s=
github.com/klauspost/cpuid v1.3.1/go.mod h1:bYW4mA6ZgKPob1/Dlai2LviZJO7KGI3uoWLd42rAQw4=
github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.1.2 h1:XhdX4fqAJUA0yj+kUwMavO0hHrSPAecYdYf1ZmxHvak=
github.com/klauspost/cpuid/v2 v2.1.2/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/klauspost/cpuid/v2 v2.2.1 h1:U33DW0aiEj633gHYw3LoDNfkDiYnE5Q8M/TKJn2f2jI=
github.com/klauspost/cpuid/v2 v2.2.1/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/mewkiz/pkg v0.0.0-20220820102221-bbbca16e2a6c h1:6AzCfQNCql3Of8ee1JY6dufssFnBWJYuCVrGcES84AA=
github.com/mewkiz/pkg v0.0.0-20220820102221-bbbca16e2a6c/go.mod h1:J/rDzvIiwiVpv72OEP8aJFxLXjGpUdviIIeqJPLIctA=
github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g=
github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM=
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38 h1:4Upfs5rLQdx7KwBct3bmPYAhWsDDJdx660gYb7Lv9TQ=
github.com/oov/audio v0.0.0-20171004131523-88a2be6dbe38/go.mod h1:Xj06yMta9R1RSKiHmxL0Bo2TB8wiKVnMgA0KVopHHkk=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -66,14 +64,14 @@ github.com/youpy/go-wav v0.3.2 h1:NLM8L/7yZ0Bntadw/0h95OyUsen+DQIVf9gay+SUsMU=
github.com/youpy/go-wav v0.3.2/go.mod h1:0FCieAXAeSdcxFfwLpRuEo0PFmAoc+8NU34h7TUvk50=
github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b h1:QqixIpc5WFIqTLxB3Hq8qs0qImAgBdq0p6rq2Qdl634=
github.com/zaf/g711 v0.0.0-20190814101024-76a4a538f52b/go.mod h1:T2h1zV50R/q0CVYnsQOQ6L7P4a2ZxH47ixWcMXFGyx8=
golang.org/x/exp v0.0.0-20221106115401-f9659909a136 h1:Fq7F/w7MAa1KJ5bt2aJ62ihqp9HDcRuyILskkpIAurw=
golang.org/x/exp v0.0.0-20221106115401-f9659909a136/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9 h1:yZNXmy+j/JpX19vZkVktWqAo7Gny4PBWYYK3zskGpx4=
golang.org/x/exp v0.0.0-20221126150942-6ab00d035af9/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/image v0.0.0-20190220214146-31aff87c08e9/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0 h1:ljd4t30dBnAvMZaQCevtY0xLLD0A+bRZXbgLMLU1F/A=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=