From 6fde234fe6d40caf5bff82d1dd4be4dcef7e5044 Mon Sep 17 00:00:00 2001 From: WeebDataHoarder <57538841+WeebDataHoarder@users.noreply.github.com> Date: Wed, 30 Nov 2022 08:09:16 +0100 Subject: [PATCH] Reduce dependencies, vendor resampler part of github.com/oov/audio --- README.md | 6 +- audio/filter/internal/resampler/LICENSE | 20 + audio/filter/internal/resampler/README.md | 3 + audio/filter/internal/resampler/quality.go | 189 ++++++ audio/filter/internal/resampler/resampler.go | 544 ++++++++++++++++++ .../internal/resampler/resampler_test.go | 182 ++++++ audio/filter/resample_filter_nocgo.go | 2 +- go.mod | 9 +- go.sum | 18 +- 9 files changed, 952 insertions(+), 21 deletions(-) create mode 100644 audio/filter/internal/resampler/LICENSE create mode 100644 audio/filter/internal/resampler/README.md create mode 100644 audio/filter/internal/resampler/quality.go create mode 100644 audio/filter/internal/resampler/resampler.go create mode 100644 audio/filter/internal/resampler/resampler_test.go diff --git a/README.md b/README.md index 12769e2..c535072 100644 --- a/README.md +++ b/README.md @@ -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.
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, [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.
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.
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.
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/encoding by [libvorbis](https://github.com/xiph/vorbis) via [go-vorbis](https://git.gammaspectra.live/S.O.N.G/go-vorbis).
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.
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).
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. | @@ -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) | | diff --git a/audio/filter/internal/resampler/LICENSE b/audio/filter/internal/resampler/LICENSE new file mode 100644 index 0000000..b47d78f --- /dev/null +++ b/audio/filter/internal/resampler/LICENSE @@ -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. \ No newline at end of file diff --git a/audio/filter/internal/resampler/README.md b/audio/filter/internal/resampler/README.md new file mode 100644 index 0000000..3ecacb4 --- /dev/null +++ b/audio/filter/internal/resampler/README.md @@ -0,0 +1,3 @@ +# resampler_nocgo + +Taken from [github.com/oov/audio](https://github.com/oov/audio/tree/8e73d0e7f9fdaa878160a5134a2c6993dbee286d/resampler) implementation \ No newline at end of file diff --git a/audio/filter/internal/resampler/quality.go b/audio/filter/internal/resampler/quality.go new file mode 100644 index 0000000..157c55e --- /dev/null +++ b/audio/filter/internal/resampler/quality.go @@ -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, + }, + } +) diff --git a/audio/filter/internal/resampler/resampler.go b/audio/filter/internal/resampler/resampler.go new file mode 100644 index 0000000..37c2b75 --- /dev/null +++ b/audio/filter/internal/resampler/resampler.go @@ -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;jmagic_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;jmem[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 (;jfilt_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;jfilt_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;inb_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;jfilt_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 +} diff --git a/audio/filter/internal/resampler/resampler_test.go b/audio/filter/internal/resampler/resampler_test.go new file mode 100644 index 0000000..39e5290 --- /dev/null +++ b/audio/filter/internal/resampler/resampler_test.go @@ -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() + } + } +} diff --git a/audio/filter/resample_filter_nocgo.go b/audio/filter/resample_filter_nocgo.go index 4f1cea1..6d199e5 100644 --- a/audio/filter/resample_filter_nocgo.go +++ b/audio/filter/resample_filter_nocgo.go @@ -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" ) diff --git a/go.mod b/go.mod index 9d2a128..dd44f7a 100644 --- a/go.mod +++ b/go.mod @@ -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 ) diff --git a/go.sum b/go.sum index 88878db..119f40a 100644 --- a/go.sum +++ b/go.sum @@ -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=