From c5189e98247433cd90fd0f515dfa08f6b252fa3e Mon Sep 17 00:00:00 2001 From: Chad Wagner Date: Fri, 4 Aug 2023 07:23:00 -0700 Subject: [PATCH 1/5] handle uint8 to float conversion --- int_buffer.go | 23 ++++++++++++++++++++--- int_buffer_test.go | 16 +++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/int_buffer.go b/int_buffer.go index fccd1de..c86665a 100644 --- a/int_buffer.go +++ b/int_buffer.go @@ -37,15 +37,21 @@ func (buf *IntBuffer) AsFloat32Buffer() *Float32Buffer { newB := &Float32Buffer{} newB.Data = make([]float32, len(buf.Data)) max := int64(0) + min := 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) + } else if int64(s) < min { + min = int64(s) } } + if -min > max { + max = -min + } buf.SourceBitDepth = 8 - if max > 127 { + if max > 255 || min < 0 { buf.SourceBitDepth = 16 } // greater than int16, expecting int24 @@ -62,9 +68,20 @@ func (buf *IntBuffer) AsFloat32Buffer() *Float32Buffer { } } newB.SourceBitDepth = buf.SourceBitDepth - factor := math.Pow(2, float64(buf.SourceBitDepth)-1) + var toFloat func(int) float32 + if buf.SourceBitDepth == 8 { + // 8-bit uses unsigned ints + toFloat = func(d int) float32 { + return float32(d)/255*2 - 1 + } + } else { + factor := 1.0 / math.Pow(2, float64(buf.SourceBitDepth-1)) + toFloat = func(d int) float32 { + return float32(float64(d) * factor) + } + } 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..9dd2bd5 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() From 81e479156e2cd636e42d4db9bd902df8a6b515ea Mon Sep 17 00:00:00 2001 From: Chad Wagner Date: Fri, 4 Aug 2023 08:01:15 -0700 Subject: [PATCH 2/5] adjust max ranges --- int_buffer.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/int_buffer.go b/int_buffer.go index c86665a..2cf8536 100644 --- a/int_buffer.go +++ b/int_buffer.go @@ -55,15 +55,15 @@ func (buf *IntBuffer) AsFloat32Buffer() *Float32Buffer { buf.SourceBitDepth = 16 } // greater than int16, expecting int24 - if max > 32767 { + if max > 1<<15 { buf.SourceBitDepth = 24 } // int 32 - if max > 8388607 { + if max > 1<<23 { buf.SourceBitDepth = 32 } // int 64 - if max > 4294967295 { + if max > 1<<31 { buf.SourceBitDepth = 64 } } From d14f91ed5ee5fb6e00f41c8c72e7d6b6e4d4b51d Mon Sep 17 00:00:00 2001 From: Chad Wagner Date: Fri, 4 Aug 2023 10:24:34 -0700 Subject: [PATCH 3/5] clean up bit depth estimate --- int_buffer.go | 68 ++++++++++++++++++++++++++------------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/int_buffer.go b/int_buffer.go index 2cf8536..51ea310 100644 --- a/int_buffer.go +++ b/int_buffer.go @@ -32,50 +32,52 @@ func (buf *IntBuffer) AsFloatBuffer() *FloatBuffer { return newB } -// 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)) +// 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.SourceBitDepth != 0 { + return buf.SourceBitDepth + } + max := int64(0) min := 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) - } else if int64(s) < min { - min = int64(s) - } - } - if -min > max { - max = -min - } - buf.SourceBitDepth = 8 - if max > 255 || min < 0 { - buf.SourceBitDepth = 16 - } - // greater than int16, expecting int24 - if max > 1<<15 { - buf.SourceBitDepth = 24 - } - // int 32 - if max > 1<<23 { - buf.SourceBitDepth = 32 + for _, s := range buf.Data { + if int64(s) > max { + max = int64(s) + } else if int64(s) < min { + min = int64(s) } - // int 64 - if max > 1<<31 { - buf.SourceBitDepth = 64 + } + if -min > max { + max = -min + } + + // 8-bit PCM uses unsigned ints (bytes) + if min >= 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 } } - newB.SourceBitDepth = buf.SourceBitDepth + 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)) + newB.SourceBitDepth = buf.GetSourceBitDepth() var toFloat func(int) float32 - if buf.SourceBitDepth == 8 { + if newB.SourceBitDepth == 8 { // 8-bit uses unsigned ints toFloat = func(d int) float32 { return float32(d)/255*2 - 1 } } else { - factor := 1.0 / math.Pow(2, float64(buf.SourceBitDepth-1)) + factor := 1.0 / math.Pow(2, float64(newB.SourceBitDepth-1)) toFloat = func(d int) float32 { return float32(float64(d) * factor) } From 2eb9e8c2bac6e897aa2f37b56c17280b6dfe971b Mon Sep 17 00:00:00 2001 From: Chad Wagner Date: Fri, 4 Aug 2023 10:56:53 -0700 Subject: [PATCH 4/5] add test for bit depth estimate --- int_buffer.go | 3 ++- int_buffer_test.go | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/int_buffer.go b/int_buffer.go index 51ea310..7898738 100644 --- a/int_buffer.go +++ b/int_buffer.go @@ -53,7 +53,8 @@ func (buf *IntBuffer) GetSourceBitDepth() int { } // 8-bit PCM uses unsigned ints (bytes) - if min >= 0 && max <= 255 { + // 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} { diff --git a/int_buffer_test.go b/int_buffer_test.go index 9dd2bd5..3317d1f 100644 --- a/int_buffer_test.go +++ b/int_buffer_test.go @@ -41,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) + } + }) + } +} From 65cb5c19bcd76223fde728f52a7dd01b8a356532 Mon Sep 17 00:00:00 2001 From: Chad Wagner Date: Fri, 4 Aug 2023 10:58:47 -0700 Subject: [PATCH 5/5] nil check --- int_buffer.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/int_buffer.go b/int_buffer.go index 7898738..f321a65 100644 --- a/int_buffer.go +++ b/int_buffer.go @@ -35,6 +35,9 @@ func (buf *IntBuffer) AsFloatBuffer() *FloatBuffer { // 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 }