Skip to content

Commit e34f051

Browse files
author
zeozeozeo
committedNov 11, 2022
first commit
0 parents  commit e34f051

File tree

5 files changed

+320
-0
lines changed

5 files changed

+320
-0
lines changed
 

‎LICENSE

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
This is free and unencumbered software released into the public domain.
2+
3+
Anyone is free to copy, modify, publish, use, compile, sell, or
4+
distribute this software, either in source code form or as a compiled
5+
binary, for any purpose, commercial or non-commercial, and by any
6+
means.
7+
8+
In jurisdictions that recognize copyright laws, the author or authors
9+
of this software dedicate any and all copyright interest in the
10+
software to the public domain. We make this dedication for the benefit
11+
of the public at large and to the detriment of our heirs and
12+
successors. We intend this dedication to be an overt act of
13+
relinquishment in perpetuity of all present and future rights to this
14+
software under copyright law.
15+
16+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
OTHER DEALINGS IN THE SOFTWARE.
23+
24+
For more information, please refer to <http://unlicense.org/>

‎README.md

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Gomplerate
2+
3+
A fast and simple pure Go audio resampling (sample rate conversion) library with zero dependencies.
4+
5+
# Example
6+
7+
## Resample audio with 2 channels from 11kHz to 44.1kHz
8+
9+
```go
10+
package main
11+
12+
import (
13+
"fmt"
14+
15+
"github.com/zeozeozeo/gomplerate"
16+
)
17+
18+
func main() {
19+
// create a new resampler, the first number is the amount of channels, the second
20+
// number is the original audio sample rate, the second audio is the target sample rate
21+
r, err := gomplerate.NewResampler(2, 11000, 44100)
22+
if err != nil {
23+
panic(err)
24+
}
25+
26+
// you should have at least 16 samples (per channel) to actually resample anything
27+
// instead of this, you can load any kind of audio, remember to use ResampleFloat64
28+
// for float64 audio and ResampleInt16 for int16 audio (ResampleInt16 will convert
29+
// the audio to float64, resample it and convert back to int16)
30+
data := []float64{
31+
// each second value is repeated here, because that's the second audio channel
32+
0, 0, .5, .5, 1, 1, .9, .9, .8, .8, .7, .7, .6, .6, .5, .5,
33+
.4, .4, .3, .3, .2, .2, .25, .25, .3, .3, .35, .35, .4, .4,
34+
.45, .45, .5, .5, .55, .55, .6, .6, .65, .65, .7, .7, .75,
35+
.75, .8, .8, .85, .85, .9, .9, .95, .95, 1, 1, 1, 1, 1, 0, 0,
36+
}
37+
38+
resampled := r.ResampleFloat64(data)
39+
fmt.Println(resampled) // will print out a lot of resampled floats
40+
}
41+
```
42+
43+
that's it...

‎go.mod

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module github.com/zeozeozeo/gomplerate
2+
3+
go 1.19

‎go.sum

Whitespace-only changes.

‎resample.go

