Compare commits

..

5 Commits

Author SHA1 Message Date
359c2d61b5 reword 2024-07-01 07:32:41 +01:00
e586478729 fix WithTimeout name in README
move it above WithBits
2024-07-01 07:23:40 +01:00
6512b35155 added Option function sections to README. 2024-07-01 07:20:52 +01:00
5aabd0a343 added 2.1.0 section to CHANGELOG 2024-07-01 07:20:30 +01:00
0558e8f81d login() is now tested for up to {Remote}.timeout seconds
if timeout exceeded return an error

runVoicemeeter() now promotes types to x64bit on 64-bit OS unless overridden.

Option functions WithTimeout() and WithBits() added.
2024-07-01 07:20:05 +01:00
4 changed files with 174 additions and 98 deletions

View File

@ -9,7 +9,20 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
## [Unreleased] ## [Unreleased]
- [x] - [x]
## [2.1.0] - 2024-07-01
### Added
- Added a configurable login timeout in seconds (defaults to 2).
- Option function added for overriding the type of Voicemeeter GUI runVoicemeeter() will launch.
- Explanation of Option functions added to README.
### Changed
- runVoicemeeter() now launches x64 GUIs for all kinds if on a 64 bit system.
- this can be overridden to force 32 bit GUI using voicemeeter.WithBits(32) Option function
## [2.0.0] - 2022-10-25 ## [2.0.0] - 2022-10-25
@ -17,150 +30,150 @@ V2 introduces some breaking changes.
### Changed ### Changed
- Removed Get prefix from getters in Bus, Strip, Vban, Button and Output types. - Removed Get prefix from getters in Bus, Strip, Vban, Button and Output types.
- Pooler now communicates event updates over a channel. - Pooler now communicates event updates over a channel.
- strip.comp now references comp struct type. (see readme for changes in setting comp parameters) - strip.comp now references comp struct type. (see readme for changes in setting comp parameters)
- strip.gate now references gate struct type. (see readme for changes in setting gate parameters) - strip.gate now references gate struct type. (see readme for changes in setting gate parameters)
- strip.eq, bus.eq now reference eQ struct type. (see readme for changes in setting eq parameters) - strip.eq, bus.eq now reference eQ struct type. (see readme for changes in setting eq parameters)
- All examples and tests have been updated to reflect the changes. - All examples and tests have been updated to reflect the changes.
### Added ### Added
- denoiser type to strip types. - denoiser type to strip types.
- XY parameters added to strip type - XY parameters added to strip type
- extra logging added to getters/setters in iRemote type. - extra logging added to getters/setters in iRemote type.
- InitPooler to Remote type in case the Pooler needs reinitiating. (perhaps the GUI closed unexpectedly) - InitPooler to Remote type in case the Pooler needs reinitiating. (perhaps the GUI closed unexpectedly)
### Fixed ### Fixed
- Functions that wrap CAPI calls in base.go now return correct error values. - Functions that wrap CAPI calls in base.go now return correct error values.
## [1.11.0] - 2022-10-10 ## [1.11.0] - 2022-10-10
### Fixed ### Fixed
- type error in getLevel - type error in getLevel
## [1.8.0] - 2022-09-17 ## [1.8.0] - 2022-09-17
### Added ### Added
- vm-cli example added + example README - vm-cli example added + example README
- Fade, App methods added to project README - Fade, App methods added to project README
## [1.7.0] - 2022-09-14 ## [1.7.0] - 2022-09-14
### Added ### Added
- voicemeeter.NewRemote now accepts a delay int argument (milliseconds). - voicemeeter.NewRemote now accepts a delay int argument (milliseconds).
- vm.Sync() can now be used to force the dirty parameters to clear. - vm.Sync() can now be used to force the dirty parameters to clear.
### Changed ### Changed
- higher level methods/functions now accept/return float64 - higher level methods/functions now accept/return float64
- tests updated to reflect changes. - tests updated to reflect changes.
## [1.5.0] - 2022-09-07 ## [1.5.0] - 2022-09-07
### Changed ### Changed
- changes to error handling. - changes to error handling.
- functions that wrap capi calls now return error types. - functions that wrap capi calls now return error types.
- higher level functions print error messages - higher level functions print error messages
## [1.4.0] - 2022-08-22 ## [1.4.0] - 2022-08-22
### Added ### Added
- midi type, supports midi devices - midi type, supports midi devices
- midi updates added to the pooler - midi updates added to the pooler
- event type, supports toggling event updates through EventAdd() and EventRemove() methods. - event type, supports toggling event updates through EventAdd() and EventRemove() methods.
- Forwarder methods for get/set float/string parameters added to Remote type - Forwarder methods for get/set float/string parameters added to Remote type
- Midi, Events sections added to README. - Midi, Events sections added to README.
### Changed ### Changed
- macrobutton updates moved into its own goroutine - macrobutton updates moved into its own goroutine
- observer example updated to include midi updates - observer example updated to include midi updates
- level updates are now disabled by default, should be enabled explicitly - level updates are now disabled by default, should be enabled explicitly
## [1.2.0] - 2022-07-10 ## [1.2.0] - 2022-07-10
### Added ### Added
- docstrings added to types, methods and functions - docstrings added to types, methods and functions
- version retractions added to go.mod - version retractions added to go.mod
### Changed ### Changed
- Entry method renamed from GetRemote to NewRemote - Entry method renamed from GetRemote to NewRemote
- Readme updated to reflect latest changes - Readme updated to reflect latest changes
## [1.1.0] - 2022-06-30 ## [1.1.0] - 2022-06-30
### Added ### Added
- Level updates implemented in Pooler struct. Runs in its own goroutine. - Level updates implemented in Pooler struct. Runs in its own goroutine.
### Fixed ### Fixed
- Fixed bug with identifier in outputs struct. - Fixed bug with identifier in outputs struct.
### Changed ### Changed
- Package files moved into root of repository. - Package files moved into root of repository.
- Remote struct now exported type - Remote struct now exported type
## [1.0.0] - 2022-06-30 ## [1.0.0] - 2022-06-30
### Added ### Added
- recorder, device structs implemented - recorder, device structs implemented
- gainlayers field in strip struct implemented - gainlayers field in strip struct implemented
- levels field in strip, bus structs implemented - levels field in strip, bus structs implemented
- pooler ratelimit set at 33ms - pooler ratelimit set at 33ms
## [0.0.3] - 2022-06-25 ## [0.0.3] - 2022-06-25
### Added ### Added
- pre-commit.ps1 added for use with git hook - pre-commit.ps1 added for use with git hook
- unit tests for factory functions added - unit tests for factory functions added
- vban parameter methods added - vban parameter methods added
- support for observers added. publisher/observer structs defined - support for observers added. publisher/observer structs defined
- Pooler struct added, pdirty, mdirty now updated continously in a goroutine - Pooler struct added, pdirty, mdirty now updated continously in a goroutine
### Changed ### Changed
- NewRemote factory method now uses director, builder types to create Remote types. - NewRemote factory method now uses director, builder types to create Remote types.
- cdll renamed to path - cdll renamed to path
- test suite now using testify/assert - test suite now using testify/assert
## [0.0.2] - 2022-06-23 ## [0.0.2] - 2022-06-23
### Added ### Added
- physicalStrip, virtualStrip, physicalBus and virtualBus types defined. - physicalStrip, virtualStrip, physicalBus and virtualBus types defined.
- factory methods for strip, bus now cast return values to interface types. - factory methods for strip, bus now cast return values to interface types.
- parameter methods added to strip, bus types. - parameter methods added to strip, bus types.
- command struct implemented - command struct implemented
- bus, vban unit tests added - bus, vban unit tests added
### Changed ### Changed
- strip, bus slices in remote type defined as interface slice types. - strip, bus slices in remote type defined as interface slice types.
- bindings in base now prepended with vm. - bindings in base now prepended with vm.
- vban fields added to kind structs - vban fields added to kind structs
## [0.0.1] - 2022-06-22 ## [0.0.1] - 2022-06-22
### Added ### Added
- interface entry point defined in remote - interface entry point defined in remote
- some base functions are exported through forwarding methods in Remote type (Login, Logout etc) - some base functions are exported through forwarding methods in Remote type (Login, Logout etc)
- wrapper around the CAPI defined in base - wrapper around the CAPI defined in base
- path helper functions defined in cdll - path helper functions defined in cdll
- kind structs defined in kinds. These describe the layout for each version. - kind structs defined in kinds. These describe the layout for each version.
- channel, strip, bus structs getter/setter procedures defined. - channel, strip, bus structs getter/setter procedures defined.
- button struct fully implemented. - button struct fully implemented.
- initial test commit - initial test commit

