Define basic audio data interface

This commit is contained in:
Atsushi Watanabe
2020-04-18 19:26:43 +09:00
committed by Lukas Herman
parent edc2ada757
commit 450f882c50
6 changed files with 384 additions and 0 deletions

80
pkg/wave/float32.go Normal file
View File

@@ -0,0 +1,80 @@
package wave
// Float32Sample is a 32-bits float audio sample.
type Float32Sample float32
func (s Float32Sample) Int() int64 {
return int64(s * 0x100000000)
}
// Float32Interleaved multi-channel interlaced Audio.
type Float32Interleaved struct {
Data []float32
Size ChunkInfo
}
// ChunkInfo returns audio chunk size.
func (a *Float32Interleaved) ChunkInfo() ChunkInfo {
return a.Size
}
func (a *Float32Interleaved) SampleFormat() SampleFormat {
return Float32SampleFormat
}
func (a *Float32Interleaved) At(i, ch int) Sample {
return Float32Sample(a.Data[i*a.Size.Channels+ch])
}
func (a *Float32Interleaved) Set(i, ch int, s Sample) {
a.Data[i*a.Size.Channels+ch] = float32(Float32SampleFormat.Convert(s).(Float32Sample))
}
func (a *Float32Interleaved) SetFloat32(i, ch int, s Float32Sample) {
a.Data[i*a.Size.Channels+ch] = float32(s)
}
func NewFloat32Interleaved(size ChunkInfo) *Float32Interleaved {
return &Float32Interleaved{
Data: make([]float32, size.Channels*size.Len),
Size: size,
}
}
// Float32NonInterleaved multi-channel interlaced Audio.
type Float32NonInterleaved struct {
Data [][]float32
Size ChunkInfo
}
// ChunkInfo returns audio chunk size.
func (a *Float32NonInterleaved) ChunkInfo() ChunkInfo {
return a.Size
}
func (a *Float32NonInterleaved) SampleFormat() SampleFormat {
return Float32SampleFormat
}
func (a *Float32NonInterleaved) At(i, ch int) Sample {
return Float32Sample(a.Data[ch][i])
}
func (a *Float32NonInterleaved) Set(i, ch int, s Sample) {
a.Data[ch][i] = float32(Float32SampleFormat.Convert(s).(Float32Sample))
}
func (a *Float32NonInterleaved) SetFloat32(i, ch int, s Float32Sample) {
a.Data[ch][i] = float32(s)
}
func NewFloat32NonInterleaved(size ChunkInfo) *Float32NonInterleaved {
d := make([][]float32, size.Channels)
for i := 0; i < size.Channels; i++ {
d[i] = make([]float32, size.Len)
}
return &Float32NonInterleaved{
Data: d,
Size: size,
}
}

53
pkg/wave/float32_test.go Normal file
View File

@@ -0,0 +1,53 @@
package wave
import (
"reflect"
"testing"
)
func TestFloat32(t *testing.T) {
cases := map[string]struct {
in Audio
expected [][]float32
}{
"Interleaved": {
in: &Float32Interleaved{
Data: []float32{
0.1, -0.5, 0.2, -0.6, 0.3, -0.7, 0.4, -0.8, 0.5, -0.9, 0.6, -1.0, 0.7, -1.1, 0.8, -1.2,
},
Size: ChunkInfo{8, 2, 48000},
},
expected: [][]float32{
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
},
},
"NonInterleaved": {
in: &Float32NonInterleaved{
Data: [][]float32{
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
},
Size: ChunkInfo{8, 2, 48000},
},
expected: [][]float32{
{0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8},
{-0.5, -0.6, -0.7, -0.8, -0.9, -1.0, -1.1, -1.2},
},
},
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
out := make([][]float32, c.in.ChunkInfo().Channels)
for i := 0; i < c.in.ChunkInfo().Channels; i++ {
for j := 0; j < c.in.ChunkInfo().Len; j++ {
out[i] = append(out[i], float32(c.in.At(j, i).(Float32Sample)))
}
}
if !reflect.DeepEqual(c.expected, out) {
t.Errorf("Sample level differs, expected: %v, got: %v", c.expected, out)
}
})
}
}

80
pkg/wave/int16.go Normal file
View File

@@ -0,0 +1,80 @@
package wave
// Int16Sample is a 16-bits signed integer audio sample.
type Int16Sample int16
func (s Int16Sample) Int() int64 {
return int64(s) << 16
}
// Int16Interleaved multi-channel interlaced Audio.
type Int16Interleaved struct {
Data []int16
Size ChunkInfo
}
// ChunkInfo returns audio chunk size.
func (a *Int16Interleaved) ChunkInfo() ChunkInfo {
return a.Size
}
func (a *Int16Interleaved) SampleFormat() SampleFormat {
return Int16SampleFormat
}
func (a *Int16Interleaved) At(i, ch int) Sample {
return Int16Sample(a.Data[i*a.Size.Channels+ch])
}
func (a *Int16Interleaved) Set(i, ch int, s Sample) {
a.Data[i*a.Size.Channels+ch] = int16(Int16SampleFormat.Convert(s).(Int16Sample))
}
func (a *Int16Interleaved) SetInt16(i, ch int, s Int16Sample) {
a.Data[i*a.Size.Channels+ch] = int16(s)
}
func NewInt16Interleaved(size ChunkInfo) *Int16Interleaved {
return &Int16Interleaved{
Data: make([]int16, size.Channels*size.Len),
Size: size,
}
}
// Int16NonInterleaved multi-channel interlaced Audio.
type Int16NonInterleaved struct {
Data [][]int16
Size ChunkInfo
}
// ChunkInfo returns audio chunk size.
func (a *Int16NonInterleaved) ChunkInfo() ChunkInfo {
return a.Size
}
func (a *Int16NonInterleaved) SampleFormat() SampleFormat {
return Int16SampleFormat
}
func (a *Int16NonInterleaved) At(i, ch int) Sample {
return Int16Sample(a.Data[ch][i])
}
func (a *Int16NonInterleaved) Set(i, ch int, s Sample) {
a.Data[ch][i] = int16(Int16SampleFormat.Convert(s).(Int16Sample))
}
func (a *Int16NonInterleaved) SetInt16(i, ch int, s Int16Sample) {
a.Data[ch][i] = int16(s)
}
func NewInt16NonInterleaved(size ChunkInfo) *Int16NonInterleaved {
d := make([][]int16, size.Channels)
for i := 0; i < size.Channels; i++ {
d[i] = make([]int16, size.Len)
}
return &Int16NonInterleaved{
Data: d,
Size: size,
}
}

