diff --git a/pkg/wave/decoder.go b/pkg/wave/decoder.go new file mode 100644 index 0000000..cbb5e2d --- /dev/null +++ b/pkg/wave/decoder.go @@ -0,0 +1,159 @@ +package wave + +import ( + "encoding/binary" + "fmt" + "math" +) + +// Format represents how audio is formatted in memory +type Format string + +const ( + FormatInt16Interleaved Format = "Int16Interleaved" + FormatInt16NonInterleaved = "Int16NonInterleaved" + FormatFloat32Interleaved = "Float32Interleaved" + FormatFloat32NonInterleaved = "Float32NonInterleaved" +) + +// Decoder decodes raw chunk to Audio +type Decoder interface { + // Decode decodes raw chunk in endian byte order + Decode(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) +} + +// DecoderFunc is a proxy type for Decoder +type DecoderFunc func(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) + +func (f DecoderFunc) Decode(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) { + return f(endian, chunk, channels) +} + +// NewDecoder creates a decoder to decode raw audio data in the given format +func NewDecoder(f Format) (Decoder, error) { + var decoder DecoderFunc + + switch f { + case FormatInt16Interleaved: + decoder = decodeInt16Interleaved + case FormatInt16NonInterleaved: + decoder = decodeInt16NonInterleaved + case FormatFloat32Interleaved: + decoder = decodeFloat32Interleaved + case FormatFloat32NonInterleaved: + decoder = decodeFloat32NonInterleaved + default: + return nil, fmt.Errorf("%s is not supported", f) + } + + return decoder, nil +} + +func calculateChunkInfo(chunk []byte, channels int, sampleSize int) (ChunkInfo, error) { + if channels <= 0 { + return ChunkInfo{}, fmt.Errorf("channels has to be greater than 0") + } + + if sampleSize <= 0 { + return ChunkInfo{}, fmt.Errorf("sample size has to be greater than 0") + } + + sampleLen := channels * sampleSize + if len(chunk)%sampleLen != 0 { + expectedLen := len(chunk) + (sampleLen - len(chunk)%sampleLen) + return ChunkInfo{}, fmt.Errorf("expected chunk to have a length of %d, but got %d", expectedLen, len(chunk)) + } + + return ChunkInfo{ + Channels: channels, + Len: len(chunk) / (channels * sampleSize), + }, nil +} + +func decodeInt16Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) { + sampleSize := 2 + chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize) + if err != nil { + return nil, err + } + + container := NewInt16Interleaved(chunkInfo) + sampleLen := sampleSize * channels + var i int + for offset := 0; offset+sampleLen <= len(chunk); offset += sampleLen { + for ch := 0; ch < channels; ch++ { + flatOffset := offset + ch*sampleSize + sample := endian.Uint16(chunk[flatOffset : flatOffset+sampleSize]) + container.SetInt16(i, ch, Int16Sample(sample)) + } + i++ + } + + return container, nil +} + +func decodeInt16NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) { + sampleSize := 2 + chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize) + if err != nil { + return nil, err + } + + container := NewInt16NonInterleaved(chunkInfo) + chunkLen := len(chunk) / channels + for ch := 0; ch < channels; ch++ { + offset := ch * chunkLen + for i := 0; i < chunkInfo.Len; i++ { + flatOffset := offset + i*sampleSize + sample := endian.Uint16(chunk[flatOffset : flatOffset+sampleSize]) + container.SetInt16(i, ch, Int16Sample(sample)) + } + } + + return container, nil +} + +func decodeFloat32Interleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) { + sampleSize := 4 + chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize) + if err != nil { + return nil, err + } + + container := NewFloat32Interleaved(chunkInfo) + sampleLen := sampleSize * channels + var i int + for offset := 0; offset+sampleLen <= len(chunk); offset += sampleLen { + for ch := 0; ch < channels; ch++ { + flatOffset := offset + ch*sampleSize + sample := endian.Uint32(chunk[flatOffset : flatOffset+sampleSize]) + sampleF := math.Float32frombits(sample) + container.SetFloat32(i, ch, Float32Sample(sampleF)) + } + i++ + } + + return container, nil +} + +func decodeFloat32NonInterleaved(endian binary.ByteOrder, chunk []byte, channels int) (Audio, error) { + sampleSize := 4 + chunkInfo, err := calculateChunkInfo(chunk, channels, sampleSize) + if err != nil { + return nil, err + } + + container := NewFloat32NonInterleaved(chunkInfo) + chunkLen := len(chunk) / channels + for ch := 0; ch < channels; ch++ { + offset := ch * chunkLen + for i := 0; i < chunkInfo.Len; i++ { + flatOffset := offset + i*sampleSize + sample := endian.Uint32(chunk[flatOffset : flatOffset+sampleSize]) + sampleF := math.Float32frombits(sample) + container.SetFloat32(i, ch, Float32Sample(sampleF)) + } + } + + return container, nil +} diff --git a/pkg/wave/decoder_test.go b/pkg/wave/decoder_test.go new file mode 100644 index 0000000..bda52db --- /dev/null +++ b/pkg/wave/decoder_test.go @@ -0,0 +1,314 @@ +package wave + +import ( + "encoding/binary" + "math" + "reflect" + "testing" +) + +func TestCalculateChunkInfo(t *testing.T) { + testCases := map[string]struct { + chunk []byte + channels int + sampleSize int + expected ChunkInfo + expectErr bool + }{ + "InvalidChunkSize1": { + chunk: make([]byte, 3), + channels: 2, + sampleSize: 2, + expected: ChunkInfo{}, + expectErr: true, + }, + "InvalidChunkSize2": { + chunk: make([]byte, 4), + channels: 2, + sampleSize: 4, + expected: ChunkInfo{}, + expectErr: true, + }, + "InvalidChannels": { + chunk: nil, + channels: 0, + sampleSize: 2, + expected: ChunkInfo{}, + expectErr: true, + }, + "InvalidSampleSize": { + chunk: nil, + channels: 2, + sampleSize: 0, + expected: ChunkInfo{}, + expectErr: true, + }, + "Valid1": { + chunk: nil, + channels: 2, + sampleSize: 2, + expected: ChunkInfo{ + Len: 0, + Channels: 2, + SamplingRate: 0, + }, + expectErr: false, + }, + "Valid2": { + chunk: make([]byte, 8), + channels: 2, + sampleSize: 4, + expected: ChunkInfo{ + Len: 1, + Channels: 2, + SamplingRate: 0, + }, + expectErr: false, + }, + "Valid3": { + chunk: make([]byte, 4), + channels: 1, + sampleSize: 2, + expected: ChunkInfo{ + Len: 2, + Channels: 1, + SamplingRate: 0, + }, + expectErr: false, + }, + } + + for testCaseName, testCase := range testCases { + testCase := testCase + t.Run(testCaseName, func(t *testing.T) { + actual, err := calculateChunkInfo(testCase.chunk, testCase.channels, testCase.sampleSize) + if testCase.expectErr && err == nil { + t.Fatal("expected an error, but got nil") + } else if !testCase.expectErr && err != nil { + t.Fatalf("expected no error, but got %s", err) + } else if !testCase.expectErr && !reflect.DeepEqual(actual, testCase.expected) { + t.Errorf("Wrong chunk info calculation result,\nexpected:\n%+v\ngot:\n%+v", testCase.expected, actual) + } + }) + } +} + +func TestDecodeInt16Interleaved(t *testing.T) { + raw := []byte{ + // 16 bits per channel + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, + } + + t.Run("BigEndian", func(t *testing.T) { + expected := &Int16Interleaved{ + Data: []int16{ + int16(binary.BigEndian.Uint16([]byte{0x01, 0x02})), + int16(binary.BigEndian.Uint16([]byte{0x03, 0x04})), + int16(binary.BigEndian.Uint16([]byte{0x05, 0x06})), + int16(binary.BigEndian.Uint16([]byte{0x07, 0x08})), + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeInt16Interleaved(binary.BigEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) + + t.Run("LittleEndian", func(t *testing.T) { + expected := &Int16Interleaved{ + Data: []int16{ + int16(binary.LittleEndian.Uint16([]byte{0x01, 0x02})), + int16(binary.LittleEndian.Uint16([]byte{0x03, 0x04})), + int16(binary.LittleEndian.Uint16([]byte{0x05, 0x06})), + int16(binary.LittleEndian.Uint16([]byte{0x07, 0x08})), + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeInt16Interleaved(binary.LittleEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) +} + +func TestDecodeInt16NonInterleaved(t *testing.T) { + raw := []byte{ + // 16 bits per channel + 0x01, 0x02, 0x03, 0x04, + 0x05, 0x06, 0x07, 0x08, + } + + t.Run("BigEndian", func(t *testing.T) { + expected := &Int16NonInterleaved{ + Data: [][]int16{ + {int16(binary.BigEndian.Uint16([]byte{0x01, 0x02})), int16(binary.BigEndian.Uint16([]byte{0x03, 0x04}))}, + {int16(binary.BigEndian.Uint16([]byte{0x05, 0x06})), int16(binary.BigEndian.Uint16([]byte{0x07, 0x08}))}, + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeInt16NonInterleaved(binary.BigEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) + + t.Run("LittleEndian", func(t *testing.T) { + expected := &Int16NonInterleaved{ + Data: [][]int16{ + {int16(binary.LittleEndian.Uint16([]byte{0x01, 0x02})), int16(binary.LittleEndian.Uint16([]byte{0x03, 0x04}))}, + {int16(binary.LittleEndian.Uint16([]byte{0x05, 0x06})), int16(binary.LittleEndian.Uint16([]byte{0x07, 0x08}))}, + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeInt16NonInterleaved(binary.LittleEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) +} + +func TestDecodeFloat32Interleaved(t *testing.T) { + raw := []byte{ + // 32 bits per channel + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + + t.Run("BigEndian", func(t *testing.T) { + expected := &Float32Interleaved{ + Data: []float32{ + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})), + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})), + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})), + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})), + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeFloat32Interleaved(binary.BigEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) + + t.Run("LittleEndian", func(t *testing.T) { + expected := &Float32Interleaved{ + Data: []float32{ + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})), + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})), + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})), + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})), + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeFloat32Interleaved(binary.LittleEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) +} + +func TestDecodeFloat32NonInterleaved(t *testing.T) { + raw := []byte{ + // 32 bits per channel + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, + 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, + } + + t.Run("BigEndian", func(t *testing.T) { + expected := &Float32NonInterleaved{ + Data: [][]float32{ + { + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})), + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})), + }, + { + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})), + math.Float32frombits(binary.BigEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})), + }, + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeFloat32NonInterleaved(binary.BigEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) + + t.Run("LittleEndian", func(t *testing.T) { + expected := &Float32NonInterleaved{ + Data: [][]float32{ + { + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x01, 0x02, 0x03, 0x04})), + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x05, 0x06, 0x07, 0x08})), + }, + { + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x09, 0x0a, 0x0b, 0x0c})), + math.Float32frombits(binary.LittleEndian.Uint32([]byte{0x0d, 0x0e, 0x0f, 0x10})), + }, + }, + Size: ChunkInfo{ + Len: 2, + Channels: 2, + }, + } + actual, err := decodeFloat32NonInterleaved(binary.LittleEndian, raw, 2) + if err != nil { + t.Fatal(err) + } + + if !reflect.DeepEqual(expected, actual) { + t.Errorf("Wrong decode result,\nexpected:\n%+v\ngot:\n%+v", expected, actual) + } + }) +}