Skip to content

Commit

Permalink
resampler: linear and decimation
Browse files Browse the repository at this point in the history
  • Loading branch information
pablodz committed Jan 10, 2023
1 parent c90b8ed commit 1423257
Show file tree
Hide file tree
Showing 18 changed files with 429 additions and 27 deletions.
75 changes: 75 additions & 0 deletions examples/linear_resampler/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package main

import (
"os"

"github.com/pablodz/sopro/pkg/audioconfig"
"github.com/pablodz/sopro/pkg/cpuarch"
"github.com/pablodz/sopro/pkg/encoding"
"github.com/pablodz/sopro/pkg/fileformat"
"github.com/pablodz/sopro/pkg/resampler"
"github.com/pablodz/sopro/pkg/transcoder"
)

func main() {

// Open the input file
in, err := os.Open("./internal/samples/v1_16b_16000.wav")
if err != nil {
panic(err)
}
defer in.Close()

// Create the output file
out, err := os.Create("./internal/samples/v1_16b_8000.wav")
if err != nil {
panic(err)
}
defer out.Close()

// create a transcoder
t := &transcoder.Transcoder{
MethodR: resampler.LINEAR_INTERPOLATION,
SourceConfigs: transcoder.TranscoderAudioConfig{
Endianness: cpuarch.LITTLE_ENDIAN,
},
TargetConfigs: transcoder.TranscoderAudioConfig{
Endianness: cpuarch.LITTLE_ENDIAN,
},
SizeBuffer: 1024,
Verbose: true,
}

// Transcode the file
err = t.Wav2Wav(
&transcoder.AudioFileIn{
Data: in,
AudioFileGeneral: transcoder.AudioFileGeneral{
Format: fileformat.AUDIO_WAV,
Config: audioconfig.WavConfig{
BitDepth: 16,
Channels: 1,
Encoding: encoding.SPACE_LINEAR, // ulaw is logarithmic
SampleRate: 16000,
},
},
},
&transcoder.AudioFileOut{
Data: out,
AudioFileGeneral: transcoder.AudioFileGeneral{
Format: fileformat.AUDIO_WAV,
Config: audioconfig.WavConfig{
BitDepth: 16,
Channels: 1,
Encoding: encoding.SPACE_LINEAR,
SampleRate: 8000,
},
},
},
)

if err != nil {
panic(err)
}

}
2 changes: 1 addition & 1 deletion examples/ulaw2wav_logpcm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func main() {

// create a transcoder
t := &transcoder.Transcoder{
Method: method.BIT_TABLE,
MethodT: method.BIT_LOOKUP_TABLE,
SourceConfigs: transcoder.TranscoderAudioConfig{
Endianness: cpuarch.LITTLE_ENDIAN,
},
Expand Down
2 changes: 1 addition & 1 deletion examples/ulaw2wav_lpcm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ func main() {

// create a transcoder
t := &transcoder.Transcoder{
Method: method.BIT_TABLE,
MethodT: method.BIT_LOOKUP_TABLE,
SourceConfigs: transcoder.TranscoderAudioConfig{
Endianness: cpuarch.LITTLE_ENDIAN,
},
Expand Down
9 changes: 8 additions & 1 deletion pkg/method/method.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ package method
const (
NOT_FILLED = (iota - 1) // Not filled
BIT_SHIFT // Bit shift
BIT_TABLE // Bit table, use a slice to store the values
BIT_LOOKUP_TABLE // Bit table, use a slice to store the values
BIT_ADVANCED_FUNCTION // Advanced function, use a function to calculate and return the values
)

var METHODS = map[int]string{
NOT_FILLED: "NOT_FILLED",
BIT_SHIFT: "BIT_SHIFT",
BIT_LOOKUP_TABLE: "BIT_LOOKUP_TABLE",
BIT_ADVANCED_FUNCTION: "BIT_ADVANCED_FUNCTION",
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package downsampler
package resampler

import "errors"

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package downsampler
package resampler

import (
"reflect"
Expand Down
25 changes: 25 additions & 0 deletions pkg/resampler/linear.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package resampler

func LinearInterpolation[T int16 | int32 | int64 | int | byte](data []T, ratio float64) ([]T, error) {

// Calculate the length of the resampled data slice.
resampledLength := int(float64(len(data)) / ratio)

// Preallocate the resampled data slice with the correct size.
resampledData := make([]T, resampledLength)

// Iterate over the original data, interpolating new samples as necessary to
// resample the data at the target sample rate.
for i := 0; i < len(data)-1; i++ {
// Calculate the interpolated value between the current and next samples.
interpolatedValue := T((float64(data[i]) + float64(data[i+1])) / 2)
resampledData[int(float64(i)/ratio)] = interpolatedValue

// Skip the next sample if necessary.
if ratio > 1.0 {
i += int(ratio) - 1
}
}

return resampledData, nil
}
48 changes: 48 additions & 0 deletions pkg/resampler/linear_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package resampler

import "testing"

func TestLinearInterpolation(t *testing.T) {
data := []int16{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
expected := []int16{1, 3, 5, 7, 9}
ratio := float64(16000) / float64(8000)
resampledData, err := LinearInterpolation(data, ratio)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if !testEq(resampledData, expected) {
t.Errorf("Expected %v, got %v", expected, resampledData)
}
}

func TestLinearInterpolation2(t *testing.T) {
data := []int16{1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
expected := []int16{1, 0, 2, 0, 3, 0, 4, 0, 5, 0, 6, 0, 7, 0, 8, 0, 9, 0, 0, 0}
ratio := float64(8000) / float64(16000)
resampledData, err := LinearInterpolation(data, ratio)
if err != nil {
t.Errorf("Expected no error, got %v", err)
}
if !testEq(resampledData, expected) {
t.Errorf("Expected %v, got %v", expected, resampledData)
}
}

// testEq is a helper function to compare two slices of int16 values.
func testEq[T int16 | int32 | int64 | int](a, b []T) bool {
if a == nil && b == nil {
return true
}
if a == nil || b == nil {
return false
}
if len(a) != len(b) {
return false
}
for i := range a {
if a[i] != b[i] {
return false
}
}
return true
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package upsampler
package resampler

// TODO: Add externals connection to infer from an endpoint
13 changes: 13 additions & 0 deletions pkg/resampler/resample.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package resampler

const (
NOT_FILLED = (iota - 1) // Not filled
LINEAR_INTERPOLATION // Linear interpolation
POLYNOMIAL_INTERPOLATION // Polynomial interpolation
)

var RESAMPLER_METHODS = map[int]string{
NOT_FILLED: "NOT_FILLED",
LINEAR_INTERPOLATION: "LINEAR_INTERPOLATION",
POLYNOMIAL_INTERPOLATION: "POLYNOMIAL_INTERPOLATION",
}
2 changes: 1 addition & 1 deletion pkg/upsampler/upsampler.go → pkg/resampler/upsampler.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package upsampler
package resampler

// TODO: Add different upsampling methods
3 changes: 2 additions & 1 deletion pkg/transcoder/models_transcoder.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package transcoder

type Transcoder struct {
Method int // the method of transcoding (e.g. 1, 2, 3, etc.)
MethodT int // the method of transcoding (e.g. 1, 2, 3, etc.)
MethodR int // the method of resampling (e.g. 1, 2, 3, etc.)
MethodAdvancedConfigs interface{} // the specific configuration options for the transcoding method
SizeBuffer int // the size of the buffer to read from the input file. Default is 1024
SourceConfigs TranscoderAudioConfig // the source configuration
Expand Down
4 changes: 0 additions & 4 deletions pkg/transcoder/mulaw2wav.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,6 @@ import (
"golang.org/x/term"
)

var WIDTH_TERMINAL = 80
var HEIGHT_TERMINAL = 30

func init() {

err := error(nil)
Expand All @@ -27,7 +24,6 @@ func init() {
}
}

// TODO: split functions for different sizes of files
// Transcode an ulaw file to a wav file (large files supported)
// https://raw.githubusercontent.com/corkami/pics/master/binary/WAV.png
// http://www.topherlee.com/software/pcm-tut-wavformat.html
Expand Down
1 change: 0 additions & 1 deletion pkg/transcoder/mulaw2wavlogpcm.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ func init() {
}
}

// TODO: split functions for different sizes of files
// Transcode an ulaw file to a wav file (large files supported)
// https://raw.githubusercontent.com/corkami/pics/master/binary/WAV.png
// http://www.topherlee.com/software/pcm-tut-wavformat.html
Expand Down
70 changes: 70 additions & 0 deletions pkg/transcoder/resampling_general.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package transcoder

import (
"fmt"
"io"
"sync"

"github.com/pablodz/sopro/pkg/audioconfig"
"github.com/pablodz/sopro/pkg/resampler"
)

var doOnceResampling sync.Once

func ResampleBytes(in *AudioFileIn, out *AudioFileOut, transcoder *Transcoder) error {

bitsProcessed, err := differentSampleRate(in, out, transcoder)
if err != nil {
return err
}
transcoder.Println("Transcoding done:", bitsProcessed, "bits processed")

return nil
}

func differentSampleRate(in *AudioFileIn, out *AudioFileOut, transcoder *Transcoder) (int, error) {
sizeBuff := 1024 // max size, more than that would be too much
if transcoder.SizeBuffer > 0 {
sizeBuff = transcoder.SizeBuffer
}
nTotal := 0
sampleRateIn := in.Config.(audioconfig.WavConfig).SampleRate
sampleRateOut := out.Config.(audioconfig.WavConfig).SampleRate
ratio := float64(sampleRateIn) / float64(sampleRateOut)

bufIn := make([]byte, sizeBuff) // input buffer
bufOut := make([]byte, sizeBuff*int(ratio)) // output buffer
transcoder.Println("ratio", ratio, "ratioInt", int(ratio))
for {
n, err := in.Reader.Read(bufIn)
if err != nil && err != io.EOF {
return nTotal, fmt.Errorf("error reading input file: %v", err)
}
if n == 0 {
break
}
bufIn = bufIn[:n]
// buf2 is different size than buf
bufOut, _ = resampler.LinearInterpolation(bufIn, ratio) // IMPORTANT:buf cut to n bytes
out.Length += len(bufOut)
if _, err = out.Writer.Write(bufOut); err != nil {
return nTotal, fmt.Errorf("error writing output file: %v", err)
}
nTotal += n

doOnceResampling.Do(func() {
transcoder.Println("[Transcoder] Transcoding data - sample of the first 4 bytes (hex)")
onlyNFirst := 8
transcoder.Println(
"[OLD]", fmt.Sprintf("% 2x", bufIn[:onlyNFirst]),
"\n[NEW]", fmt.Sprintf("% 2x", bufOut[:onlyNFirst/2]),
)
transcoder.Println("[Transcoder] Transcoding data - sample of the first 4 bytes (decimal)")
transcoder.Println(
"[OLD]", fmt.Sprintf("%3d", bufIn[:onlyNFirst]),
"\n[NEW]", fmt.Sprintf("%3d", bufOut[:onlyNFirst/2]),
)
})
}
return nTotal, nil
}
38 changes: 36 additions & 2 deletions pkg/transcoder/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,13 @@ import (

"github.com/pablodz/sopro/pkg/audioconfig"
"github.com/pablodz/sopro/pkg/encoding"
"github.com/pablodz/sopro/pkg/method"
"github.com/pablodz/sopro/pkg/resampler"
)

var WIDTH_TERMINAL = 80
var HEIGHT_TERMINAL = 30

const ErrUnsupportedConversion = "unsupported conversion"

func (t *Transcoder) Mulaw2Wav(in *AudioFileIn, out *AudioFileOut) error {
Expand All @@ -15,9 +20,13 @@ func (t *Transcoder) Mulaw2Wav(in *AudioFileIn, out *AudioFileOut) error {
outSpace := out.Config.(audioconfig.WavConfig).Encoding

switch {
case inSpace == encoding.SPACE_LOGARITHMIC && outSpace == encoding.SPACE_LINEAR:
case t.MethodT != method.BIT_LOOKUP_TABLE &&
inSpace == encoding.SPACE_LOGARITHMIC &&
outSpace == encoding.SPACE_LINEAR:
return mulaw2WavLpcm(in, out, t)
case inSpace == encoding.SPACE_LOGARITHMIC && outSpace == encoding.SPACE_LOGARITHMIC:
case t.MethodT != method.BIT_LOOKUP_TABLE &&
inSpace == encoding.SPACE_LOGARITHMIC &&
outSpace == encoding.SPACE_LOGARITHMIC:
return mulaw2WavLogpcm(in, out, t)
default:
return fmt.Errorf(
Expand All @@ -29,3 +38,28 @@ func (t *Transcoder) Mulaw2Wav(in *AudioFileIn, out *AudioFileOut) error {

}
}

func (t *Transcoder) Wav2Wav(in *AudioFileIn, out *AudioFileOut) error {

inSpace := in.Config.(audioconfig.WavConfig).Encoding
outSpace := out.Config.(audioconfig.WavConfig).Encoding

switch {
case t.MethodR == resampler.LINEAR_INTERPOLATION &&
inSpace == encoding.SPACE_LINEAR &&
outSpace == encoding.SPACE_LINEAR:
return wavLpcm2wavLpcm(in, out, t)
case t.MethodR == resampler.LINEAR_INTERPOLATION &&
inSpace == encoding.SPACE_LOGARITHMIC &&
outSpace == encoding.SPACE_LOGARITHMIC:
fallthrough
default:
return fmt.Errorf(
"%s: %s -> %s",
ErrUnsupportedConversion,
encoding.ENCODINGS[inSpace],
encoding.ENCODINGS[outSpace],
)

}
}
Loading

0 comments on commit 1423257

Please sign in to comment.