53
pkg/wave/int16_test.go Normal file
View File

@@ -0,0 +1,53 @@
package wave
import (
"reflect"
"testing"
)
func TestInt16(t *testing.T) {
cases := map[string]struct {
in Audio
expected [][]int16
}{
"Interleaved": {
in: &Int16Interleaved{
Data: []int16{
1, -5, 2, -6, 3, -7, 4, -8, 5, -9, 6, -10, 7, -11, 8, -12,
},
Size: ChunkInfo{8, 2, 48000},
},
expected: [][]int16{
{1, 2, 3, 4, 5, 6, 7, 8},
{-5, -6, -7, -8, -9, -10, -11, -12},
},
},
"NonInterleaved": {
in: &Int16NonInterleaved{
Data: [][]int16{
{1, 2, 3, 4, 5, 6, 7, 8},
{-5, -6, -7, -8, -9, -10, -11, -12},
},
Size: ChunkInfo{8, 2, 48000},
},
expected: [][]int16{
{1, 2, 3, 4, 5, 6, 7, 8},
{-5, -6, -7, -8, -9, -10, -11, -12},
},
},
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
out := make([][]int16, c.in.ChunkInfo().Channels)
for i := 0; i < c.in.ChunkInfo().Channels; i++ {
for j := 0; j < c.in.ChunkInfo().Len; j++ {
out[i] = append(out[i], int16(c.in.At(j, i).(Int16Sample)))
}
}
if !reflect.DeepEqual(c.expected, out) {
t.Errorf("Sample level differs, expected: %v, got: %v", c.expected, out)
}
})
}
}

57
pkg/wave/wave.go Normal file
View File

@@ -0,0 +1,57 @@
// Package wave implements a basic audio data library.
package wave
// Audio is a finite series of audio Sample values.
type Audio interface {
SampleFormat() SampleFormat
ChunkInfo() ChunkInfo
At(i, ch int) Sample
}
// ChunkInfo contains size of the audio chunk.
type ChunkInfo struct {
Len int
Channels int
SamplingRate int
}
// SampleFormat can convert any Sample to one from its own sample format.
type SampleFormat interface {
Convert(c Sample) Sample
}
// SampleFormatFunc returns a SampleFormat that invokes f to implement the conversion.
func SampleFormatFunc(f func(Sample) Sample) SampleFormat {
return &sampleFormatFunc{f}
}
type sampleFormatFunc struct {
f func(Sample) Sample
}
func (f *sampleFormatFunc) Convert(s Sample) Sample {
return f.f(s)
}
// SampleFormats for the standard formats.
var (
Int16SampleFormat = SampleFormatFunc(func(s Sample) Sample {
if _, ok := s.(Int16Sample); ok {
return s
}
return Int16Sample(s.Int() >> 16)
})
Float32SampleFormat = SampleFormatFunc(func(s Sample) Sample {
if _, ok := s.(Float32Sample); ok {
return s
}
return Float32Sample(float32(s.Int()) / 0x100000000)
})
)
// Sample can convert itself to 64-bits signed value.
type Sample interface {
// Int returns the audio level value for the sample.
// A value ranges within [0, 0xffffffff], but is represented by a int64.
Int() int64
}

61
pkg/wave/wave_test.go Normal file
View File

@@ -0,0 +1,61 @@
package wave
import (
"math"
"reflect"
"testing"
)
func TestConvert(t *testing.T) {
cases := map[string]struct {
in []Sample
typ SampleFormat
expected []Sample
}{
"Int16ToFloat32": {
in: []Sample{
Int16Sample(-0x1000),
Int16Sample(-0x100),
Int16Sample(0x0),
Int16Sample(0x100),
Int16Sample(0x1000),
},
typ: Float32SampleFormat,
expected: []Sample{
Float32Sample(-math.Pow(2, -4)),
Float32Sample(-math.Pow(2, -8)),
Float32Sample(0.0),
Float32Sample(math.Pow(2, -8)),
Float32Sample(math.Pow(2, -4)),
},
},
"Float32ToInt16": {
in: []Sample{
Float32Sample(-math.Pow(2, -4)),
Float32Sample(-math.Pow(2, -8)),
Float32Sample(0.0),
Float32Sample(math.Pow(2, -8)),
Float32Sample(math.Pow(2, -4)),
},
typ: Int16SampleFormat,
expected: []Sample{
Int16Sample(-0x1000),
Int16Sample(-0x100),
Int16Sample(0x0),
Int16Sample(0x100),
Int16Sample(0x1000),
},
},
}
for name, c := range cases {
c := c
t.Run(name, func(t *testing.T) {
for i := range c.in {
s := c.typ.Convert(c.in[i])
if !reflect.DeepEqual(c.expected[i], s) {
t.Errorf("Convert result differs, expected: %v, got: %v", c.expected[i], s)
}
}
})
}
}