85 lines
2.1 KiB
Go
85 lines
2.1 KiB
Go
// ABOUTME: Simple linear resampler for converting audio sample rates
|
|
// ABOUTME: Used to convert MP3 files to 48kHz for Opus encoding
|
|
package server
|
|
|
|
// Resampler performs linear interpolation to convert between sample rates
|
|
type Resampler struct {
|
|
inputRate int
|
|
outputRate int
|
|
channels int
|
|
ratio float64
|
|
position float64
|
|
lastSample []int32 // one sample per channel
|
|
}
|
|
|
|
func NewResampler(inputRate, outputRate, channels int) *Resampler {
|
|
return &Resampler{
|
|
inputRate: inputRate,
|
|
outputRate: outputRate,
|
|
channels: channels,
|
|
ratio: float64(inputRate) / float64(outputRate),
|
|
position: 0.0,
|
|
lastSample: make([]int32, channels),
|
|
}
|
|
}
|
|
|
|
// Resample converts interleaved input samples at inputRate to outputRate via linear interpolation.
|
|
func (r *Resampler) Resample(input []int32, output []int32) int {
|
|
if len(input) == 0 {
|
|
return 0
|
|
}
|
|
|
|
inputFrames := len(input) / r.channels
|
|
outputFrames := len(output) / r.channels
|
|
|
|
outIdx := 0
|
|
|
|
for outIdx < outputFrames {
|
|
// Calculate which input frame we need
|
|
inputPos := r.position
|
|
inputIdx := int(inputPos)
|
|
|
|
// If we've consumed all input, stop
|
|
if inputIdx >= inputFrames-1 {
|
|
break
|
|
}
|
|
|
|
frac := inputPos - float64(inputIdx)
|
|
|
|
for ch := 0; ch < r.channels; ch++ {
|
|
sample1 := input[inputIdx*r.channels+ch]
|
|
sample2 := input[(inputIdx+1)*r.channels+ch]
|
|
|
|
interpolated := float64(sample1)*(1.0-frac) + float64(sample2)*frac
|
|
output[outIdx*r.channels+ch] = int32(interpolated)
|
|
}
|
|
|
|
outIdx++
|
|
r.position += r.ratio
|
|
}
|
|
|
|
// Reset position for next chunk, keeping fractional part
|
|
r.position -= float64(int(r.position))
|
|
|
|
return outIdx * r.channels
|
|
}
|
|
|
|
func (r *Resampler) Reset() {
|
|
r.position = 0.0
|
|
for i := range r.lastSample {
|
|
r.lastSample[i] = 0
|
|
}
|
|
}
|
|
|
|
func (r *Resampler) OutputSamplesNeeded(inputSamples int) int {
|
|
inputFrames := inputSamples / r.channels
|
|
outputFrames := int(float64(inputFrames) / r.ratio)
|
|
return outputFrames * r.channels
|
|
}
|
|
|
|
func (r *Resampler) InputSamplesNeeded(outputSamples int) int {
|
|
outputFrames := outputSamples / r.channels
|
|
inputFrames := int(float64(outputFrames) * r.ratio)
|
|
return inputFrames * r.channels
|
|
}
|