From 142325749df01c65c9f6657b0ab0fa63332b3008 Mon Sep 17 00:00:00 2001 From: Pablo Diaz Date: Tue, 10 Jan 2023 02:03:11 -0400 Subject: [PATCH] resampler: linear and decimation --- examples/linear_resampler/main.go | 75 ++++++++++ examples/ulaw2wav_logpcm/main.go | 2 +- examples/ulaw2wav_lpcm/main.go | 2 +- pkg/method/method.go | 9 +- pkg/{downsampler => resampler}/downsampler.go | 2 +- .../downsampler_test.go | 2 +- pkg/resampler/linear.go | 25 ++++ pkg/resampler/linear_test.go | 48 +++++++ .../neuronal_networks.go | 2 +- pkg/resampler/resample.go | 13 ++ pkg/{upsampler => resampler}/upsampler.go | 2 +- pkg/transcoder/models_transcoder.go | 3 +- pkg/transcoder/mulaw2wav.go | 4 - pkg/transcoder/mulaw2wavlogpcm.go | 1 - pkg/transcoder/resampling_general.go | 70 +++++++++ pkg/transcoder/router.go | 38 ++++- pkg/transcoder/transcoding_general.go | 25 ++-- pkg/transcoder/wavlpcm2wavlpcm.go | 133 ++++++++++++++++++ 18 files changed, 429 insertions(+), 27 deletions(-) create mode 100644 examples/linear_resampler/main.go rename pkg/{downsampler => resampler}/downsampler.go (95%) rename pkg/{downsampler => resampler}/downsampler_test.go (97%) create mode 100644 pkg/resampler/linear.go create mode 100644 pkg/resampler/linear_test.go rename pkg/{upsampler => resampler}/neuronal_networks.go (77%) create mode 100644 pkg/resampler/resample.go rename pkg/{upsampler => resampler}/upsampler.go (70%) create mode 100644 pkg/transcoder/resampling_general.go create mode 100644 pkg/transcoder/wavlpcm2wavlpcm.go diff --git a/examples/linear_resampler/main.go b/examples/linear_resampler/main.go new file mode 100644 index 0000000..c4fcff4 --- /dev/null +++ b/examples/linear_resampler/main.go @@ -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) + } + +} diff --git a/examples/ulaw2wav_logpcm/main.go b/examples/ulaw2wav_logpcm/main.go index 7fe7921..74505f1 100644 --- a/examples/ulaw2wav_logpcm/main.go +++ b/examples/ulaw2wav_logpcm/main.go @@ -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, }, diff --git a/examples/ulaw2wav_lpcm/main.go b/examples/ulaw2wav_lpcm/main.go index accd527..7106a39 100644 --- a/examples/ulaw2wav_lpcm/main.go +++ b/examples/ulaw2wav_lpcm/main.go @@ -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, }, diff --git a/pkg/method/method.go b/pkg/method/method.go index 6fa5c6d..d71a2b7 100644 --- a/pkg/method/method.go +++ b/pkg/method/method.go @@ -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", +} diff --git a/pkg/downsampler/downsampler.go b/pkg/resampler/downsampler.go similarity index 95% rename from pkg/downsampler/downsampler.go rename to pkg/resampler/downsampler.go index ce85540..9345660 100644 --- a/pkg/downsampler/downsampler.go +++ b/pkg/resampler/downsampler.go @@ -1,4 +1,4 @@ -package downsampler +package resampler import "errors" diff --git a/pkg/downsampler/downsampler_test.go b/pkg/resampler/downsampler_test.go similarity index 97% rename from pkg/downsampler/downsampler_test.go rename to pkg/resampler/downsampler_test.go index 9059619..5391003 100644 --- a/pkg/downsampler/downsampler_test.go +++ b/pkg/resampler/downsampler_test.go @@ -1,4 +1,4 @@ -package downsampler +package resampler import ( "reflect" diff --git a/pkg/resampler/linear.go b/pkg/resampler/linear.go new file mode 100644 index 0000000..620642a --- /dev/null +++ b/pkg/resampler/linear.go @@ -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 +} diff --git a/pkg/resampler/linear_test.go b/pkg/resampler/linear_test.go new file mode 100644 index 0000000..efe32e6 --- /dev/null +++ b/pkg/resampler/linear_test.go @@ -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 +} diff --git a/pkg/upsampler/neuronal_networks.go b/pkg/resampler/neuronal_networks.go similarity index 77% rename from pkg/upsampler/neuronal_networks.go rename to pkg/resampler/neuronal_networks.go index 3316a38..a051d70 100644 --- a/pkg/upsampler/neuronal_networks.go +++ b/pkg/resampler/neuronal_networks.go @@ -1,3 +1,3 @@ -package upsampler +package resampler // TODO: Add externals connection to infer from an endpoint diff --git a/pkg/resampler/resample.go b/pkg/resampler/resample.go new file mode 100644 index 0000000..73967a5 --- /dev/null +++ b/pkg/resampler/resample.go @@ -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", +} diff --git a/pkg/upsampler/upsampler.go b/pkg/resampler/upsampler.go similarity index 70% rename from pkg/upsampler/upsampler.go rename to pkg/resampler/upsampler.go index 2f517f3..3cae07b 100644 --- a/pkg/upsampler/upsampler.go +++ b/pkg/resampler/upsampler.go @@ -1,3 +1,3 @@ -package upsampler +package resampler // TODO: Add different upsampling methods diff --git a/pkg/transcoder/models_transcoder.go b/pkg/transcoder/models_transcoder.go index ade4ecf..13b33e0 100644 --- a/pkg/transcoder/models_transcoder.go +++ b/pkg/transcoder/models_transcoder.go @@ -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 diff --git a/pkg/transcoder/mulaw2wav.go b/pkg/transcoder/mulaw2wav.go index 7c80de5..eb44a36 100644 --- a/pkg/transcoder/mulaw2wav.go +++ b/pkg/transcoder/mulaw2wav.go @@ -15,9 +15,6 @@ import ( "golang.org/x/term" ) -var WIDTH_TERMINAL = 80 -var HEIGHT_TERMINAL = 30 - func init() { err := error(nil) @@ -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 diff --git a/pkg/transcoder/mulaw2wavlogpcm.go b/pkg/transcoder/mulaw2wavlogpcm.go index d112ef1..fcadad0 100644 --- a/pkg/transcoder/mulaw2wavlogpcm.go +++ b/pkg/transcoder/mulaw2wavlogpcm.go @@ -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 diff --git a/pkg/transcoder/resampling_general.go b/pkg/transcoder/resampling_general.go new file mode 100644 index 0000000..c036c36 --- /dev/null +++ b/pkg/transcoder/resampling_general.go @@ -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 +} diff --git a/pkg/transcoder/router.go b/pkg/transcoder/router.go index 1ea8ea2..316e930 100644 --- a/pkg/transcoder/router.go +++ b/pkg/transcoder/router.go @@ -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 { @@ -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( @@ -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], + ) + + } +} diff --git a/pkg/transcoder/transcoding_general.go b/pkg/transcoder/transcoding_general.go index b2c737d..c81a722 100644 --- a/pkg/transcoder/transcoding_general.go +++ b/pkg/transcoder/transcoding_general.go @@ -35,7 +35,7 @@ func TranscodeBytes(in *AudioFileIn, out *AudioFileOut, transcoder *Transcoder) } - log.Println("Transcoding done:", bitsProcessed, "bits processed") + transcoder.Println("Transcoding done:", bitsProcessed, "bits processed") return nil } @@ -73,35 +73,36 @@ func differentSpaceEncoding(in *AudioFileIn, out *AudioFileOut, transcoder *Tran sizeBuff = transcoder.SizeBuffer } nTotal := 0 - buf := make([]byte, sizeBuff) // input buffer - buf2 := make([]byte, sizeBuff*2) // output buffer + bufIn := make([]byte, sizeBuff) // input buffer + bufOut := make([]byte, sizeBuff*2) // output buffer for { - n, err := in.Reader.Read(buf) + 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 } - buf = buf[:n] + bufIn = bufIn[:n] // buf2 is different size than buf - buf2, _ = decoder.DecodeFrameUlaw2Lpcm(buf) // IMPORTANT:buf cut to n bytes - out.Length += len(buf2) - if _, err = out.Writer.Write(buf2); err != nil { + bufOut, _ = decoder.DecodeFrameUlaw2Lpcm(bufIn) // 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 doOnceTranscoding.Do(func() { transcoder.Println("[Transcoder] Transcoding data - sample of the first 4 bytes (hex)") onlyNFirst := 4 transcoder.Println( - "[OLD]", fmt.Sprintf("% 2x", buf[:onlyNFirst]), - "\n[NEW]", fmt.Sprintf("% 2x", buf2[:onlyNFirst*2]), + "[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", buf[:onlyNFirst]), - "\n[NEW]", fmt.Sprintf("%3d", buf2[:onlyNFirst*2]), + "[OLD]", fmt.Sprintf("%3d", bufIn[:onlyNFirst]), + "\n[NEW]", fmt.Sprintf("%3d", bufOut[:onlyNFirst*2]), ) }) } diff --git a/pkg/transcoder/wavlpcm2wavlpcm.go b/pkg/transcoder/wavlpcm2wavlpcm.go new file mode 100644 index 0000000..5af130f --- /dev/null +++ b/pkg/transcoder/wavlpcm2wavlpcm.go @@ -0,0 +1,133 @@ +package transcoder + +import ( + "bufio" + "fmt" + "io" + "os" + + "github.com/pablodz/sopro/pkg/audioconfig" + "github.com/pablodz/sopro/pkg/cpuarch" + "github.com/pablodz/sopro/pkg/encoding" +) + +func wavLpcm2wavLpcm(in *AudioFileIn, out *AudioFileOut, transcoder *Transcoder) (err error) { + + // read all the file + if transcoder.Verbose { + graphIn(in) + } + + // Get the WAV file configuration + channels := out.Config.(audioconfig.WavConfig).Channels + sampleRate := out.Config.(audioconfig.WavConfig).SampleRate + bitsPerSample := out.Config.(audioconfig.WavConfig).BitDepth + transcoder.SourceConfigs.Encoding = in.Config.(audioconfig.WavConfig).Encoding + transcoder.TargetConfigs.Encoding = out.Config.(audioconfig.WavConfig).Encoding + transcoder.BitDepth = bitsPerSample + + if transcoder.SourceConfigs.Endianness == cpuarch.NOT_FILLED && transcoder.TargetConfigs.Endianness == cpuarch.NOT_FILLED { + transcoder.SourceConfigs.Endianness = cpuarch.LITTLE_ENDIAN // replace with cpuarch.GetEndianess() + transcoder.TargetConfigs.Endianness = cpuarch.LITTLE_ENDIAN + } + + transcoder.Println( + "\n[Format] ", in.Format, "=>", out.Format, + "\n[Encoding] ", encoding.ENCODINGS[in.Config.(audioconfig.WavConfig).Encoding], "=>", encoding.ENCODINGS[out.Config.(audioconfig.WavConfig).Encoding], + "\n[Channels] ", in.Config.(audioconfig.WavConfig).Channels, "=>", channels, + "\n[SampleRate] ", in.Config.(audioconfig.WavConfig).SampleRate, "=>", sampleRate, "kHz", + "\n[BitDepth] ", in.Config.(audioconfig.WavConfig).BitDepth, "=>", bitsPerSample, "bytes", + "\n[Transcoder][Source][Encoding]", encoding.ENCODINGS[transcoder.SourceConfigs.Encoding], + "\n[Transcoder][Target][Encoding]", encoding.ENCODINGS[transcoder.TargetConfigs.Encoding], + "\n[Transcoder][BitDepth] ", transcoder.BitDepth, + "\n[Transcoder][Endianness] ", cpuarch.ENDIANESSES[cpuarch.GetEndianess()], + ) + + // Create a buffered reader and writer + in.Reader = bufio.NewReader(in.Data) + out.Writer = bufio.NewWriter(out.Data) + out.Length = 0 + + in.Reader.Discard(44) // avoid first 44 bytes of in + + headersWav := []byte{ + 'R', 'I', 'F', 'F', // Chunk ID + 0, 0, 0, 0, // Chunk size + 'W', 'A', 'V', 'E', // Format + 'f', 'm', 't', ' ', // Sub-chunk 1 ID + 16, 0, 0, 0, // Sub-chunk 1 size + audioconfig.WAVE_FORMAT_PCM, 0, // Audio format (PCM) + byte(channels), 0, // Number of channels + byte(sampleRate & 0xFF), // sample rate (low) + byte(sampleRate >> 8 & 0xFF), // sample rate (mid) + byte(sampleRate >> 16 & 0xFF), // sample rate (high) + byte(sampleRate >> 24 & 0xFF), // sample rate (high) + byte(sampleRate * channels * (bitsPerSample / 8) & 0xFF), // byte rate (low) + byte(sampleRate * channels * (bitsPerSample / 8) >> 8 & 0xFF), // byte rate (mid) + byte(sampleRate * channels * (bitsPerSample / 8) >> 16 & 0xFF), // byte rate (high) + byte(sampleRate * channels * (bitsPerSample / 8) >> 24 & 0xFF), // byte rate (high) + byte(channels * (bitsPerSample / 8)), 0, // block align + byte(bitsPerSample), 0, // bits per sample + 'd', 'a', 't', 'a', + 0, 0, 0, 0, + } + out.Writer.Write(headersWav) + out.Length += len(headersWav) + + if transcoder.Verbose { + audioconfig.PrintWavHeaders(headersWav) + } + + // Copy the data from the input file to the output file in chunks + if err = ResampleBytes(in, out, transcoder); err != nil { + return fmt.Errorf("error converting bytes: %v", err) + } + + // Flush the output file + if err := out.Writer.Flush(); err != nil { + return fmt.Errorf("error flushing output file: %v", err) + } + transcoder.Println("Wrote", out.Length, "bytes to output file") + + // Update the file size and data size fields + fileFixer := out.Data.(*os.File) + r, err := fileFixer.Seek(4, io.SeekStart) + if err != nil { + return fmt.Errorf("error seeking file: %v", err) + } + transcoder.Println("Seeked to:", r) + fileSize := []byte{ + byte((out.Length - 8) & 0xff), + byte((out.Length - 8) >> 8 & 0xff), + byte((out.Length - 8) >> 16 & 0xff), + byte((out.Length - 8) >> 24 & 0xff), + } + n, err := fileFixer.Write(fileSize) + if err != nil { + return fmt.Errorf("error writing file size: %v", err) + } + transcoder.Println("File size:", fmt.Sprintf("% 02x", fileSize), "bytes written:", n) + dataSize := []byte{ + byte((out.Length - 44) & 0xff), + byte((out.Length - 44) >> 8 & 0xff), + byte((out.Length - 44) >> 16 & 0xff), + byte((out.Length - 44) >> 24 & 0xff), + } + r, err = fileFixer.Seek(40, io.SeekStart) + if err != nil { + return fmt.Errorf("[2]error seeking file: %v", err) + } + transcoder.Println("Seeked to:", r) + n, err = fileFixer.Write(dataSize) + if err != nil { + return fmt.Errorf("error writing data size: %v", err) + } + transcoder.Println("Data size:", fmt.Sprintf("% 02x", dataSize), "bytes written:", n) + + if transcoder.Verbose { + graphOut(in, out) + } + + return nil + +}