Skip to content

Commit

Permalink
Enhance performance of resampling #6
Browse files Browse the repository at this point in the history
  • Loading branch information
kjs001127 authored May 15, 2023
2 parents 4626d4d + 3cd58ee commit 2c25201
Show file tree
Hide file tree
Showing 3 changed files with 70 additions and 100 deletions.
121 changes: 51 additions & 70 deletions pkg/resampler/resample.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@ import (
)

type Resampler struct {
filter *filter
precision int
from int
to int
from int
to int

filter *filter
window *window

indexStep int64
scale float64
timeStampIdx int64
window *window
sampleRatio float64
precision int
}

func New(highQuality bool, from int, to int) *Resampler {
Expand All @@ -22,82 +27,78 @@ func New(highQuality bool, from int, to int) *Resampler {
}

window := newWindow()
for i := 0; i < paddingSize; i++ {
window.push(0)
}
window.push(make([]float64, paddingSize))

sampleRatio := float64(to) / float64(from)
scale := math.Min(sampleRatio, 1.0)
indexStep := int64(scale * float64(int(f.precision)))

return &Resampler{
filter: f,
precision: int(f.precision),
from: from,
to: to,
window: window,
filter: f,
precision: int(f.precision),
from: from,
to: to,
scale: scale,
sampleRatio: sampleRatio,
indexStep: indexStep,
window: window,
}
}

