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.
This commit is contained in:
onyx-and-iris 2022-07-09 19:01:58 +01:00
parent f16bed893f
commit 70d69f5599
32 changed files with 179 additions and 75 deletions

View File

@ -41,7 +41,7 @@ package main
import ( import (
"fmt" "fmt"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter" "github.com/onyx-and-iris/voicemeeter-api-go"
) )
func main() { func main() {

View File

@ -112,6 +112,21 @@ func mdirty() bool {
return int(res) == 1 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 // getVMType returns the type of Voicemeeter, as a string
func getVMType() string { func getVMType() string {
var type_ uint64 var type_ uint64

View File

@ -253,13 +253,13 @@ func (bm *busMode) GetRearOnly() bool {
func newBusLevels(i int, k *kind) levels { func newBusLevels(i int, k *kind) levels {
init := i * 8 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 { func (l *levels) All() []float32 {
var levels []float32 var levels []float32
for i := l.init; i < l.init+l.offset; i++ { 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 return levels
} }

View File

@ -2,37 +2,45 @@ package main
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter" "github.com/onyx-and-iris/voicemeeter-api-go"
) )
type observer struct { type observer struct {
i int vm *voicemeeter.Remote
} }
func (o observer) OnUpdate(subject string) { 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() { func main() {
vmRem := voicemeeter.GetRemote("banana") vmRem := voicemeeter.GetRemote("potato")
vmRem.Login() vmRem.Login()
o := observer{1} o := observer{vmRem}
o2 := observer{2}
o3 := observer{3}
o4 := observer{4}
vmRem.Register(o) vmRem.Register(o)
vmRem.Register(o2) time.Sleep(30 * time.Second)
vmRem.Register(o3) vmRem.Deregister(o)
vmRem.Register(o4)
time.Sleep(5 * time.Second)
vmRem.Deregister(o2)
time.Sleep(5 * time.Second)
vmRem.Logout() vmRem.Logout()
} }

4
go.mod
View File

@ -3,8 +3,8 @@ module github.com/onyx-and-iris/voicemeeter-api-go
go 1.18 go 1.18
require ( require (
github.com/stretchr/testify v1.7.5 github.com/stretchr/testify v1.8.0
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 golang.org/x/sys v0.0.0-20220708085239-5a0f0661e09d
) )
require ( require (

8
go.sum
View File

@ -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.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 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.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.5 h1:s5PTfem8p8EbKQOctVV53k6jCJt3UX4IEJzwh+C324Q= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0/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-20220708085239-5a0f0661e09d h1:/m5NbqQelATgoSPVC2Z23sR4kVNokFwDDyWh/3rGY+I=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -2,7 +2,6 @@ package voicemeeter
import ( import (
"fmt" "fmt"
"math"
) )
// iRemote provides a set of common forwarding methods // 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) param := fmt.Sprintf("%s.%s", ir.identifier(), p)
setParameterString(param, v) 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
}

45
levels.go Normal file
View File

@ -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
}

View File

@ -43,15 +43,18 @@ func (p *publisher) notify(subject string) {
// pooler continuously polls the dirty paramters // pooler continuously polls the dirty paramters
// it is expected to be run in a goroutine // it is expected to be run in a goroutine
type pooler struct { type pooler struct {
k *kind
run bool run bool
publisher publisher
} }
func newPooler() *pooler { func newPooler(k *kind) *pooler {
p := &pooler{ p := &pooler{
k: k,
run: true, run: true,
} }
go p.runner() go p.runner()
go p.levels()
return p return p
} }
@ -66,3 +69,16 @@ func (p *pooler) runner() {
time.Sleep(33 * time.Millisecond) 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)
}
}

View File

@ -5,9 +5,8 @@ import (
"os" "os"
) )
// A remote type represents the API for a kind, // A Remote type represents the API for a kind
// comprised of slices representing each member type Remote struct {
type remote struct {
kind *kind kind *kind
Strip []t_strip Strip []t_strip
Bus []t_bus Bus []t_bus
@ -21,53 +20,53 @@ type remote struct {
} }
// String implements the fmt.stringer interface // String implements the fmt.stringer interface
func (r *remote) String() string { func (r *Remote) String() string {
return fmt.Sprintf("Voicemeeter %s", r.kind) return fmt.Sprintf("Voicemeeter %s", r.kind)
} }
// Login logs into the API // Login logs into the API
// then it intializes the pooler // then it intializes the pooler
func (r *remote) Login() { func (r *Remote) Login() {
r.pooler = newPooler()
login(r.kind.name) login(r.kind.name)
r.pooler = newPooler(r.kind)
} }
// Logout logs out of the API // Logout logs out of the API
// it also terminates the pooler // it also terminates the pooler
func (r *remote) Logout() { func (r *Remote) Logout() {
r.pooler.run = false r.pooler.run = false
logout() logout()
} }
func (r *remote) Type() string { func (r *Remote) Type() string {
return getVMType() return getVMType()
} }
func (r *remote) Version() string { func (r *Remote) Version() string {
return getVersion() return getVersion()
} }
// Pdirty returns true iff a parameter value has changed // Pdirty returns true iff a parameter value has changed
func (r *remote) Pdirty() bool { func (r *Remote) Pdirty() bool {
return pdirty() return pdirty()
} }
// Mdirty returns true iff a macrobutton value has changed // Mdirty returns true iff a macrobutton value has changed
func (r *remote) Mdirty() bool { func (r *Remote) Mdirty() bool {
return mdirty() return mdirty()
} }
func (r *remote) SendText(script string) { func (r *Remote) SendText(script string) {
setParametersMulti(script) setParametersMulti(script)
} }
// Register forwards the register method to Pooler // Register forwards the register method to Pooler
func (r *remote) Register(o observer) { func (r *Remote) Register(o observer) {
r.pooler.Register(o) r.pooler.Register(o)
} }
// Register forwards the deregister method to Pooler // Register forwards the deregister method to Pooler
func (r *remote) Deregister(o observer) { func (r *Remote) Deregister(o observer) {
r.pooler.Deregister(o) r.pooler.Deregister(o)
} }
@ -81,7 +80,7 @@ type remoteBuilder interface {
makeDevice() remoteBuilder makeDevice() remoteBuilder
makeRecorder() remoteBuilder makeRecorder() remoteBuilder
Build() remoteBuilder Build() remoteBuilder
Get() *remote Get() *Remote
} }
// directory is responsible for directing the genericBuilder // directory is responsible for directing the genericBuilder
@ -100,13 +99,13 @@ func (d *director) Construct() {
} }
// Get forwards the Get method to the builder // Get forwards the Get method to the builder
func (d *director) Get() *remote { func (d *director) Get() *Remote {
return d.builder.Get() return d.builder.Get()
} }
type genericBuilder struct { type genericBuilder struct {
k *kind k *kind
r remote r Remote
} }
func (b *genericBuilder) setKind() remoteBuilder { func (b *genericBuilder) setKind() remoteBuilder {
@ -157,28 +156,28 @@ func (b *genericBuilder) makeButton() remoteBuilder {
return b 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 { func (b *genericBuilder) makeCommand() remoteBuilder {
fmt.Println("building command") fmt.Println("building command")
b.r.Command = newCommand() b.r.Command = newCommand()
return b 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 { func (b *genericBuilder) makeVban() remoteBuilder {
fmt.Println("building vban") fmt.Println("building vban")
b.r.Vban = newVban(b.k) b.r.Vban = newVban(b.k)
return b 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 { func (b *genericBuilder) makeDevice() remoteBuilder {
fmt.Println("building device") fmt.Println("building device")
b.r.Device = newDevice() b.r.Device = newDevice()
return b 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 { func (b *genericBuilder) makeRecorder() remoteBuilder {
fmt.Println("building recorder") fmt.Println("building recorder")
b.r.Recorder = newRecorder() b.r.Recorder = newRecorder()
@ -186,7 +185,7 @@ func (b *genericBuilder) makeRecorder() remoteBuilder {
} }
// Get returns a fully constructed remote type for a kind // Get returns a fully constructed remote type for a kind
func (b *genericBuilder) Get() *remote { func (b *genericBuilder) Get() *Remote {
return &b.r return &b.r
} }
@ -217,9 +216,9 @@ func (potb *potatoBuilder) Build() remoteBuilder {
return potb.setKind().makeStrip().makeBus().makeButton().makeCommand().makeVban().makeDevice().makeRecorder() 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 // this is the interface entry point
func GetRemote(kindId string) *remote { func GetRemote(kindId string) *Remote {
_kind, ok := kindMap[kindId] _kind, ok := kindMap[kindId]
if !ok { if !ok {
err := fmt.Errorf("unknown Voicemeeter kind '%s'", kindId) err := fmt.Errorf("unknown Voicemeeter kind '%s'", kindId)
@ -230,11 +229,11 @@ func GetRemote(kindId string) *remote {
director := director{} director := director{}
switch _kind.name { switch _kind.name {
case "basic": case "basic":
director.SetBuilder(&basicBuilder{genericBuilder{_kind, remote{}}}) director.SetBuilder(&basicBuilder{genericBuilder{_kind, Remote{}}})
case "banana": case "banana":
director.SetBuilder(&bananaBuilder{genericBuilder{_kind, remote{}}}) director.SetBuilder(&bananaBuilder{genericBuilder{_kind, Remote{}}})
case "potato": case "potato":
director.SetBuilder(&potatoBuilder{genericBuilder{_kind, remote{}}}) director.SetBuilder(&potatoBuilder{genericBuilder{_kind, Remote{}}})
} }
director.Construct() director.Construct()
return director.Get() return director.Get()

View File

@ -256,29 +256,42 @@ func newStripLevels(i int, k *kind) levels {
init = (k.physIn * 2) + ((i - k.physIn) * 8) init = (k.physIn * 2) + ((i - k.physIn) * 8)
os = 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 { func (l *levels) PreFader() []float32 {
_levelCache.stripMode = 0
var levels []float32 var levels []float32
for i := l.init; i < l.init+l.offset; i++ { 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 return levels
} }
func (l *levels) PostFader() []float32 { func (l *levels) PostFader() []float32 {
_levelCache.stripMode = 1
var levels []float32 var levels []float32
for i := l.init; i < l.init+l.offset; i++ { 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 return levels
} }
func (l *levels) PostMute() []float32 { func (l *levels) PostMute() []float32 {
_levelCache.stripMode = 2
var levels []float32 var levels []float32
for i := l.init; i < l.init+l.offset; i++ { 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 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)
}

View File

@ -5,7 +5,7 @@ import (
"testing" "testing"
"time" "time"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter" "github.com/onyx-and-iris/voicemeeter-api-go"
) )
var ( var (

24
util.go Normal file
View File

@ -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
}