From 56af3f81594bb0e3735c390c8aa37276ff8d882d Mon Sep 17 00:00:00 2001 From: Joel Wetzell Date: Mon, 27 Jan 2025 18:41:44 -0600 Subject: [PATCH] add basic working example --- bmip10.go | 120 ++++++++++++++++++++++++++++++++++++++ bmip10_test.go | 45 ++++++++++++++ examples/decode/decode.go | 21 +++++++ examples/encode/encode.go | 18 ++++++ go.mod | 3 + 5 files changed, 207 insertions(+) create mode 100644 bmip10.go create mode 100644 bmip10_test.go create mode 100644 examples/decode/decode.go create mode 100644 examples/encode/encode.go create mode 100644 go.mod diff --git a/bmip10.go b/bmip10.go new file mode 100644 index 0000000..edd05f3 --- /dev/null +++ b/bmip10.go @@ -0,0 +1,120 @@ +package bmip10 + +type Config struct { + SampleStates uint32 + CodedStates uint32 + LossyCodeWidth uint32 + LossyRounding uint32 + LosslessCodes uint32 + LossyCodes uint32 + CodeTables uint32 + DefaultTable uint32 + LowestTableThresh uint32 + HighestTableThresh uint32 + LossyFlag uint32 +} + +func SetupBMIP10(sampleBits uint32, codedBits uint32) *Config { + config := Config{} + config.SampleStates = 1 << sampleBits + config.CodedStates = 1 << codedBits + config.LossyCodeWidth = (1 << (sampleBits - codedBits + 1)) - 1 + config.LossyRounding = config.LossyCodeWidth / 2 + config.LosslessCodes = config.CodedStates / 2 + config.LossyCodes = config.CodedStates / 2 + config.CodeTables = 1 + config.CodedStates/2 + config.DefaultTable = config.CodeTables / 2 + config.LowestTableThresh = config.LosslessCodes / 2 + config.HighestTableThresh = config.SampleStates - (config.LosslessCodes / 2) + config.LossyFlag = (1 << codedBits) / 2 + + return &config +} + +// Use the decoded sample to select the next table to use +func NextTable(config *Config, sample int32) int32 { + if sample < int32(config.LowestTableThresh) { + return 0 + } else if sample >= int32(config.HighestTableThresh) { + return int32(config.LossyCodes) + } else { + return (sample - int32(config.LowestTableThresh) + int32(config.LossyRounding)) / int32(config.LossyCodeWidth) + } +} + +// For a given sample and code table calculate the code word +func EncodeSample(config *Config, table int32, sample int32) int32 { + lossless_low := table * int32(config.LossyCodeWidth) + lossless_high := lossless_low + int32(config.LosslessCodes) + + if sample >= lossless_low && sample < lossless_high { + return sample - lossless_low + } else if sample < lossless_low { + + return int32(config.LossyFlag) | (sample / int32(config.LossyCodeWidth)) + } else { + return int32(config.LossyFlag) | ((sample-lossless_high)/int32(config.LossyCodeWidth) + table) + } +} + +// For a given code word and code table decode the sample +func DecodeSample(config *Config, table int32, code_word int32) int32 { + index := code_word & ^int32(config.LossyFlag) + if (code_word & int32(config.LossyFlag)) != 0 { + if index < table { + return index*int32(config.LossyCodeWidth) + int32(config.LossyRounding) + } else { + return index*int32(config.LossyCodeWidth) + int32(config.LossyRounding) + int32(config.LosslessCodes) + } + } else { + + return table*int32(config.LossyCodeWidth) + index + } +} + +type Encoder struct { + Config *Config + Table int32 +} + +func NewEncoder(sampleBits uint32, codedBits uint32) *Encoder { + config := SetupBMIP10(sampleBits, codedBits) + return &Encoder{ + Config: config, + Table: int32(config.DefaultTable), + } +} + +func (e *Encoder) Encode(sample int32) int32 { + code_word := EncodeSample(e.Config, e.Table, sample) + decoded := DecodeSample(e.Config, e.Table, code_word) + e.Table = NextTable(e.Config, decoded) + return code_word +} + +func (e *Encoder) Reset(sample int32) { + e.Table = int32(e.Config.DefaultTable) +} + +type Decoder struct { + Config *Config + Table int32 +} + +func NewDecoder(sampleBits uint32, codedBits uint32) *Decoder { + config := SetupBMIP10(sampleBits, codedBits) + return &Decoder{ + Config: config, + Table: int32(config.DefaultTable), + } +} + +func (d *Decoder) Decode(code_word int32) int32 { + decoded := DecodeSample(d.Config, d.Table, code_word) + d.Table = NextTable(d.Config, decoded) + return decoded +} + +func (d *Decoder) Reset(sample int32) { + d.Table = int32(d.Config.DefaultTable) +} diff --git a/bmip10_test.go b/bmip10_test.go new file mode 100644 index 0000000..2c12789 --- /dev/null +++ b/bmip10_test.go @@ -0,0 +1,45 @@ +package bmip10 + +import ( + "fmt" + "reflect" + "testing" +) + +func TestBMIP10Setup(t *testing.T) { + var tests = []struct { + description string + sampleBits uint32 + codedBits uint32 + expected Config + }{ + { + description: "10bit samples 8bit code words", + sampleBits: 10, + codedBits: 8, + expected: Config{ + SampleStates: 1024, + CodedStates: 256, + LossyCodeWidth: 7, + LossyRounding: 3, + LosslessCodes: 128, + LossyCodes: 128, + CodeTables: 129, + DefaultTable: 64, + LowestTableThresh: 64, + HighestTableThresh: 960, + LossyFlag: 128, + }, + }, + } + + for _, testCase := range tests { + actual := SetupBMIP10(testCase.sampleBits, testCase.codedBits) + + if !reflect.DeepEqual(actual, &testCase.expected) { + t.Errorf("Test '%s' failed to setup config properly", testCase.description) + fmt.Printf("expected: %v\n", testCase.expected) + fmt.Printf("actual: %v\n", actual) + } + } +} diff --git a/examples/decode/decode.go b/examples/decode/decode.go new file mode 100644 index 0000000..d8512a3 --- /dev/null +++ b/examples/decode/decode.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + + bmip10 "github.com/jwetzell/bmip10-go" +) + +func main() { + + decoder := bmip10.NewDecoder(10, 8) + + encodedData := []int32{128, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 116, 143, 163, 196, 250} + + fmt.Printf("index\tenc\tdec\n") + + for i := 0; i < len(encodedData); i++ { + decoded := decoder.Decode(encodedData[i]) + fmt.Printf("%d\t%d\t%d\n", i, encodedData[i], decoded) + } +} diff --git a/examples/encode/encode.go b/examples/encode/encode.go new file mode 100644 index 0000000..5a447a6 --- /dev/null +++ b/examples/encode/encode.go @@ -0,0 +1,18 @@ +package main + +import ( + "fmt" + + bmip10 "github.com/jwetzell/bmip10-go" +) + +func main() { + var rawData []int32 = []int32{1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987} + encoder := bmip10.NewEncoder(10, 8) + + fmt.Printf("index\traw\tenc\n") + for i := 0; i < len(rawData); i++ { + encoded := encoder.Encode(rawData[i]) + fmt.Printf("%d\t%d\t%d\n", i, rawData[i], encoded) + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..30d66e1 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/jwetzell/bmip10-go + +go 1.23.1