View File

@ -1,16 +1,14 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/onyx-and-iris/voicemeeter.svg)](https://pkg.go.dev/github.com/onyx-and-iris/voicemeeter/v2) [![Go Reference](https://pkg.go.dev/badge/github.com/onyx-and-iris/voicemeeter.svg)](https://pkg.go.dev/github.com/onyx-and-iris/voicemeeter/v2)
# A Go Wrapper for Voicemeeter API # A Go Wrapper for the Voicemeeter API
This package offers a Go interface for the Voicemeeter Remote C API.
For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md) For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
## Tested against ## Tested against
- Basic 1.0.8.8 - Basic 1.1.1.1
- Banana 2.0.6.8 - Banana 2.1.1.1
- Potato 3.0.2.8 - Potato 3.1.1.1
## Requirements ## Requirements
@ -67,7 +65,7 @@ func vmConnect() (*voicemeeter.Remote, error) {
} }
``` ```
## `voicemeeter.NewRemote(<kindId>, <delay>)` ## `voicemeeter.NewRemote(<kindId>, <delay>, opts ...Option)`
### `kindId` ### `kindId`
@ -83,6 +81,18 @@ Pass a delay in milliseconds to force the getters to wait for dirty parameters t
Useful if not listening for event updates. Useful if not listening for event updates.
### `voicemeeter.WithTimeout(timeout int)`
Set a login timeout, defaults to 2 seconds. For example to set it to 1s:
`voicemeeter.NewRemote("banana", 20, voicemeeter.WithTimeout(1))`
### `voicemeeter.WithBits(bits int)`
Override the type of Voicemeeter GUI to launch on 64 bit systems. For example, to force 32 bit GUI:
`voicemeeter.NewRemote("banana", 20, voicemeeter.WithBits(32))`
## `Remote Type` ## `Remote Type`
#### `vm.Strip` #### `vm.Strip`