+250
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,250 @@
1+
// This is free and unencumbered software released into the public domain.
2+
//
3+
// Anyone is free to copy, modify, publish, use, compile, sell, or
4+
// distribute this software, either in source code form or as a compiled
5+
// binary, for any purpose, commercial or non-commercial, and by any
6+
// means.
7+
//
8+
// In jurisdictions that recognize copyright laws, the author or authors
9+
// of this software dedicate any and all copyright interest in the
10+
// software to the public domain. We make this dedication for the benefit
11+
// of the public at large and to the detriment of our heirs and
12+
// successors. We intend this dedication to be an overt act of
13+
// relinquishment in perpetuity of all present and future rights to this
14+
// software under copyright law.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18+
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19+
// IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20+
// OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21+
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22+
// OTHER DEALINGS IN THE SOFTWARE.
23+
//
24+
// For more information, please refer to <http://unlicense.org/>
25+
26+
package gomplerate
27+
28+
import (
29+
"fmt"
30+
)
31+
32+
type Resampler struct {
33+
FromRate int // The original audio sample rate.
34+
ToRate int // The resampled audio sample rate.
35+
Channels int // The amount of channels.
36+
}
37+
38+
func NewResampler(channels, inputRate, outputRate int) (*Resampler, error) {
39+
if channels < 1 {
40+
return nil, fmt.Errorf("at least 1 channel is required (have %d)", channels)
41+
}
42+
if inputRate < 1 {
43+
return nil, fmt.Errorf("input sample rate must be bigger than 0 (got %d)", inputRate)
44+
}
45+
if outputRate < 1 {
46+
return nil, fmt.Errorf("output sample rate must be bigger than 0 (got %d)", outputRate)
47+
}
48+
49+
resampler := &Resampler{
50+
FromRate: inputRate,
51+
ToRate: outputRate,
52+
Channels: channels,
53+
}
54+
55+
return resampler, nil
56+
}
57+
58+
// Resamples a float64 audio buffer. Returns the resampled buffer.
59+
func (resampler *Resampler) ResampleFloat64(data []float64) []float64 {
60+
if len(data) == 0 {
61+
return nil
62+
}
63+
if resampler.FromRate == resampler.ToRate {
64+
return data[:]
65+
}
66+
/*
67+
// The audio must have at least 4 samples
68+
if len(data)/resampler.Channels < 4 {
69+
return nil
70+
}
71+
*/
72+
73+
// Split channels
74+
channels := make([][]float64, resampler.Channels)
75+
for i := 0; i < len(data); i++ {
76+
channelIdx := i % resampler.Channels
77+
channels[channelIdx] = append(channels[channelIdx], data[i])
78+
}
79+
80+
resampled := make(
81+
[]float64,
82+
int((float64(len(data))/float64(resampler.FromRate))*float64(resampler.ToRate)),
83+
)
84+
85+
// Resample channels
86+
resampledData := make([][]float64, len(channels))
87+
for c := 0; c < len(channels); c++ {
88+
resampledData[c] = resampler.resampleChannelData(channels[c])
89+
}
90+
91+
for i := 0; i < len(resampled); i++ {
92+
dataIdx := i / resampler.Channels
93+
dataLen := len(resampledData[i%len(channels)])
94+
if dataLen == 0 {
95+
continue
96+
}
97+
if dataIdx > dataLen-1 {
98+
dataIdx = dataLen - 1
99+
}
100+
if dataIdx < 0 {
101+
dataIdx = 0
102+
}
103+
resampled[i] = resampledData[i%len(channels)][dataIdx]
104+
}
105+
106+
return resampled
107+
}
108+
109+
// Resamples an int16 audio buffer. Returns the resampled buffer.
110+
func (resampler *Resampler) ResampleInt16(data []int16) (resampledi16 []int16) {
111+
// Convert the data to float64
112+
f64data := make([]float64, len(data))
113+
for i := 0; i < len(data); i++ {
114+
f64data[i] = float64(data[i]) / float64(0x7FFF)
115+
}
116+
// Resample
117+
resampledf64 := resampler.ResampleFloat64(f64data)
118+
// Convert back to int16
119+
for i := 0; i < len(resampledf64); i++ {
120+
resampledi16[i] = int16(resampledf64[i] * float64(0x7FFF))
121+
}
122+
return
123+
}
124+
125+
func (resampler *Resampler) resampleChannelData(data []float64) []float64 {
126+
// Need at least 16 samples to resample a channel
127+
if len(data) <= 16 {
128+
return make([]float64, len(data))
129+
}
130+
131+
// The samples we can use to resample
132+
availSamples := len(data) - 16
133+
134+
// The resample step between new samples
135+
channelFrom := float64(resampler.FromRate) / float64(resampler.Channels)
136+
channelTo := float64(resampler.ToRate) / float64(resampler.Channels)
137+
step := channelFrom / channelTo
138+
139+
output := []float64{}
140+
141+
// Resample each position from x0
142+
for x := step; x < float64(availSamples); x += step {
143+
xi0 := float64(uint64(x))
144+
xi := []float64{xi0, xi0 + 1, xi0 + 2, xi0 + 3}
145+
yi0 := uint64(xi0)
146+
yi := []float64{
147+
float64(data[yi0]),
148+
float64(data[yi0+1]),
149+
float64(data[yi0+2]),
150+
float64(data[yi0+3]),
151+
}
152+
xo := []float64{x}
153+
yo := []float64{0.0}
154+
if err := spline(xi, yi, xo, yo); err != nil {
155+
return data[:]
156+
}
157+
158+
output = append(output, yo[0]/float64(0x7FFF))
159+
}
160+
return output
161+
}
162+
163+
func spline(xi, yi, xo, yo []float64) (err error) {
164+
if len(xi) != 4 {
165+
return fmt.Errorf("invalid xi")
166+
}
167+
if len(yi) != 4 {
168+
return fmt.Errorf("invalid yi")
169+
}
170+
if len(xo) == 0 {
171+
return fmt.Errorf("invalid xo")
172+
}
173+
if len(yo) != len(xo) {
174+
return fmt.Errorf("invalid yo")
175+
}
176+
177+
x0, x1, x2, x3 := xi[0], xi[1], xi[2], xi[3]
178+
y0, y1, y2, y3 := yi[0], yi[1], yi[2], yi[3]
179+
h0, h1, h2, _, u1, l2, _ := splineLU(xi)
180+
c1, c2 := splineC1(yi, h0, h1), splineC2(yi, h1, h2)
181+
m1, m2 := splineM1(c1, c2, u1, l2), splineM2(c1, c2, u1, l2) // m0=m3=0
182+
183+
for k, v := range xo {
184+
if v <= x1 {
185+
yo[k] = splineZ0(m1, h0, x0, x1, y0, y1, v)
186+
} else if v <= x2 {
187+
yo[k] = splineZ1(m1, m2, h1, x1, x2, y1, y2, v)
188+
} else {
189+
yo[k] = splineZ2(m2, h2, x2, x3, y2, y3, v)
190+
}
191+
}
192+
193+
return
194+
}
195+
196+
func splineZ0(m1, h0, x0, x1, y0, y1, x float64) float64 {
197+
v0 := 0.0
198+
v1 := (x - x0) * (x - x0) * (x - x0) * m1 / (6 * h0)
199+
v2 := -1.0 * y0 * (x - x1) / h0
200+
v3 := (y1 - h0*h0*m1/6) * (x - x0) / h0
201+
return v0 + v1 + v2 + v3
202+
}
203+
204+
func splineZ1(m1, m2, h1, x1, x2, y1, y2, x float64) float64 {
205+
v0 := -1.0 * (x - x2) * (x - x2) * (x - x2) * m1 / (6 * h1)
206+
v1 := (x - x1) * (x - x1) * (x - x1) * m2 / (6 * h1)
207+
v2 := -1.0 * (y1 - h1*h1*m1/6) * (x - x2) / h1
208+
v3 := (y2 - h1*h1*m2/6) * (x - x1) / h1
209+
return v0 + v1 + v2 + v3
210+
}
211+
212+
func splineZ2(m2, h2, x2, x3, y2, y3, x float64) float64 {
213+
v0 := -1.0 * (x - x3) * (x - x3) * (x - x3) * m2 / (6 * h2)
214+
v1 := 0.0
215+
v2 := -1.0 * (y2 - h2*h2*m2/6) * (x - x3) / h2
216+
v3 := y3 * (x - x2) / h2
217+
return v0 + v1 + v2 + v3
218+
}
219+
220+
func splineM1(c1, c2, u1, l2 float64) float64 {
221+
return (c1/u1 - c2/2) / (2/u1 - l2/2)
222+
}
223+
224+
func splineM2(c1, c2, u1, l2 float64) float64 {
225+
return (c1/2 - c2/l2) / (u1/2 - 2/l2)
226+
}
227+
228+
func splineC1(yi []float64, h0, h1 float64) float64 {
229+
y0, y1, y2, _ := yi[0], yi[1], yi[2], yi[3]
230+
return 6.0 / (h0 + h1) * ((y2-y1)/h1 - (y1-y0)/h0)
231+
}
232+
233+
func splineC2(yi []float64, h1, h2 float64) float64 {
234+
_, y1, y2, y3 := yi[0], yi[1], yi[2], yi[3]
235+
return 6.0 / (h1 + h2) * ((y3-y2)/h2 - (y2-y1)/h1)
236+
}
237+
238+
func splineLU(xi []float64) (h0, h1, h2, l1, u1, l2, u2 float64) {
239+
x0, x1, x2, x3 := xi[0], xi[1], xi[2], xi[3]
240+
241+
h0, h1, h2 = x1-x0, x2-x1, x3-x2
242+
243+
l1 = h0 / (h1 + h0)
244+
u1 = h1 / (h1 + h0)
245+
246+
l2 = h1 / (h2 + h1)
247+
u2 = h2 / (h2 + h1)
248+
249+
return
250+
}

0 commit comments

Comments
 (0)
Please sign in to comment.