func (r *Resampler) Resample(in []float64) ([]float64, error) {
if err := r.supply(in); err != nil {
return nil, err
}
return r.read(), nil
func (r *Resampler) Resample(in []float64) []float64 {
r.supply(in)
return r.read()
}

func (r *Resampler) supply(buf []float64) error {
for _, b := range buf {
r.window.push(b)
}
return nil
func (r *Resampler) supply(buf []float64) {
r.window.push(buf)
}

func (r *Resampler) read() []float64 {
var ret []float64

scale := math.Min(float64(r.to)/float64(r.from), 1.0)
indexStep := int64(scale * float64(r.precision))
size := math.Ceil(r.sampleRatio * (float64(r.window.right-paddingSize) - r.timestamp()))
ret := make([]float64, 0, int64(size))
nWin := int64(len(r.filter.arr))

for int64(r.timestamp()) < r.window.right-paddingSize {
for {

var sample float64
timestamp := r.timestamp()
tsFlooredTmp, timestampFrac := math.Modf(timestamp)
timestampFloored := int64(tsFlooredTmp)

leftPadding := max(0, timestampFloored-r.window.left)
rightPadding := max(0, r.window.right-timestampFloored-1)
if timestampFloored >= r.window.right-paddingSize {
break
}

leftPadding := timestampFloored - r.window.left
rightPadding := r.window.right - timestampFloored - 1

frac := scale * timestampFrac
frac := r.scale * timestampFrac
indexFrac := frac * float64(r.precision)
offsetTmp, eta := math.Modf(indexFrac)
offset := int64(offsetTmp)
iMax := min(leftPadding+1, (nWin-offset)/indexStep)
iMax := min(leftPadding+1, (nWin-offset)/r.indexStep)

idx := offset
deltaFactor := r.scale * eta
for i := int64(0); i < iMax; i++ {
idx := offset + i*indexStep
weight := r.filterFactor(idx) + r.deltaFactor(idx)*eta
s, err := r.window.get(timestampFloored - i)
// TODO: handle error, panic 은 임시, 코드 문제가 아니라면 일어나지 않는 에러
if err != nil {
panic(err)
}
sample += weight * s
weight := r.filter.arr[idx]*r.scale + r.filter.delta[idx]*deltaFactor
sample += weight * r.window.get(timestampFloored-i)
idx += r.indexStep
}

frac = scale - frac
frac = r.scale - frac
indexFrac = frac * float64(r.precision)
offsetTmp, eta = math.Modf(indexFrac)
offset = int64(offsetTmp)
kMax := min(rightPadding, (nWin-offset)/indexStep)
kMax := min(rightPadding, (nWin-offset)/r.indexStep)

idx = offset
deltaFactor = r.scale * eta
for k := int64(0); k < kMax; k++ {
idx := offset + k*indexStep
weight := r.filterFactor(idx) + r.deltaFactor(idx)*eta
s, err := r.window.get(timestampFloored + k + 1)
// TODO: handle error, panic 은 임시, 코드 문제가 아니라면 일어나지 않는 에러
if err != nil {
panic(err)
}
sample += weight * s
weight := r.filter.arr[idx]*r.scale + r.filter.delta[idx]*deltaFactor
sample += weight * r.window.get(timestampFloored+k+1)
idx += r.indexStep
}

ret = append(ret, sample)
Expand All @@ -109,23 +110,3 @@ func (r *Resampler) read() []float64 {
func (r *Resampler) timestamp() float64 {
return float64(r.timeStampIdx) * float64(r.from) / float64(r.to)
}

func (r *Resampler) sampleRatio() float64 {
return float64(r.to) / float64(r.from)
}

func (r *Resampler) filterFactor(idx int64) float64 {
ret := r.filter.arr[idx]
if r.sampleRatio() < 1 {
ret *= r.sampleRatio()
}
return ret
}

func (r *Resampler) deltaFactor(idx int64) float64 {
ret := r.filter.delta[idx]
if r.sampleRatio() < 1 {
ret *= r.sampleRatio()
}
return ret
}
20 changes: 5 additions & 15 deletions pkg/resampler/resample_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@ func TestDownSampleFast(t *testing.T) {
s := New(false, 48000, 8000)

readSize := 960
println("size ", len(pcm48000)/readSize)
var samples []float64
for i := 0; i < len(pcm48000)-readSize; i += readSize {
reSampled, err := s.Resample(pcm48000[i : i+readSize])
assert.NilError(t, err)
reSampled := s.Resample(pcm48000[i : i+readSize])
assert.Equal(t, readSize/6, len(reSampled))
samples = append(samples, reSampled...)
}
Expand All @@ -37,8 +37,7 @@ func TestUpSampleFast(t *testing.T) {
readSize := 160
var samples []float64
for i := 0; i < len(pcm48000)-readSize; i += readSize {
reSampled, err := s.Resample(pcm48000[i : i+readSize])
assert.NilError(t, err)
reSampled := s.Resample(pcm48000[i : i+readSize])
assert.Equal(t, readSize*6, len(reSampled))
samples = append(samples, reSampled...)
}
Expand All @@ -56,8 +55,7 @@ func TestDownSampleRandomSize(t *testing.T) {

readSize := 960
for i := readSize; i < len(pcm48000); i += readSize {
reSampled, err := s.Resample(pcm48000[i-readSize : i])
assert.NilError(t, err)
reSampled := s.Resample(pcm48000[i-readSize : i])
assert.Equal(t, readSize/6, len(reSampled))
samples = append(samples, reSampled...)
readSize = rand.Intn(100) * 6
Expand All @@ -75,8 +73,7 @@ func TestUpSampleRandomSize(t *testing.T) {
readSize := 160
var samples []float64
for i := readSize; i < len(pcm48000); i += readSize {
reSampled, err := s.Resample(pcm48000[i-readSize : i])
assert.NilError(t, err)
reSampled := s.Resample(pcm48000[i-readSize : i])
assert.Equal(t, readSize*6, len(reSampled))
samples = append(samples, reSampled...)
readSize = rand.Intn(500)
Expand All @@ -85,13 +82,6 @@ func TestUpSampleRandomSize(t *testing.T) {
writeWav("./example/timeout_48000_fast.wav", ToBytes(samples), 48000)
}

func TestTooBigInput(t *testing.T) {
s := New(false, 8000, 48000)

_, err := s.Resample(make([]float64, bufSize*2))
assert.Error(t, err, "window capacity is not enough")
}

func readWav(path string) []byte {
f, err := os.Open(path)
if err != nil {
Expand Down
29 changes: 14 additions & 15 deletions pkg/resampler/window.go
Original file line number Diff line number Diff line change
@@ -1,34 +1,33 @@
package resampler

import (
"fmt"
)

const bufSize = 10000
const bufSize = int64(8192) // 2^13
const maxBufIdx = bufSize - 1
const paddingSize = 300

// input sample 누적을 위한 원형 큐
type window struct {
buf [bufSize]float64
left int64
right int64
buf [bufSize]float64
}

func newWindow() *window {
return &window{}
}

func (w *window) get(i int64) (float64, error) {
if w.left > i || i >= w.right {
return 0.0, fmt.Errorf("invalid index: %d", i)
}
return w.buf[i%bufSize], nil
func (w *window) get(i int64) float64 {
return w.buf[i&maxBufIdx]
}

func (w *window) push(s float64) {
w.buf[w.right%bufSize] = s
w.right++
w.left = max(w.right-bufSize, w.left)
func (w *window) push(buf []float64) {
for _, s := range buf {
w.buf[w.right&maxBufIdx] = s
w.right++
}

if newVal := w.right - bufSize; newVal > w.left {
w.left = newVal
}
}

func (w *window) isFull() bool {
Expand Down

0 comments on commit 2c25201

Please sign in to comment.