34
base.go
View File

@ -2,8 +2,10 @@ package voicemeeter
import ( import (
"bytes" "bytes"
"errors"
"fmt" "fmt"
"math" "math"
"runtime"
"strings" "strings"
"syscall" "syscall"
"time" "time"
@ -45,16 +47,29 @@ var (
// login logs into the API, // login logs into the API,
// attempts to launch Voicemeeter if it's not running, // attempts to launch Voicemeeter if it's not running,
// initializes dirty parameters. // initializes dirty parameters.
func login(kindId string) error { func login(kindId string, timeout, bits int) error {
res, _, _ := vmLogin.Call() res, _, _ := vmLogin.Call()
if res == 1 { if res == 1 {
runVoicemeeter(kindId) runVoicemeeter(kindId, bits)
time.Sleep(time.Second)
} else if res != 0 { } else if res != 0 {
err := fmt.Errorf("VBVMR_Login returned %d", res) err := fmt.Errorf("VBVMR_Login returned %d", res)
return err return err
} }
log.Info("Logged into Voicemeeter ", kindId)
var ver_s string
start := time.Now()
var err error
for time.Since(start).Seconds() < float64(timeout) {
time.Sleep(time.Duration(100) * time.Millisecond)
if ver_s, err = getVersion(); err == nil {
log.Infof("Logged into Voicemeeter %s v%s", kindMap[kindId], ver_s)
log.Debugf("Log in time: %.2f", time.Since(start).Seconds())
break
}
}
if err != nil {
return errors.New("timeout logging into the API")
}
clear() clear()
return nil return nil
} }
@ -68,18 +83,22 @@ func logout(kindId string) error {
err := fmt.Errorf("VBVMR_Logout returned %d", int32(res)) err := fmt.Errorf("VBVMR_Logout returned %d", int32(res))
return err return err
} }
log.Info("Logged out of Voicemeeter ", kindId) log.Infof("Logged out of Voicemeeter %s", kindMap[kindId])
return nil return nil
} }
// runVoicemeeter attempts to launch a Voicemeeter GUI of a kind. // runVoicemeeter attempts to launch a Voicemeeter GUI of a kind.
func runVoicemeeter(kindId string) error { func runVoicemeeter(kindId string, bits int) error {
vals := map[string]uint64{ vals := map[string]uint64{
"basic": 1, "basic": 1,
"banana": 2, "banana": 2,
"potato": 3, "potato": 3,
} }
res, _, _ := vmRunvm.Call(uintptr(vals[kindId])) val := vals[kindId]
if strings.Contains(runtime.GOARCH, "64") && bits == 64 {
val += 3
}
res, _, _ := vmRunvm.Call(uintptr(val))
if int32(res) != 0 { if int32(res) != 0 {
err := fmt.Errorf("VBVMR_RunVoicemeeter returned %d", int32(res)) err := fmt.Errorf("VBVMR_RunVoicemeeter returned %d", int32(res))
return err return err
@ -93,6 +112,7 @@ func getVersion() (string, error) {
res, _, _ := vmGetvmVersion.Call(uintptr(unsafe.Pointer(&ver))) res, _, _ := vmGetvmVersion.Call(uintptr(unsafe.Pointer(&ver)))
if int32(res) != 0 { if int32(res) != 0 {
err := fmt.Errorf("VBVMR_GetVoicemeeterVersion returned %d", int32(res)) err := fmt.Errorf("VBVMR_GetVoicemeeterVersion returned %d", int32(res))
log.Error(err.Error())
return "", err return "", err
} }
v1 := (ver & 0xFF000000) >> 24 v1 := (ver & 0xFF000000) >> 24

View File

@ -20,7 +20,9 @@ type Remote struct {
Recorder *recorder Recorder *recorder
Midi *midi_t Midi *midi_t
pooler *pooler pooler *pooler
timeout int
bits int
} }
// String implements the fmt.stringer interface // String implements the fmt.stringer interface
@ -31,7 +33,7 @@ func (r *Remote) String() string {
// Login logs into the API // Login logs into the API
// then it intializes the pooler // then it intializes the pooler
func (r *Remote) Login() error { func (r *Remote) Login() error {
err := login(r.Kind.Name) err := login(r.Kind.Name, r.timeout, r.bits)
if err != nil { if err != nil {
return err return err
} }
@ -57,12 +59,10 @@ func (r *Remote) InitPooler() {
// Run launches the Voicemeeter GUI for a kind. // Run launches the Voicemeeter GUI for a kind.
func (r *Remote) Run(kindId string) error { func (r *Remote) Run(kindId string) error {
err := runVoicemeeter(kindId) err := runVoicemeeter(kindId, r.bits)
if err != nil { if err != nil {
return err return err
} }
time.Sleep(time.Second)
clear()
return nil return nil
} }
@ -173,6 +173,7 @@ type remoteBuilder interface {
makeDevice() remoteBuilder makeDevice() remoteBuilder
makeRecorder() remoteBuilder makeRecorder() remoteBuilder
makeMidi() remoteBuilder makeMidi() remoteBuilder
setDefaults() remoteBuilder
Build() remoteBuilder Build() remoteBuilder
Get() *Remote Get() *Remote
} }
@ -212,7 +213,7 @@ func (b *genericBuilder) setKind() remoteBuilder {
// makeStrip makes a strip slice and assigns it to remote.Strip // makeStrip makes a strip slice and assigns it to remote.Strip
// []iStrip comprises of both physical and virtual strip types // []iStrip comprises of both physical and virtual strip types
func (b *genericBuilder) makeStrip() remoteBuilder { func (b *genericBuilder) makeStrip() remoteBuilder {
log.Info("building strip") log.Debug("building strip")
strip := make([]iStrip, b.k.NumStrip()) strip := make([]iStrip, b.k.NumStrip())
for i := 0; i < b.k.NumStrip(); i++ { for i := 0; i < b.k.NumStrip(); i++ {
if i < b.k.PhysIn { if i < b.k.PhysIn {
@ -228,7 +229,7 @@ func (b *genericBuilder) makeStrip() remoteBuilder {
// makeBus makes a bus slice and assigns it to remote.Bus // makeBus makes a bus slice and assigns it to remote.Bus
// []t_bus comprises of both physical and virtual bus types // []t_bus comprises of both physical and virtual bus types
func (b *genericBuilder) makeBus() remoteBuilder { func (b *genericBuilder) makeBus() remoteBuilder {
log.Info("building bus") log.Debug("building bus")
bus := make([]iBus, b.k.NumBus()) bus := make([]iBus, b.k.NumBus())
for i := 0; i < b.k.NumBus(); i++ { for i := 0; i < b.k.NumBus(); i++ {
if i < b.k.PhysOut { if i < b.k.PhysOut {
@ -243,7 +244,7 @@ func (b *genericBuilder) makeBus() remoteBuilder {
// makeButton makes a button slice and assigns it to remote.Button // makeButton makes a button slice and assigns it to remote.Button
func (b *genericBuilder) makeButton() remoteBuilder { func (b *genericBuilder) makeButton() remoteBuilder {
log.Info("building button") log.Debug("building button")
button := make([]button, 80) button := make([]button, 80)
for i := 0; i < 80; i++ { for i := 0; i < 80; i++ {
button[i] = newButton(i) button[i] = newButton(i)
@ -254,39 +255,46 @@ func (b *genericBuilder) makeButton() remoteBuilder {
// 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 {
log.Info("building command") log.Debug("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 {
log.Info("building vban") log.Debug("building vban")
b.r.Vban = newVban(b.k) b.r.Vban = newVban(b.k)
return b return b
} }
// makeDevice makes a device type and assigns it to remote.Device // makeDevice makes a device type and assigns it to remote.Device
func (b *genericBuilder) makeDevice() remoteBuilder { func (b *genericBuilder) makeDevice() remoteBuilder {
log.Info("building device") log.Debug("building device")
b.r.Device = newDevice() b.r.Device = newDevice()
return b return b
} }
// makeRecorder makes a recorder type and assigns it to remote.Recorder // makeRecorder makes a recorder type and assigns it to remote.Recorder
func (b *genericBuilder) makeRecorder() remoteBuilder { func (b *genericBuilder) makeRecorder() remoteBuilder {
log.Info("building recorder") log.Debug("building recorder")
b.r.Recorder = newRecorder() b.r.Recorder = newRecorder()
return b return b
} }
// makeMidi makes a midi type and assigns it to remote.Midi // makeMidi makes a midi type and assigns it to remote.Midi
func (b *genericBuilder) makeMidi() remoteBuilder { func (b *genericBuilder) makeMidi() remoteBuilder {
log.Info("building midi") log.Debug("building midi")
b.r.Midi = newMidi() b.r.Midi = newMidi()
return b return b
} }
// setDefaults sets defaults for optional members
func (b *genericBuilder) setDefaults() remoteBuilder {
b.r.bits = 64
b.r.timeout = 2
return b
}
// 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
@ -306,7 +314,8 @@ func (basb *genericBuilder) Build() remoteBuilder {
makeCommand(). makeCommand().
makeVban(). makeVban().
makeDevice(). makeDevice().
makeMidi() makeMidi().
setDefaults()
} }
// bananaBuilder represents a builder specific to banana type // bananaBuilder represents a builder specific to banana type
@ -324,7 +333,8 @@ func (banb *bananaBuilder) Build() remoteBuilder {
makeVban(). makeVban().
makeDevice(). makeDevice().
makeRecorder(). makeRecorder().
makeMidi() makeMidi().
setDefaults()
} }
// potatoBuilder represents a builder specific to potato type // potatoBuilder represents a builder specific to potato type
@ -342,7 +352,8 @@ func (potb *potatoBuilder) Build() remoteBuilder {
makeVban(). makeVban().
makeDevice(). makeDevice().
makeRecorder(). makeRecorder().
makeMidi() makeMidi().
setDefaults()
} }
var ( var (
@ -350,6 +361,22 @@ var (
vmdelay int vmdelay int
) )
type Option func(*Remote)
func WithTimeout(timeout int) Option {
return func(r *Remote) {
r.timeout = timeout
}
}
func WithBits(bits int) Option {
return func(r *Remote) {
if bits == 32 || bits == 64 {
r.bits = bits
}
}
}
func init() { func init() {
log.SetOutput(os.Stdout) log.SetOutput(os.Stdout)
log.SetLevel(log.WarnLevel) log.SetLevel(log.WarnLevel)
@ -357,7 +384,7 @@ func init() {
// NewRemote returns a Remote type for a kind // NewRemote returns a Remote type for a kind
// this is the interface entry point // this is the interface entry point
func NewRemote(kindId string, delay int) (*Remote, error) { func NewRemote(kindId string, delay int, opts ...Option) (*Remote, error) {
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)
@ -380,5 +407,11 @@ func NewRemote(kindId string, delay int) (*Remote, error) {
director.SetBuilder(&potatoBuilder{genericBuilder{kind, Remote{}}}) director.SetBuilder(&potatoBuilder{genericBuilder{kind, Remote{}}})
} }
director.Construct() director.Construct()
return director.Get(), nil r := director.Get()
for _, opt := range opts {
opt(r)
}
return r, nil
} }