Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

resampler: linear and decimation #3

Merged
merged 1 commit into from
Jan 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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