From 70d69f55993f4b0e8d7e0213f2140b6d3e9d32cc Mon Sep 17 00:00:00 2001 From: onyx-and-iris <75868496+onyx-and-iris@users.noreply.github.com> Date: Sat, 9 Jul 2022 19:01:58 +0100 Subject: [PATCH] package module moved into root of repository. example in readme updated. level pooler implemented, runs in its own goroutine. Remote type now exported observers example updated. --- README.md | 2 +- voicemeeter/base.go => base.go | 15 ++++++ voicemeeter/bus.go => bus.go | 4 +- voicemeeter/bus_test.go => bus_test.go | 0 voicemeeter/button.go => button.go | 0 voicemeeter/button_test.go => button_test.go | 0 voicemeeter/command.go => command.go | 0 .../command_test.go => command_test.go | 0 voicemeeter/device.go => device.go | 0 voicemeeter/device_test.go => device_test.go | 0 examples/observer/observer.go | 42 +++++++++------ go.mod | 4 +- go.sum | 8 +-- voicemeeter/iremote.go => iremote.go | 16 ------ voicemeeter/kinds.go => kinds.go | 0 voicemeeter/kinds_test.go => kinds_test.go | 0 levels.go | 45 ++++++++++++++++ voicemeeter/outputs.go => outputs.go | 0 .../outputs_test.go => outputs_test.go | 0 voicemeeter/path.go => path.go | 0 voicemeeter/path_test.go => path_test.go | 0 voicemeeter/publisher.go => publisher.go | 18 ++++++- voicemeeter/recorder.go => recorder.go | 0 .../recorder_test.go => recorder_test.go | 0 voicemeeter/remote.go => remote.go | 53 +++++++++---------- voicemeeter/remote_test.go => remote_test.go | 0 voicemeeter/strip.go => strip.go | 21 ++++++-- voicemeeter/strip_test.go => strip_test.go | 0 tests/helper_test.go | 2 +- util.go | 24 +++++++++ voicemeeter/vban.go => vban.go | 0 voicemeeter/vban_test.go => vban_test.go | 0 32 files changed, 179 insertions(+), 75 deletions(-) rename voicemeeter/base.go => base.go (91%) rename voicemeeter/bus.go => bus.go (98%) rename voicemeeter/bus_test.go => bus_test.go (100%) rename voicemeeter/button.go => button.go (100%) rename voicemeeter/button_test.go => button_test.go (100%) rename voicemeeter/command.go => command.go (100%) rename voicemeeter/command_test.go => command_test.go (100%) rename voicemeeter/device.go => device.go (100%) rename voicemeeter/device_test.go => device_test.go (100%) rename voicemeeter/iremote.go => iremote.go (89%) rename voicemeeter/kinds.go => kinds.go (100%) rename voicemeeter/kinds_test.go => kinds_test.go (100%) create mode 100644 levels.go rename voicemeeter/outputs.go => outputs.go (100%) rename voicemeeter/outputs_test.go => outputs_test.go (100%) rename voicemeeter/path.go => path.go (100%) rename voicemeeter/path_test.go => path_test.go (100%) rename voicemeeter/publisher.go => publisher.go (75%) rename voicemeeter/recorder.go => recorder.go (100%) rename voicemeeter/recorder_test.go => recorder_test.go (100%) rename voicemeeter/remote.go => remote.go (80%) rename voicemeeter/remote_test.go => remote_test.go (100%) rename voicemeeter/strip.go => strip.go (92%) rename voicemeeter/strip_test.go => strip_test.go (100%) create mode 100644 util.go rename voicemeeter/vban.go => vban.go (100%) rename voicemeeter/vban_test.go => vban_test.go (100%) diff --git a/README.md b/README.md index 0179a8d..ef3065d 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ package main import ( "fmt" - "github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter" + "github.com/onyx-and-iris/voicemeeter-api-go" ) func main() { diff --git a/voicemeeter/base.go b/base.go similarity index 91% rename from voicemeeter/base.go rename to base.go index 29ac477..48801e9 100644 --- a/voicemeeter/base.go +++ b/base.go @@ -112,6 +112,21 @@ func mdirty() bool { return int(res) == 1 } +func ldirty(k *kind) bool { + _levelCache.stripLevelsBuff = make([]float32, (2*k.physIn)+(8*k.virtIn)) + _levelCache.busLevelsBuff = make([]float32, 8*k.numBus()) + + for i := 0; i < (2*k.physIn)+(8*k.virtIn); i++ { + _levelCache.stripLevelsBuff[i] = float32(getLevel(_levelCache.stripMode, i)) + _levelCache.stripComp[i] = _levelCache.stripLevelsBuff[i] == _levelCache.stripLevels[i] + } + for i := 0; i < 8*k.numBus(); i++ { + _levelCache.busLevelsBuff[i] = float32(getLevel(3, i)) + _levelCache.busComp[i] = _levelCache.busLevelsBuff[i] == _levelCache.busLevels[i] + } + return !(allTrue(_levelCache.stripComp, (2*k.physIn)+(8*k.virtIn)) && allTrue(_levelCache.busComp, 8*k.numBus())) +} + // getVMType returns the type of Voicemeeter, as a string func getVMType() string { var type_ uint64 diff --git a/voicemeeter/bus.go b/bus.go similarity index 98% rename from voicemeeter/bus.go rename to bus.go index 9b24b86..1f2bfbe 100644 --- a/voicemeeter/bus.go +++ b/bus.go @@ -253,13 +253,13 @@ func (bm *busMode) GetRearOnly() bool { func newBusLevels(i int, k *kind) levels { init := i * 8 - return levels{iRemote{fmt.Sprintf("bus[%d]", i), i}, k, init, 8} + return levels{iRemote{fmt.Sprintf("bus[%d]", i), i}, k, init, 8, "bus"} } func (l *levels) All() []float32 { var levels []float32 for i := l.init; i < l.init+l.offset; i++ { - levels = append(levels, l.convertLevel(getLevel(3, i))) + levels = append(levels, l.convertLevel(_levelCache.busLevels[i])) } return levels } diff --git a/voicemeeter/bus_test.go b/bus_test.go similarity index 100% rename from voicemeeter/bus_test.go rename to bus_test.go diff --git a/voicemeeter/button.go b/button.go similarity index 100% rename from voicemeeter/button.go rename to button.go diff --git a/voicemeeter/button_test.go b/button_test.go similarity index 100% rename from voicemeeter/button_test.go rename to button_test.go diff --git a/voicemeeter/command.go b/command.go similarity index 100% rename from voicemeeter/command.go rename to command.go diff --git a/voicemeeter/command_test.go b/command_test.go similarity index 100% rename from voicemeeter/command_test.go rename to command_test.go diff --git a/voicemeeter/device.go b/device.go similarity index 100% rename from voicemeeter/device.go rename to device.go diff --git a/voicemeeter/device_test.go b/device_test.go similarity index 100% rename from voicemeeter/device_test.go rename to device_test.go diff --git a/examples/observer/observer.go b/examples/observer/observer.go index 887abf9..48eca0e 100644 --- a/examples/observer/observer.go +++ b/examples/observer/observer.go @@ -2,37 +2,45 @@ package main import ( "fmt" + "strings" "time" - "github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter" + "github.com/onyx-and-iris/voicemeeter-api-go" ) type observer struct { - i int + vm *voicemeeter.Remote } func (o observer) OnUpdate(subject string) { - fmt.Println(o.i, subject) + if strings.Compare(subject, "pdirty") == 0 { + fmt.Println("pdirty!") + } + if strings.Compare(subject, "mdirty") == 0 { + fmt.Println("mdirty!") + } + if strings.Compare(subject, "ldirty") == 0 { + fmt.Printf("%v %v %v %v %v %v %v %v\n", + o.vm.Bus[0].Levels().IsDirty(), + o.vm.Bus[1].Levels().IsDirty(), + o.vm.Bus[2].Levels().IsDirty(), + o.vm.Bus[3].Levels().IsDirty(), + o.vm.Bus[4].Levels().IsDirty(), + o.vm.Bus[5].Levels().IsDirty(), + o.vm.Bus[6].Levels().IsDirty(), + o.vm.Bus[7].Levels().IsDirty(), + ) + } } func main() { - vmRem := voicemeeter.GetRemote("banana") + vmRem := voicemeeter.GetRemote("potato") vmRem.Login() - o := observer{1} - o2 := observer{2} - o3 := observer{3} - o4 := observer{4} + o := observer{vmRem} vmRem.Register(o) - vmRem.Register(o2) - vmRem.Register(o3) - vmRem.Register(o4) - - time.Sleep(5 * time.Second) - - vmRem.Deregister(o2) - - time.Sleep(5 * time.Second) + time.Sleep(30 * time.Second) + vmRem.Deregister(o) vmRem.Logout() } diff --git a/go.mod b/go.mod index 839504e..ff32be2 100644 --- a/go.mod +++ b/go.mod @@ -3,8 +3,8 @@ module github.com/onyx-and-iris/voicemeeter-api-go go 1.18 require ( - github.com/stretchr/testify v1.7.5 - golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 + github.com/stretchr/testify v1.8.0 + golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d ) require ( diff --git a/go.sum b/go.sum index 949a86e..34f7d2f 100644 --- a/go.sum +++ b/go.sum @@ -6,10 +6,10 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= -golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I= +golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/voicemeeter/iremote.go b/iremote.go similarity index 89% rename from voicemeeter/iremote.go rename to iremote.go index 96e4aec..ff69873 100644 --- a/voicemeeter/iremote.go +++ b/iremote.go @@ -2,7 +2,6 @@ package voicemeeter import ( "fmt" - "math" ) // iRemote provides a set of common forwarding methods @@ -68,18 +67,3 @@ func (ir *iRemote) setter_string(p, v string) { param := fmt.Sprintf("%s.%s", ir.identifier(), p) setParameterString(param, v) } - -type levels struct { - iRemote - k *kind - init int - offset int -} - -func (l *levels) convertLevel(i float32) float32 { - if i > 0 { - val := 20 * math.Log10(float64(i)) - return float32(val) - } - return -200.0 -} diff --git a/voicemeeter/kinds.go b/kinds.go similarity index 100% rename from voicemeeter/kinds.go rename to kinds.go diff --git a/voicemeeter/kinds_test.go b/kinds_test.go similarity index 100% rename from voicemeeter/kinds_test.go rename to kinds_test.go diff --git a/levels.go b/levels.go new file mode 100644 index 0000000..3d440b5 --- /dev/null +++ b/levels.go @@ -0,0 +1,45 @@ +package voicemeeter + +import "math" + +// levels +type levels struct { + iRemote + k *kind + init int + offset int + id string +} + +func (l *levels) convertLevel(i float32) float32 { + if i > 0 { + val := 20 * math.Log10(float64(i)) + return float32(roundFloat(float64(val), 1)) + } + return -200.0 +} + +var _levelCache *levelCache + +// levelCache defines level slices used by the pooler to track updates +type levelCache struct { + stripMode int + stripLevels []float32 + busLevels []float32 + stripLevelsBuff []float32 + busLevelsBuff []float32 + stripComp []bool + busComp []bool +} + +// newLevelCache returns a levelCache struct address +func newLevelCache(k *kind) *levelCache { + stripLevels := make([]float32, (2*k.physIn)+(8*k.virtIn)) + busLevels := make([]float32, 8*k.numBus()) + stripComp := make([]bool, (2*k.physIn)+(8*k.virtIn)) + busComp := make([]bool, 8*k.numBus()) + if _levelCache == nil { + _levelCache = &levelCache{stripMode: 0, stripLevels: stripLevels, busLevels: busLevels, stripComp: stripComp, busComp: busComp} + } + return _levelCache +} diff --git a/voicemeeter/outputs.go b/outputs.go similarity index 100% rename from voicemeeter/outputs.go rename to outputs.go diff --git a/voicemeeter/outputs_test.go b/outputs_test.go similarity index 100% rename from voicemeeter/outputs_test.go rename to outputs_test.go diff --git a/voicemeeter/path.go b/path.go similarity index 100% rename from voicemeeter/path.go rename to path.go diff --git a/voicemeeter/path_test.go b/path_test.go similarity index 100% rename from voicemeeter/path_test.go rename to path_test.go diff --git a/voicemeeter/publisher.go b/publisher.go similarity index 75% rename from voicemeeter/publisher.go rename to publisher.go index e114c71..96f2c4a 100644 --- a/voicemeeter/publisher.go +++ b/publisher.go @@ -43,15 +43,18 @@ func (p *publisher) notify(subject string) { // pooler continuously polls the dirty paramters // it is expected to be run in a goroutine type pooler struct { + k *kind run bool publisher } -func newPooler() *pooler { +func newPooler(k *kind) *pooler { p := &pooler{ + k: k, run: true, } go p.runner() + go p.levels() return p } @@ -66,3 +69,16 @@ func (p *pooler) runner() { time.Sleep(33 * time.Millisecond) } } + +func (p *pooler) levels() { + _levelCache = newLevelCache(p.k) + + for p.run { + if ldirty(p.k) { + update(_levelCache.stripLevels, _levelCache.stripLevelsBuff, (2*p.k.physIn)+(8*p.k.virtIn)) + update(_levelCache.busLevels, _levelCache.busLevelsBuff, 8*p.k.numBus()) + p.notify("ldirty") + } + time.Sleep(33 * time.Millisecond) + } +} diff --git a/voicemeeter/recorder.go b/recorder.go similarity index 100% rename from voicemeeter/recorder.go rename to recorder.go diff --git a/voicemeeter/recorder_test.go b/recorder_test.go similarity index 100% rename from voicemeeter/recorder_test.go rename to recorder_test.go diff --git a/voicemeeter/remote.go b/remote.go similarity index 80% rename from voicemeeter/remote.go rename to remote.go index f8d8938..ce573ea 100644 --- a/voicemeeter/remote.go +++ b/remote.go @@ -5,9 +5,8 @@ import ( "os" ) -// A remote type represents the API for a kind, -// comprised of slices representing each member -type remote struct { +// A Remote type represents the API for a kind +type Remote struct { kind *kind Strip []t_strip Bus []t_bus @@ -21,53 +20,53 @@ type remote struct { } // String implements the fmt.stringer interface -func (r *remote) String() string { +func (r *Remote) String() string { return fmt.Sprintf("Voicemeeter %s", r.kind) } // Login logs into the API // then it intializes the pooler -func (r *remote) Login() { - r.pooler = newPooler() +func (r *Remote) Login() { login(r.kind.name) + r.pooler = newPooler(r.kind) } // Logout logs out of the API // it also terminates the pooler -func (r *remote) Logout() { +func (r *Remote) Logout() { r.pooler.run = false logout() } -func (r *remote) Type() string { +func (r *Remote) Type() string { return getVMType() } -func (r *remote) Version() string { +func (r *Remote) Version() string { return getVersion() } // Pdirty returns true iff a parameter value has changed -func (r *remote) Pdirty() bool { +func (r *Remote) Pdirty() bool { return pdirty() } // Mdirty returns true iff a macrobutton value has changed -func (r *remote) Mdirty() bool { +func (r *Remote) Mdirty() bool { return mdirty() } -func (r *remote) SendText(script string) { +func (r *Remote) SendText(script string) { setParametersMulti(script) } // Register forwards the register method to Pooler -func (r *remote) Register(o observer) { +func (r *Remote) Register(o observer) { r.pooler.Register(o) } // Register forwards the deregister method to Pooler -func (r *remote) Deregister(o observer) { +func (r *Remote) Deregister(o observer) { r.pooler.Deregister(o) } @@ -81,7 +80,7 @@ type remoteBuilder interface { makeDevice() remoteBuilder makeRecorder() remoteBuilder Build() remoteBuilder - Get() *remote + Get() *Remote } // directory is responsible for directing the genericBuilder @@ -100,13 +99,13 @@ func (d *director) Construct() { } // Get forwards the Get method to the builder -func (d *director) Get() *remote { +func (d *director) Get() *Remote { return d.builder.Get() } type genericBuilder struct { k *kind - r remote + r Remote } func (b *genericBuilder) setKind() remoteBuilder { @@ -157,28 +156,28 @@ func (b *genericBuilder) makeButton() remoteBuilder { return b } -// makeCommand makes a Command type and assigns it to remote.Command +// makeCommand makes a command type and assigns it to remote.Command func (b *genericBuilder) makeCommand() remoteBuilder { fmt.Println("building command") b.r.Command = newCommand() return b } -// makeVban makes a Vban type and assigns it to remote.Vban +// makeVban makes a vban type and assigns it to remote.Vban func (b *genericBuilder) makeVban() remoteBuilder { fmt.Println("building vban") b.r.Vban = newVban(b.k) return b } -// makeVban makes a Vban type and assigns it to remote.Vban +// makeDevice makes a device type and assigns it to remote.Device func (b *genericBuilder) makeDevice() remoteBuilder { fmt.Println("building device") b.r.Device = newDevice() return b } -// makeRecorder makes a recorder type and assigns it to remote.Vban +// makeRecorder makes a recorder type and assigns it to remote.Recorder func (b *genericBuilder) makeRecorder() remoteBuilder { fmt.Println("building recorder") b.r.Recorder = newRecorder() @@ -186,7 +185,7 @@ func (b *genericBuilder) makeRecorder() remoteBuilder { } // Get returns a fully constructed remote type for a kind -func (b *genericBuilder) Get() *remote { +func (b *genericBuilder) Get() *Remote { return &b.r } @@ -217,9 +216,9 @@ func (potb *potatoBuilder) Build() remoteBuilder { return potb.setKind().makeStrip().makeBus().makeButton().makeCommand().makeVban().makeDevice().makeRecorder() } -// GetRemote returns a remote type for a kind +// GetRemote returns a Remote type for a kind // this is the interface entry point -func GetRemote(kindId string) *remote { +func GetRemote(kindId string) *Remote { _kind, ok := kindMap[kindId] if !ok { err := fmt.Errorf("unknown Voicemeeter kind '%s'", kindId) @@ -230,11 +229,11 @@ func GetRemote(kindId string) *remote { director := director{} switch _kind.name { case "basic": - director.SetBuilder(&basicBuilder{genericBuilder{_kind, remote{}}}) + director.SetBuilder(&basicBuilder{genericBuilder{_kind, Remote{}}}) case "banana": - director.SetBuilder(&bananaBuilder{genericBuilder{_kind, remote{}}}) + director.SetBuilder(&bananaBuilder{genericBuilder{_kind, Remote{}}}) case "potato": - director.SetBuilder(&potatoBuilder{genericBuilder{_kind, remote{}}}) + director.SetBuilder(&potatoBuilder{genericBuilder{_kind, Remote{}}}) } director.Construct() return director.Get() diff --git a/voicemeeter/remote_test.go b/remote_test.go similarity index 100% rename from voicemeeter/remote_test.go rename to remote_test.go diff --git a/voicemeeter/strip.go b/strip.go similarity index 92% rename from voicemeeter/strip.go rename to strip.go index ad90c4a..4efcdf7 100644 --- a/voicemeeter/strip.go +++ b/strip.go @@ -256,29 +256,42 @@ func newStripLevels(i int, k *kind) levels { init = (k.physIn * 2) + ((i - k.physIn) * 8) os = 8 } - return levels{iRemote{fmt.Sprintf("strip[%d]", i), i}, k, init, os} + return levels{iRemote{fmt.Sprintf("strip[%d]", i), i}, k, init, os, "strip"} } func (l *levels) PreFader() []float32 { + _levelCache.stripMode = 0 var levels []float32 for i := l.init; i < l.init+l.offset; i++ { - levels = append(levels, l.convertLevel(getLevel(0, i))) + levels = append(levels, l.convertLevel(_levelCache.stripLevels[i])) } return levels } func (l *levels) PostFader() []float32 { + _levelCache.stripMode = 1 var levels []float32 for i := l.init; i < l.init+l.offset; i++ { - levels = append(levels, l.convertLevel(getLevel(1, i))) + levels = append(levels, l.convertLevel(_levelCache.stripLevels[i])) } return levels } func (l *levels) PostMute() []float32 { + _levelCache.stripMode = 2 var levels []float32 for i := l.init; i < l.init+l.offset; i++ { - levels = append(levels, l.convertLevel(getLevel(2, i))) + levels = append(levels, l.convertLevel(_levelCache.stripLevels[i])) } return levels } + +func (l *levels) IsDirty() bool { + var vals []bool + if l.id == "strip" { + vals = _levelCache.stripComp[l.init : l.init+l.offset] + } else if l.id == "bus" { + vals = _levelCache.busComp[l.init : l.init+l.offset] + } + return !allTrue(vals, l.offset) +} diff --git a/voicemeeter/strip_test.go b/strip_test.go similarity index 100% rename from voicemeeter/strip_test.go rename to strip_test.go diff --git a/tests/helper_test.go b/tests/helper_test.go index e3a0191..d75af4c 100644 --- a/tests/helper_test.go +++ b/tests/helper_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter" + "github.com/onyx-and-iris/voicemeeter-api-go" ) var ( diff --git a/util.go b/util.go new file mode 100644 index 0000000..70e95be --- /dev/null +++ b/util.go @@ -0,0 +1,24 @@ +package voicemeeter + +import "math" + +// allTrue accepts a boolean slice and evaluates if all elements are True +func allTrue(s []bool, sz int) bool { + for i := 0; i < sz; i++ { + if !s[i] { + return false + } + } + return true +} + +func update(s1 []float32, s2 []float32, sz int) { + for i := 0; i < sz; i++ { + s1[i] = s2[i] + } +} + +func roundFloat(val float64, precision uint) float64 { + ratio := math.Pow(10, float64(precision)) + return math.Round(val*ratio) / ratio +} diff --git a/voicemeeter/vban.go b/vban.go similarity index 100% rename from voicemeeter/vban.go rename to vban.go diff --git a/voicemeeter/vban_test.go b/vban_test.go similarity index 100% rename from voicemeeter/vban_test.go rename to vban_test.go