diff --git a/int_buffer.go b/int_buffer.go index fccd1de..f321a65 100644 --- a/int_buffer.go +++ b/int_buffer.go @@ -32,39 +32,62 @@ func (buf *IntBuffer) AsFloatBuffer() *FloatBuffer { return newB } +// GetSourceBitDepth returns buf.SourceBitDepth if populated, otherwise returns an estimate +// of the source bit depth based on the range of integer values contained in the buffer. +func (buf *IntBuffer) GetSourceBitDepth() int { + if buf == nil { + return 0 + } + if buf.SourceBitDepth != 0 { + return buf.SourceBitDepth + } + + max := int64(0) + min := int64(0) + for _, s := range buf.Data { + if int64(s) > max { + max = int64(s) + } else if int64(s) < min { + min = int64(s) + } + } + if -min > max { + max = -min + } + + // 8-bit PCM uses unsigned ints (bytes) + // Require max > 0 (vals in a silent 8-bit buffer should be ~128) + if min >= 0 && max > 0 && max <= 255 { + return 8 + } + for _, n := range []int{16, 24, 32} { + // max abs val of an n-bit signed int is 2^(n-1) + if max <= 1<<(n-1) { + return n + } + } + return 64 +} + // AsFloat32Buffer returns a copy of this buffer but with data converted to float 32. func (buf *IntBuffer) AsFloat32Buffer() *Float32Buffer { newB := &Float32Buffer{} newB.Data = make([]float32, len(buf.Data)) - max := int64(0) - // try to guess the bit depths without knowing the source - if buf.SourceBitDepth == 0 { - for _, s := range buf.Data { - if int64(s) > max { - max = int64(s) - } - } - buf.SourceBitDepth = 8 - if max > 127 { - buf.SourceBitDepth = 16 - } - // greater than int16, expecting int24 - if max > 32767 { - buf.SourceBitDepth = 24 - } - // int 32 - if max > 8388607 { - buf.SourceBitDepth = 32 + newB.SourceBitDepth = buf.GetSourceBitDepth() + var toFloat func(int) float32 + if newB.SourceBitDepth == 8 { + // 8-bit uses unsigned ints + toFloat = func(d int) float32 { + return float32(d)/255*2 - 1 } - // int 64 - if max > 4294967295 { - buf.SourceBitDepth = 64 + } else { + factor := 1.0 / math.Pow(2, float64(newB.SourceBitDepth-1)) + toFloat = func(d int) float32 { + return float32(float64(d) * factor) } } - newB.SourceBitDepth = buf.SourceBitDepth - factor := math.Pow(2, float64(buf.SourceBitDepth)-1) for i := 0; i < len(buf.Data); i++ { - newB.Data[i] = float32(float64(buf.Data[i]) / factor) + newB.Data[i] = toFloat(buf.Data[i]) } newB.Format = &Format{ NumChannels: buf.Format.NumChannels, diff --git a/int_buffer_test.go b/int_buffer_test.go index 2133858..3317d1f 100644 --- a/int_buffer_test.go +++ b/int_buffer_test.go @@ -6,17 +6,19 @@ import ( func TestIntBuffer_AsFloat32Buffer(t *testing.T) { type fields struct { - Range int + Range []int SourceBitDepth int } tests := []struct { name string fields fields }{ - {name: "16bit range", - fields: fields{Range: int(int16(1<<15 - 1)), SourceBitDepth: 16}}, - {name: "24bit range", - fields: fields{Range: int(int32(1 << 23)), SourceBitDepth: 24}}, + {name: "8bit range", // [0, 255] + fields: fields{Range: []int{0, 1<<8 - 1}, SourceBitDepth: 8}}, + {name: "16bit range", // [-32768, 32767] + fields: fields{Range: []int{-1<<15, 1<<15 - 1}, SourceBitDepth: 16}}, + {name: "24bit range", // [-8388608, 8388607] + fields: fields{Range: []int{-1<<23, 1<<23 - 1}, SourceBitDepth: 24}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -25,9 +27,9 @@ func TestIntBuffer_AsFloat32Buffer(t *testing.T) { SourceBitDepth: tt.fields.SourceBitDepth, } intData := []int{ - -tt.fields.Range, + tt.fields.Range[0], 0, - tt.fields.Range, + tt.fields.Range[1], } buf.Data = intData got := buf.AsFloat32Buffer() @@ -39,3 +41,40 @@ func TestIntBuffer_AsFloat32Buffer(t *testing.T) { }) } } + +func TestIntBuffer_GetSourceBitDepth(t *testing.T) { + type fields struct { + Range []int + SourceBitDepth int + } + tests := []struct { + name string + fields fields + }{ + {name: "empty buf", + fields: fields{Range: []int{0, 0}, SourceBitDepth: 16}}, + {name: "signed byte", + fields: fields{Range: []int{-128, 127}, SourceBitDepth: 16}}, + {name: "8bit range", // [0, 255] + fields: fields{Range: []int{0, 1<<8 - 1}, SourceBitDepth: 8}}, + {name: "16bit range", // [-32768, 32767] + fields: fields{Range: []int{-1<<15, 1<<15 - 1}, SourceBitDepth: 16}}, + {name: "24bit range", // [-8388608, 8388607] + fields: fields{Range: []int{-1<<23, 1<<23 - 1}, SourceBitDepth: 24}}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + buf := &IntBuffer{} + intData := []int{ + tt.fields.Range[0], + 0, + tt.fields.Range[1], + } + buf.Data = intData + got := buf.GetSourceBitDepth() + if got != tt.fields.SourceBitDepth { + t.Errorf("%d was misestimated as %d", tt.fields.SourceBitDepth, got) + } + }) + } +}