add basic working example

This commit is contained in:
2025-01-27 18:41:44 -06:00
commit 56af3f8159
5 changed files with 207 additions and 0 deletions
+120
View File
@@ -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)
}
+45
View File
@@ -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)
}
}
}
+21
View File
@@ -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)
}
}
+18
View File
@@ -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)
}
}
+3
View File
@@ -0,0 +1,3 @@
module github.com/jwetzell/bmip10-go
go 1.23.1