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 (
"fmt"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter"
"github.com/onyx-and-iris/voicemeeter-api-go"
)
func main() {

View File

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

View File

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

View File

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

4
go.mod
View File

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

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.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=

View File

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

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
// 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)
}
}

View File

@ -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()

View File

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

View File

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

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
}