67 Commits

Author SHA1 Message Date
onyx-and-iris
8bb8336599 interactive mode added to vm-cli example
toggle, get and set refactored into separate functions

flag vars moved from global  scope

-i flag added to README
2022-10-04 20:56:38 +01:00
onyx-and-iris
5b3f10c4b8 print bus level values in observer example 2022-10-04 20:18:48 +01:00
onyx-and-iris
7022c5dacf fix type error in getLevel 2022-10-04 20:18:26 +01:00
onyx-and-iris
5bb7eb9668 remove logrus from vm-cli 2022-09-29 19:04:32 +01:00
onyx-and-iris
5e5a82f198 upd -v flag description 2022-09-29 18:31:15 +01:00
onyx-and-iris
d8de82b90e add note to install in vm-cli 2022-09-29 18:27:26 +01:00
onyx-and-iris
5b4592f484 upd dep ver for obs, hotkeys examples 2022-09-29 18:25:23 +01:00
onyx-and-iris
b2dd96abc4 add install to vm-cli readme 2022-09-29 18:17:23 +01:00
onyx-and-iris
6605d6e62d set log level to info in examples
add verbose flag to vm-cli example

update readme for vm-cli example
2022-09-29 18:05:21 +01:00
onyx-and-iris
3ab5daa61c update install instructions in readme. 2022-09-29 18:04:13 +01:00
onyx-and-iris
0d06098af8 now using logrus package
logrus added to go.mod
2022-09-29 18:02:11 +01:00
onyx-and-iris
9df10d939d clean up installation in readme 2022-09-23 18:17:56 +01:00
onyx-and-iris
d82e6a39f5 fix package name in readme 2022-09-22 23:24:54 +01:00
onyx-and-iris
10d7cea523 Merge branch 'dev' of https://github.com/onyx-and-iris/voicemeeter-api-go into dev 2022-09-22 22:55:24 +01:00
onyx-and-iris
8a9b5f9a97 rename dir structure to match package name
update examples, readme
2022-09-22 22:55:08 +01:00
norm
8a611e39aa change func and var names 2022-09-21 07:30:14 +01:00
norm
ff56dd16f5 upd readme with new default delay 2022-09-19 03:05:02 +01:00
norm
440c3b8eec increae default delay to 20 2022-09-19 03:03:51 +01:00
onyx-and-iris
a5f653f569 add error message if config not found 2022-09-18 20:57:59 +01:00
onyx-and-iris
73893209a1 upd dependency ver 2022-09-18 05:53:08 +01:00
onyx-and-iris
303f1a871c md fix 2022-09-18 05:45:09 +01:00
onyx-and-iris
2c6baf20e4 vm-cli reworked.
now accepts -kind and -delay flags.

arg parsing moved into separate run_commands function.

README added to vm-cli example
2022-09-18 05:39:42 +01:00
onyx-and-iris
c05bf500ee obs example now reads conn info from toml.
vm, obs connect logic moved into separate functions.

README added for obs example.
2022-09-18 05:38:22 +01:00
onyx-and-iris
2533f1c162 refactor observer example.
newObserver factory method added
2022-09-18 05:37:10 +01:00
onyx-and-iris
21fb2ad597 refactor example in readme 2022-09-18 05:36:24 +01:00
onyx-and-iris
1284c92680 refactor hotkeys example. 2022-09-18 05:35:53 +01:00
onyx-and-iris
a87c11099c config.toml added to gitignore 2022-09-18 05:35:27 +01:00
onyx-and-iris
00f66e8f9e GetFloat, SetFloat now return error type on error
GetString, SetString now return error type on error

SendText now returns error type on error

remote_test updated.
2022-09-18 05:35:04 +01:00
onyx-and-iris
e16a51c0da Fade and App methods added to readme. 2022-09-17 03:47:54 +01:00
onyx-and-iris
8a03904ab8 add readme to vm-cli example 2022-09-17 03:21:04 +01:00
onyx-and-iris
5fb7f5e4db remove redundant files 2022-09-17 03:13:50 +01:00
onyx-and-iris
915e3a9609 add vm-cli example 2022-09-17 03:11:53 +01:00
onyx-and-iris
0a7b8e0662 fix whitespace 2022-09-14 21:17:20 +01:00
onyx-and-iris
c32e3995db add hotkeys example 2022-09-14 21:16:29 +01:00
onyx-and-iris
07218472df obs example updated 2022-09-14 16:43:04 +01:00
onyx-and-iris
db7826dab6 reword in readme 2022-09-14 16:11:34 +01:00
onyx-and-iris
32780f11e4 changelog, readme updated to reflect changes
minor bump.
2022-09-14 16:05:49 +01:00
onyx-and-iris
8ca545b1c4 NewRemote now accepts delay param
getters clear dirty params sync enabled.

examples and tests updated
2022-09-14 16:05:15 +01:00
onyx-and-iris
a4b84f289e higher methods/functions now accept/return float64
update tests

update readme, changelog
2022-09-14 00:58:05 +01:00
onyx-and-iris
67cb8509b4 Update README.md 2022-09-07 22:32:37 +01:00
onyx-and-iris
e4de1d49cb upd go.mod for obs example 2022-09-07 21:10:42 +01:00
onyx-and-iris
50d045d823 fix ver in changelog 2022-09-07 21:04:14 +01:00
onyx-and-iris
6ed4e38dae changes to error handling
readme, changelog updated to reflect changes

version bump
2022-09-07 20:59:55 +01:00
onyx-and-iris
505b5969a2 go fmt all files.
add midi event toggle to the pooler
2022-08-23 14:03:07 +01:00
onyx-and-iris
7f992a1a87 only check subject once per update 2022-08-23 03:58:49 +01:00
onyx-and-iris
fa8e9f3e76 obs example added. 2022-08-23 03:53:51 +01:00
onyx-and-iris
7a79555cb8 use log.Fatal during setup procedures.
update readme example
2022-08-23 03:35:34 +01:00
onyx-and-iris
6fabc43998 return err from NewRemote\
pass Kind.Name to logout

update examples/tests to reflect changes
2022-08-23 03:16:43 +01:00
onyx-and-iris
3fd08ff606 fix deregister in observer example 2022-08-22 22:54:38 +01:00
onyx-and-iris
7744971a10 changelog updated to reflect changes
minor version bump
2022-08-22 22:40:08 +01:00
onyx-and-iris
82bbcd06b1 tested against version updated.
Get/Set Float/String, EventAdd() + EventRemote() added to README

Midi and Events sections added to README.
2022-08-22 22:39:35 +01:00
onyx-and-iris
cba2ac85ec observer example updated with midi event.
ldirty now enabled explicitly in observer example.
2022-08-22 22:34:32 +01:00
onyx-and-iris
dd895daffb float/string getter/setter tests added
test helper added
2022-08-22 22:34:03 +01:00
onyx-and-iris
87a05d81e4 getter/setter method forwarders added to Remote
EventAdd() and EventRemote() added to Remote

method chaining in builder types split across lines.
2022-08-22 22:33:22 +01:00
onyx-and-iris
3ea4aee863 event type added for toggline evet subscriptions.
level updates now disabled by default.

each event updater runs in its own goroutine.
2022-08-22 22:29:30 +01:00
onyx-and-iris
69476ffcd9 getMidiMessage implemented
midi type added.
2022-08-22 22:28:11 +01:00
onyx-and-iris
76e6d3cba7 Remote Kind field now exported.
Kind fields/methods now exported

vmRem renamed vm in examples/tests. prefer short variable name.

minor version bump
2022-07-18 16:23:15 +01:00
onyx-and-iris
1af67bb219 Update strip.go
fix typo in docstrings
2022-07-12 17:33:50 +01:00
onyx-and-iris
14264d0818 docstrings added to remote 2022-07-10 23:35:56 +01:00
onyx-and-iris
1efac19b12 docstrings added to functions, types and methods
CHANGELOG first update

pre-commit updated to look in root of repo.

version retraction added to go.mod

README updated to reflect changes
2022-07-10 23:08:14 +01:00
onyx-and-iris
36522cf80c reword in readme 2022-07-09 19:31:28 +01:00
onyx-and-iris
119a996afd upd ver in readme 2022-07-09 19:26:45 +01:00
onyx-and-iris
70d69f5599 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.
2022-07-09 19:01:58 +01:00
onyx-and-iris
f16bed893f markup change 2022-07-07 14:29:15 +01:00
onyx-and-iris
e7061ce855 add heading to installation section 2022-07-02 12:30:45 +01:00
onyx-and-iris
39411ab332 gainlayers, levels added to readme.
bus mode methods fixed
2022-07-01 14:03:01 +01:00
onyx-and-iris
6148d45b76 upd minor ver in readme 2022-07-01 01:41:43 +01:00
51 changed files with 2212 additions and 796 deletions

5
.gitignore vendored
View File

@@ -1,5 +1,8 @@
# quick tests
quick.go
quick
#config
config.toml
# Binaries for programs and plugins
*.exe

View File

@@ -10,3 +10,120 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass
## [Unreleased]
- [x]
## [1.7.0] - 2022-09-14
### Added
- voicemeeter.NewRemote now accepts a delay int argument (milliseconds).
- vm.Sync() can now be used to force the dirty parameters to clear.
### Changed
- higher level methods/functions now accept/return float64
- tests updated to reflect changes.
## [1.5.0] - 2022-09-07
### Changed
- changes to error handling.
- functions that wrap capi calls now return error types.
- higher level functions print error messages
## [1.4.0] - 2022-08-22
### Added
- midi type, supports midi devices
- midi updates added to the pooler
- event type, supports toggling event updates through EventAdd() and EventRemove() methods.
- Forwarder methods for get/set float/string parameters added to Remote type
- Midi, Events sections added to README.
### Changed
- macrobutton updates moved into its own goroutine
- observer example updated to include midi updates
- level updates are now disabled by default, should be enabled explicitly
## [1.2.0] - 2022-07-10
### Added
- docstrings added to types, methods and functions
- version retractions added to go.mod
### Changed
- Entry method renamed from GetRemote to NewRemote
- Readme updated to reflect latest changes
## [1.1.0] - 2022-06-30
### Added
- Level updates implemented in Pooler struct. Runs in its own goroutine.
### Fixed
- Fixed bug with identifier in outputs struct.
### Changed
- Package files moved into root of repository.
- Remote struct now exported type
## [1.0.0] - 2022-06-30
### Added
- recorder, device structs implemented
- gainlayers field in strip struct implemented
- levels field in strip, bus structs implemented
- pooler ratelimit set at 33ms
## [0.0.3] - 2022-06-25
### Added
- pre-commit.ps1 added for use with git hook
- unit tests for factory functions added
- vban parameter methods added
- support for observers added. publisher/observer structs defined
- Pooler struct added, pdirty, mdirty now updated continously in a goroutine
### Changed
- NewRemote factory method now uses director, builder types to create Remote types.
- cdll renamed to path
- test suite now using testify/assert
## [0.0.2] - 2022-06-23
### Added
- physicalStrip, virtualStrip, physicalBus and virtualBus types defined.
- factory methods for strip, bus now cast return values to interface types.
- parameter methods added to strip, bus types.
- command struct implemented
- bus, vban unit tests added
### Changed
- strip, bus slices in remote type defined as interface slice types.
- bindings in base now prepended with vm.
- vban fields added to kind structs
## [0.0.1] - 2022-06-22
### Added
- interface entry point defined in remote
- some base functions are exported through forwarding methods in Remote type (Login, Logout etc)
- wrapper around the CAPI defined in base
- path helper functions defined in cdll
- kind structs defined in kinds. These describe the layout for each version.
- channel, strip, bus structs getter/setter procedures defined.
- button struct fully implemented.
- initial test commit

357
README.md
View File

@@ -1,4 +1,4 @@
[![Go Reference](https://pkg.go.dev/badge/github.com/onyx-and-iris/voicemeeter-api-go.svg)](https://pkg.go.dev/github.com/onyx-and-iris/voicemeeter-api-go)
[![Go Reference](https://pkg.go.dev/badge/github.com/onyx-and-iris/voicemeeter.svg)](https://pkg.go.dev/github.com/onyx-and-iris/voicemeeter)
# A Go Wrapper for Voicemeeter API
@@ -8,9 +8,9 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
## Tested against
- Basic 1.0.8.2
- Banana 2.0.6.2
- Potato 3.0.2.2
- Basic 1.0.8.4
- Banana 2.0.6.4
- Potato 3.0.2.4
## Requirements
@@ -19,13 +19,12 @@ For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md)
## Installation
Add to your `go.mod` file:
Initialize your own module then `go get`
`require github.com/onyx-and-iris/voicemeeter-api-go v1.0.0`
Install voicemeeter-api-go package from your console
`go get github.com/onyx-and-iris/voicemeeter-api-go`
```
go mod init github.com/x/y
go get github.com/onyx-and-iris/voicemeeter
```
## `Use`
@@ -36,26 +35,41 @@ package main
import (
"fmt"
"log"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter"
"github.com/onyx-and-iris/voicemeeter"
)
func main() {
kindId := "banana"
vmRem := voicemeeter.GetRemote(kindId)
vm, err := vmConnect()
if err != nil {
log.Fatal(err)
}
defer vm.Logout()
vmRem.Login()
vm.Strip[0].SetLabel("rode podmic")
vm.Strip[0].SetMute(true)
fmt.Printf("Strip 0 (%s) mute was set to %v\n", vm.Strip[0].GetLabel(), vm.Strip[0].GetMute())
}
vmRem.Strip[0].SetLabel("rode podmic")
vmRem.Strip[0].SetMute(true)
fmt.Printf("Strip 0 (%s) mute was set to %v\n", vmRem.Strip[0].GetLabel(), vmRem.Strip[0].GetMute())
func vmConnect() (*voicemeeter.Remote, error) {
vm, err := voicemeeter.NewRemote("banana", 20)
if err != nil {
return nil, err
}
vmRem.Logout()
err = vm.Login()
if err != nil {
return nil, err
}
return vm, nil
}
```
## `voicemeeter.NewRemote(<kindId>, <delay>)`
## `kindId`
### `kindId`
Pass the kind of Voicemeeter as an argument. kindId may be:
@@ -63,42 +77,107 @@ Pass the kind of Voicemeeter as an argument. kindId may be:
- `banana`
- `potato`
### `delay`
Pass a delay in milliseconds to force the getters to wait for dirty parameters to clear.
Useful if not running callbacks.
## `Remote Type`
#### `vmRem.Strip`
#### `vm.Strip`
[]t_strip slice containing both physicalStrip and virtualStrip types
#### `vmRem.Bus`
#### `vm.Bus`
[]t_bus slice containing both physicalBus and virtualBus types
#### `vmRem.Button`
#### `vm.Button`
[]button slice containing button types, one for each macrobutton
#### `vmRem.Command`
#### `vm.Command`
pointer to command type, represents action type functions
#### `vmRem.Vban`
#### `vm.Vban`
pointer to vban type, containing both vbanInStream and vbanOutStream slices
#### `vmRem.Device`
#### `vm.Device`
pointer to device type, represents physical input/output hardware devices
#### `vmRem.Recorder`
#### `vm.Recorder`
pointer to recorder type, represents the recorder
#### `vmRem.Type()`
#### `vm.Midi`
pointer to midi type, represents a connected midi device
#### `vm.Type()`
returns the type of Voicemeeter as a string
#### `vmRem.Version()`
#### `vm.Version()`
returns the version of Voicemeeter as a string
#### `vmRem.SendText(<script>)`
#### `vm.GetFloat(<param>)`
gets a float parameter value
#### `vm.SetFloat(<param>, <value>)`
sets a float parameter value eg. vm.SetFloat("strip[0].mute", 1)
#### `vm.GetString(<param>)`
gets a string parameter value
#### `vm.SetString(<param>, <value>)`
sets a string parameter value eg. vm.SetString("strip[0].label", "podmic")
#### `vm.SendText(<script>)`
sets many parameters in script format eg. ("Strip[0].Mute=1;Bus[3].Gain=3.6")
#### `vmRem.Register(o observer)`
#### `vm.Register(o observer)`
register an object as an observer
#### `vmRem.Deregister(o observer)`
#### `vm.Deregister(o observer)`
deregister an object as an observer
#### `vmRem.Pdirty()`
#### `vm.EventAdd(<event>)`
adds an event to the pooler eg. vm.EventAdd("ldirty")
#### `vm.EventRemove(<event>)`
removes an event to the pooler eg. vm.EventRemove("pdirty")
#### `vm.Pdirty()`
returns True iff a GUI parameter has changed
#### `vmRem.Mdirty()`
returns True iff a macrobutton paramter has changed
#### `vm.Mdirty()`
returns True iff a macrobutton parameter has changed
#### `vm.Sync()`
Use this to force dirty parameters to clear after a delay in milliseconds.
## `Available commands`
### Strip
The following functions are available
The following methods are available
- `GetMute() bool`
- `SetMute(val bool)`
@@ -111,29 +190,65 @@ The following functions are available
- `GetLabel() string`
- `SetLabel(val string)`
- `GetGain() float64`
- `SetGain(val float32)` from -60.0 to 12.0
- `SetGain(val float64)` from -60.0 to 12.0
- `GetMc() bool`
- `SetMc(val bool)`
- `GetComp() float64`
- `SetComp(val float32)` from 0.0 to 10.0
- `SetComp(val float64)` from 0.0 to 10.0
- `GetGate() float64`
- `SetGate(val float32)` from 0.0 to 10.0
- `SetGate(val float64)` from 0.0 to 10.0
- `GetAudibility() float64`
- `SetAudibility(val float32)` from 0.0 to 10.0
- `SetAudibility(val float64)` from 0.0 to 10.0
- `GetA1() bool - GetA5() bool`
- `SetA1(val bool) - SetA5(val bool)`
- `AppGain(name string, gain float64)`
- `AppMute(name string, val bool)`
example:
```go
vmRem.Strip[3].SetGain(3.7)
fmt.Println(vmRem.Strip[0].GetLabel())
vmRem.Strip[4].SetA1(true)
vm.Strip[3].SetGain(3.7)
fmt.Println(vm.Strip[0].GetLabel())
vm.Strip[4].SetA1(true)
vm.Strip[5].AppGain("Spotify", 0.5)
vm.Strip[5].AppMute("Spotify", true)
```
##### Gainlayers
- `vm.Strip[i].GainLayer()[j]`
The following methods are available
- `Get() float64`
- `Set(val float64)`
example:
```go
vm.Strip[6].GainLayer()[3].Set(-13.6)
```
##### Levels
- `vm.Strip[i].Levels()`
The following methods are available
- `PreFader() []float64`
- `PostFader() []float64`
- `PostMute() []float64`
example:
```go
fmt.Println(vm.Strip[5].Levels().PreFader())
```
### Bus
The following functions are available
The following methods are available
- `String() string`
- `GetMute() bool`
@@ -145,40 +260,84 @@ The following functions are available
- `GetLabel() string`
- `SetLabel(val string)`
- `GetGain() float64`
- `SetGain(val float32)` from -60.0 to 12.0
- `SetGain(val float64)` from -60.0 to 12.0
```go
vmRem.Bus[3].SetEq(true)
fmt.Println(vmRem.Bus[0].GetLabel())
vm.Bus[3].SetEq(true)
fmt.Println(vm.Bus[0].GetLabel())
```
##### Modes
- `vmRem.Bus[i].Mode()`
- `vm.Bus[i].Mode()`
The following functions are available
The following methods are available
- `normal`: boolean
- `amix`: boolean
- `bmix`: boolean
- `composite`: boolean
- `tvmix`: boolean
- `upmix21`: boolean
- `upmix41`: boolean
- `upmix61`: boolean
- `centeronly`: boolean
- `lfeonly`: boolean
- `rearonly`: boolean
- `SetNormal(val bool)`
- `GetNormal() bool`
- `SetAmix(val bool)`
- `GetAmix() bool`
- `SetBmix(val bool)`
- `GetBmix() bool`
- `SetRepeat(val bool)`
- `GetRepeat() bool`
- `SetComposite(val bool)`
- `GetComposite() bool`
- `SetTvMix(val bool)`
- `GetTvMix() bool`
- `SetUpMix21(val bool)`
- `GetUpMix21() bool`
- `SetUpMix41(val bool)`
- `GetUpMix41() bool`
- `SetUpMix61(val bool)`
- `GetUpMix61() bool`
- `SetCenterOnly(val bool)`
- `GetCenterOnly() bool`
- `SetLfeOnly(val bool)`
- `GetLfeOnly() bool`
- `SetRearOnly(val bool)`
- `GetRearOnly() bool`
example:
```go
vmRem.Bus[3].Mode().SetAmix(true)
vm.Bus[3].Mode().SetAmix(true)
vm.Bus[4].Mode().SetCenterOnly(true)
```
##### Levels
- `vm.Bus[i].Levels()`
The following methods are available
- `All() []float64`
example:
```go
fmt.Println(vm.Bus[1].Levels().All())
```
### Strip | Bus
The following methods are available.
- `FadeTo(target float64, time_ int)`: float, int
- `FadeBy(change float64, time_ int)`: float, int
Modify gain to or by the selected amount in db over a time interval in ms.
example:
```go
vm.Strip[3].FadeBy(-8.3, 500)
vm.Bus[3].FadeTo(-12.8, 500)
```
### Button
The following functions are available
The following methods are available
- `GetState() bool`
- `SetState(val bool)`
@@ -190,13 +349,13 @@ The following functions are available
example:
```go
vmRem.Button[37].SetState(true)
fmt.Println(vmRem.Button[64].GetStateOnly())
vm.Button[37].SetState(true)
fmt.Println(vm.Button[64].GetStateOnly())
```
### Command
The following functions are available
The following methods are available
- `Show()` Show Voicemeeter GUI if it's hidden
- `Hide()` Hide Voicemeeter GUI if it's shown
@@ -207,19 +366,19 @@ The following functions are available
example:
```go
vmRem.Command.Restart()
vmRem.Command.Show()
vm.Command.Restart()
vm.Command.Show()
```
### VBAN
- `vmRem.Vban.Enable()` `vmRem.Vban.Disable()` Turn VBAN on or off
- `vm.Vban.Enable()` `vm.Vban.Disable()` Turn VBAN on or off
##### Instream | Outstream
- `vmRem.Vban.InStream` `vmRem.Vban.OutStream`
- `vm.Vban.InStream` `vm.Vban.OutStream`
The following functions are available
The following methods are available
- `GetOn() bool`
- `SetOn(val bool)`
@@ -244,35 +403,35 @@ example:
```go
# turn VBAN on
vmRem.Vban.Enable()
vm.Vban.Enable()
// turn on vban instream 0
vmRem.Vban.InStream[0].SetOn(true)
vm.Vban.InStream[0].SetOn(true)
// set bit property for outstream 3 to 24
vmRem.Vban.OutStream[3].SetBit(24)
vm.Vban.OutStream[3].SetBit(24)
```
### Device
The following functions are available
The following methods are available
- `Ins`
- `Outs`
- `Ins()`
- `Outs()`
- `Input(val int)`
- `Output(val int)`
example:
```go
for i := 0; i < int(vmRem.Device.Ins()); i++ {
fmt.Println(vmRem.Device.Input(i))
for i := 0; i < int(vm.Device.Ins()); i++ {
fmt.Println(vm.Device.Input(i))
}
```
### Recorder
The following functions are available
The following methods are available
- `Play()`
- `Stop()`
@@ -285,14 +444,46 @@ The following functions are available
example:
```go
vmRem.Recorder.Play()
vmRem.Recorder.Stop()
vm.Recorder.Play()
vm.Recorder.Stop()
# Enable loop play
vmRem.Recorder.Loop(true)
vm.Recorder.Loop(true)
# Disable recorder out channel B2
vmRem.Recorder.SetB2(false)
vm.Recorder.SetB2(false)
```
### Midi
The following methods are available
- `Channel()` returns the current midi channel
- `Current()` returns the most recently pressed midi button
- `Get(<button>)` returns the value in cache for the midi button
example:
```go
var current = vm.Midi.Current()
var val = vm.Midi.Get(current)
```
### Events
By default level updates are disabled. Any event may be enabled or disabled. The following events exist:
- `pdirty` parameter updates
- `mdirty` macrobutton updates
- `midi` midi updates
- `ldirty` level updates
example:
```go
vm.EventAdd("ldirty")
vm.EventRemove("pdirty")
```
### Run tests
@@ -300,7 +491,7 @@ vmRem.Recorder.SetB2(false)
To run all tests:
```
go run test ./...
go test ./...
```
### Official Documentation

View File

@@ -4,11 +4,12 @@ import (
"bytes"
"fmt"
"math"
"os"
"strings"
"syscall"
"time"
"unsafe"
log "github.com/sirupsen/logrus"
)
var (
@@ -37,40 +38,43 @@ var (
vmMdirty = mod.NewProc("VBVMR_MacroButton_IsDirty")
vmGetMacroStatus = mod.NewProc("VBVMR_MacroButton_GetStatus")
vmSetMacroStatus = mod.NewProc("VBVMR_MacroButton_SetStatus")
vmGetMidiMessage = mod.NewProc("VBVMR_GetMidiMessage")
)
// login logs into the API,
// then attempts to launch Voicemeeter if it's not running.
func login(kindId string) {
// attempts to launch Voicemeeter if it's not running,
// initializes dirty parameters.
func login(kindId string) error {
res, _, _ := vmLogin.Call()
if res == 1 {
runVoicemeeter(kindId)
time.Sleep(time.Second)
} else if res != 0 {
err := fmt.Errorf("VBVMR_Login returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
fmt.Println("Logged into API")
log.Info("Logged into Voicemeeter ", kindId)
for pdirty() || mdirty() {
}
return nil
}
// logout logs out of the API,
// delayed for 100ms to allow final operation to complete.
func logout() {
func logout(kindId string) error {
time.Sleep(100 * time.Millisecond)
res, _, _ := vmLogout.Call()
if res != 0 {
err := fmt.Errorf("VBVMR_Logout returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
fmt.Println("Logged out of API")
log.Info("Logged out of Voicemeeter ", kindId)
return nil
}
// runVoicemeeter attempts to launch a Voicemeeter GUI of a kind.
func runVoicemeeter(kindId string) {
func runVoicemeeter(kindId string) error {
vals := map[string]uint64{
"basic": 1,
"banana": 2,
@@ -79,25 +83,24 @@ func runVoicemeeter(kindId string) {
res, _, _ := vmRunvm.Call(uintptr(vals[kindId]))
if res != 0 {
err := fmt.Errorf("VBVMR_RunVoicemeeter returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
return nil
}
// getVersion returns the version of Voicemeeter as a string
func getVersion() string {
func getVersion() (string, error) {
var ver uint64
res, _, _ := vmGetvmVersion.Call(uintptr(unsafe.Pointer(&ver)))
if res != 0 {
err := fmt.Errorf("VBVMR_GetVoicemeeterVersion returned %d", res)
fmt.Println(err)
os.Exit(1)
return "", err
}
v1 := (ver & 0xFF000000) >> 24
v2 := (ver & 0x00FF0000) >> 16
v3 := (ver & 0x0000FF00) >> 8
v4 := ver & 0x000000FF
return fmt.Sprintf("%d.%d.%d.%d", v1, v2, v3, v4)
return fmt.Sprintf("%d.%d.%d.%d", v1, v2, v3, v4), nil
}
// pdirty returns true iff a parameter value has changed
@@ -112,27 +115,49 @@ func mdirty() bool {
return int(res) == 1
}
// ldirty returns true iff a level value has changed
func ldirty(k *kind) bool {
_levelCache.stripLevelsBuff = make([]float64, (2*k.PhysIn)+(8*k.VirtIn))
_levelCache.busLevelsBuff = make([]float64, 8*k.NumBus())
for i := 0; i < (2*k.PhysIn)+(8*k.VirtIn); i++ {
val, _ := getLevel(_levelCache.stripMode, i)
_levelCache.stripLevelsBuff[i] = val
_levelCache.stripComp[i] = _levelCache.stripLevelsBuff[i] == _levelCache.stripLevels[i]
}
for i := 0; i < 8*k.NumBus(); i++ {
val, _ := getLevel(3, i)
_levelCache.busLevelsBuff[i] = val
_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 {
func getVMType() (string, error) {
var type_ uint64
res, _, _ := vmGetvmType.Call(
uintptr(unsafe.Pointer(&type_)),
)
if res != 0 {
err := fmt.Errorf("VBVMR_GetVoicemeeterType returned %d", res)
fmt.Println(err)
os.Exit(1)
return "", err
}
vals := map[uint64]string{
1: "basic",
2: "banana",
3: "potato",
}
return vals[type_]
return vals[type_], nil
}
// getParameterFloat gets the value of a float parameter
func getParameterFloat(name string) float64 {
func getParameterFloat(name string) (float64, error) {
if vmsync {
time.Sleep(time.Duration(vmdelay) * time.Millisecond)
for pdirty() || mdirty() {
}
}
var value float32
b := append([]byte(name), 0)
res, _, _ := vmGetParamFloat.Call(
@@ -141,29 +166,33 @@ func getParameterFloat(name string) float64 {
)
if res != 0 {
err := fmt.Errorf("VBVMR_GetParameterFloat returned %d", res)
fmt.Println(err)
os.Exit(1)
return 0, err
}
return math.Round(float64(value)*10) / 10
return math.Round(float64(value)*10) / 10, nil
}
// setParameterFloat sets the value of a float parameter
func setParameterFloat(name string, value float32) {
func setParameterFloat(name string, value float64) error {
b1 := append([]byte(name), 0)
b2 := math.Float32bits(value)
b2 := math.Float32bits(float32(value))
res, _, _ := vmSetParamFloat.Call(
uintptr(unsafe.Pointer(&b1[0])),
uintptr(b2),
)
if res != 0 {
err := fmt.Errorf("VBVMR_SetParameterFloat returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
return nil
}
// getParameterString gets the value of a string parameter
func getParameterString(name string) string {
func getParameterString(name string) (string, error) {
if vmsync {
time.Sleep(time.Duration(vmdelay) * time.Millisecond)
for pdirty() || mdirty() {
}
}
b1 := append([]byte(name), 0)
var b2 [512]byte
res, _, _ := vmGetParamString.Call(
@@ -172,15 +201,14 @@ func getParameterString(name string) string {
)
if res != 0 {
err := fmt.Errorf("VBVMR_GetParameterStringA returned %d", res)
fmt.Println(err)
os.Exit(1)
return "", err
}
str := bytes.Trim(b2[:], "\x00")
return string(str)
return string(str), nil
}
// getParameterString sets the value of a string parameter
func setParameterString(name, value string) {
// setParameterString sets the value of a string parameter
func setParameterString(name, value string) error {
b1 := append([]byte(name), 0)
b2 := append([]byte(value), 0)
res, _, _ := vmSetParamString.Call(
@@ -189,26 +217,31 @@ func setParameterString(name, value string) {
)
if res != 0 {
err := fmt.Errorf("VBVMR_SetParameterStringA returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
return nil
}
// setParametersMulti sets multiple parameters with a script
func setParametersMulti(script string) {
func setParametersMulti(script string) error {
b1 := append([]byte(script), 0)
res, _, _ := vmSetParameters.Call(
uintptr(unsafe.Pointer(&b1[0])),
)
if res != 0 {
err := fmt.Errorf("VBVMR_SetParameters returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
return nil
}
// getMacroStatus gets a macrobutton value
func getMacroStatus(id, mode int) float32 {
func getMacroStatus(id, mode int) (float64, error) {
if vmsync {
time.Sleep(time.Duration(vmdelay) * time.Millisecond)
for pdirty() || mdirty() {
}
}
var state float32
res, _, _ := vmGetMacroStatus.Call(
uintptr(id),
@@ -217,14 +250,13 @@ func getMacroStatus(id, mode int) float32 {
)
if res != 0 {
err := fmt.Errorf("VBVMR_MacroButton_GetStatus returned %d", res)
fmt.Println(err)
os.Exit(1)
return 0, err
}
return state
return float64(state), nil
}
// setMacroStatus sets a macrobutton value
func setMacroStatus(id, state, mode int) {
func setMacroStatus(id, state, mode int) error {
res, _, _ := vmSetMacroStatus.Call(
uintptr(id),
uintptr(state),
@@ -232,12 +264,13 @@ func setMacroStatus(id, state, mode int) {
)
if res != 0 {
err := fmt.Errorf("VBVMR_MacroButton_SetStatus returned %d", res)
fmt.Println(err)
os.Exit(1)
return err
}
return nil
}
func get_num_devices(dir string) uint64 {
// getNumDevices returns the number of hardware input/output devices
func getNumDevices(dir string) uint64 {
if strings.Compare(dir, "in") == 0 {
res, _, _ := vmGetDevNumIn.Call()
return uint64(res)
@@ -247,7 +280,8 @@ func get_num_devices(dir string) uint64 {
}
}
func get_device_description(i int, dir string) (string, uint64, string) {
// getDeviceDescription returns name, driver type and hwid for a given device
func getDeviceDescription(i int, dir string) (string, uint64, string, error) {
var t_ uint64
var b1 [512]byte
var b2 [512]byte
@@ -260,8 +294,7 @@ func get_device_description(i int, dir string) (string, uint64, string) {
)
if res != 0 {
err := fmt.Errorf("VBVMR_Input_GetDeviceDescA returned %d", res)
fmt.Println(err)
os.Exit(1)
return "", 0, "", err
}
} else {
res, _, _ := vmGetDevDescOut.Call(
@@ -272,17 +305,16 @@ func get_device_description(i int, dir string) (string, uint64, string) {
)
if res != 0 {
err := fmt.Errorf("VBVMR_Output_GetDeviceDescA returned %d", res)
fmt.Println(err)
os.Exit(1)
return "", 0, "", err
}
}
name := bytes.Trim(b1[:], "\x00")
hwid := bytes.Trim(b2[:], "\x00")
return string(name), t_, string(hwid)
return string(name), t_, string(hwid), nil
}
// getLevel returns a single level value of type type_ for channel[i]
func getLevel(type_, i int) float32 {
func getLevel(type_, i int) (float64, error) {
var val float32
res, _, _ := vmGetLevelFloat.Call(
uintptr(type_),
@@ -291,8 +323,41 @@ func getLevel(type_, i int) float32 {
)
if res != 0 {
err := fmt.Errorf("VBVMR_GetLevel returned %d", res)
return 0, err
}
return float64(val), nil
}
// getMidiMessage gets midi channel, pitch and velocity for a single midi input
func getMidiMessage() bool {
var midi = newMidi()
var b1 [1024]byte
res, _, _ := vmGetMidiMessage.Call(
uintptr(unsafe.Pointer(&b1[0])),
uintptr(1024),
)
x := int(res)
if x < 0 {
err := fmt.Errorf("VBVMR_GetMidiMessage returned %d", res)
if err != nil {
fmt.Println(err)
os.Exit(1)
}
return val
return false
}
msg := bytes.Trim(b1[:], "\x00")
if len(msg) > 0 {
for i := 0; i < len(msg)%3; i++ {
msg = append(msg, 0)
}
for i := 0; i < len(msg); i += 3 {
var ch = int(msg[i])
var pitch = int(msg[i+1])
var vel = int(msg[i+2])
midi.channel = ch
midi.current = pitch
midi.cache[pitch] = vel
}
}
return len(msg) > 0
}

View File

@@ -2,9 +2,11 @@ package voicemeeter
import (
"fmt"
"time"
)
type t_bus interface {
// iBus defines the interface bus types must satisfy
type iBus interface {
String() string
GetMute() bool
SetMute(val bool)
@@ -15,9 +17,11 @@ type t_bus interface {
GetLabel() string
SetLabel(val string)
GetGain() float64
SetGain(val float32)
Mode() t_busMode
SetGain(val float64)
Mode() iBusMode
Levels() *levels
FadeTo(target float32, time_ int)
FadeBy(change float32, time_ int)
}
// bus represents a bus channel
@@ -73,30 +77,44 @@ func (b *bus) GetGain() float64 {
}
// SetGain sets the value of the Gain parameter
func (b *bus) SetGain(val float32) {
func (b *bus) SetGain(val float64) {
b.setter_float("Gain", val)
}
// Mode returns address of a busMode struct
func (b *bus) Mode() t_busMode {
func (b *bus) Mode() iBusMode {
return &b.mode
}
// Levels returns the gainlayer field
// Levels returns the levels field
func (b *bus) Levels() *levels {
return &b.levels
}
// FadeTo sets the value of gain to target over at time interval of time_
func (b *bus) FadeTo(target float32, time_ int) {
b.setter_string("FadeTo", fmt.Sprintf("(\"%f\", %d)", target, time_))
time.Sleep(time.Millisecond)
}
// FadeBy adjusts the value of gain by change over a time interval of time_
func (b *bus) FadeBy(change float32, time_ int) {
b.setter_string("FadeBy", fmt.Sprintf("(\"%f\", %d)", change, time_))
time.Sleep(time.Millisecond)
}
// physicalBus represents a single physical bus
type physicalBus struct {
bus
}
func newPhysicalBus(i int, k *kind) t_bus {
// newPhysicalBus returns a physicalBus type cast to an iBus
func newPhysicalBus(i int, k *kind) iBus {
b := newBusMode(i)
l := newBusLevels(i, k)
pb := physicalBus{bus{iRemote{fmt.Sprintf("bus[%d]", i), i}, b, l}}
return t_bus(&pb)
return iBus(&pb)
}
// String implements the fmt.stringer interface
@@ -104,15 +122,17 @@ func (p *physicalBus) String() string {
return fmt.Sprintf("PhysicalBus%d", p.index)
}
// virtualBus represents a single virtual bus
type virtualBus struct {
bus
}
func newVirtualBus(i int, k *kind) t_bus {
// newVirtualBus returns a virtualBus type cast to an iBus
func newVirtualBus(i int, k *kind) iBus {
b := newBusMode(i)
l := newBusLevels(i, k)
vb := virtualBus{bus{iRemote{fmt.Sprintf("bus[%d]", i), i}, b, l}}
return t_bus(&vb)
return iBus(&vb)
}
// String implements the fmt.stringer interface
@@ -120,7 +140,8 @@ func (v *virtualBus) String() string {
return fmt.Sprintf("VirtualBus%d", v.index)
}
type t_busMode interface {
// iBusMode defines the interface busMode type must satisfy
type iBusMode interface {
SetNormal(val bool)
GetNormal() bool
SetAmix(val bool)
@@ -147,119 +168,147 @@ type t_busMode interface {
GetRearOnly() bool
}
// busMode offers methods for getting/setting bus mode states
type busMode struct {
iRemote
}
// newBusMode returns a busMode struct
func newBusMode(i int) busMode {
return busMode{iRemote{fmt.Sprintf("bus[%d].mode", i), i}}
}
func (bm *busMode) SetNormal(val bool) {
bm.setter_bool("Normal", val)
}
// GetNormal gets the value of the Mode.Normal parameter
func (bm *busMode) GetNormal() bool {
return bm.getter_bool("Normal")
}
func (bm *busMode) SetAmix(val bool) {
bm.setter_bool("Amix", val)
// SetNormal sets the value of the Mode.Normal parameter
func (bm *busMode) SetNormal(val bool) {
bm.setter_bool("Normal", val)
}
// GetAmix gets the value of the Mode.Amix parameter
func (bm *busMode) GetAmix() bool {
return bm.getter_bool("Amix")
}
func (bm *busMode) SetBmix(val bool) {
bm.setter_bool("Bmix", val)
// SetAmix sets the value of the Mode.Amix parameter
func (bm *busMode) SetAmix(val bool) {
bm.setter_bool("Amix", val)
}
// GetBmix gets the value of the Mode.Bmix parameter
func (bm *busMode) GetBmix() bool {
return bm.getter_bool("Bmix")
}
func (bm *busMode) SetRepeat(val bool) {
bm.setter_bool("Repeat", val)
// SetBmix sets the value of the Mode.Bmix parameter
func (bm *busMode) SetBmix(val bool) {
bm.setter_bool("Bmix", val)
}
// GetRepeat gets the value of the Mode.Repeat parameter
func (bm *busMode) GetRepeat() bool {
return bm.getter_bool("Repeat")
}
func (bm *busMode) SetComposite(val bool) {
bm.setter_bool("Composite", val)
// SetRepeat sets the value of the Mode.Repeat parameter
func (bm *busMode) SetRepeat(val bool) {
bm.setter_bool("Repeat", val)
}
// GetComposite gets the value of the Mode.Composite parameter
func (bm *busMode) GetComposite() bool {
return bm.getter_bool("Composite")
}
func (bm *busMode) SetTvMix(val bool) {
bm.setter_bool("TvMix", val)
// SetComposite sets the value of the Mode.Composite parameter
func (bm *busMode) SetComposite(val bool) {
bm.setter_bool("Composite", val)
}
// GetTvMix gets the value of the Mode.TvMix parameter
func (bm *busMode) GetTvMix() bool {
return bm.getter_bool("TvMix")
}
func (bm *busMode) SetUpMix21(val bool) {
bm.setter_bool("UpMix21", val)
// SetTvMix sets the value of the Mode.TvMix parameter
func (bm *busMode) SetTvMix(val bool) {
bm.setter_bool("TvMix", val)
}
// GetUpMix21 gets the value of the Mode.UpMix21 parameter
func (bm *busMode) GetUpMix21() bool {
return bm.getter_bool("UpMix21")
}
func (bm *busMode) SetUpMix41(val bool) {
bm.setter_bool("UpMix41", val)
// SetUpMix21 sets the value of the Mode.UpMix21 parameter
func (bm *busMode) SetUpMix21(val bool) {
bm.setter_bool("UpMix21", val)
}
// GetUpMix41 gets the value of the Mode.UpMix41 parameter
func (bm *busMode) GetUpMix41() bool {
return bm.getter_bool("UpMix41")
}
func (bm *busMode) SetUpMix61(val bool) {
bm.setter_bool("UpMix61", val)
// SetUpMix41 sets the value of the Mode.UpMix41 parameter
func (bm *busMode) SetUpMix41(val bool) {
bm.setter_bool("UpMix41", val)
}
// GetUpMix61 gets the value of the Mode.UpMix61 parameter
func (bm *busMode) GetUpMix61() bool {
return bm.getter_bool("UpMix61")
}
func (bm *busMode) SetCenterOnly(val bool) {
bm.setter_bool("CenterOnly", val)
// SetUpMix61 sets the value of the Mode.UpMix61 parameter
func (bm *busMode) SetUpMix61(val bool) {
bm.setter_bool("UpMix61", val)
}
// GetCenterOnly gets the value of the Mode.CenterOnly parameter
func (bm *busMode) GetCenterOnly() bool {
return bm.getter_bool("CenterOnly")
}
func (bm *busMode) SetLfeOnly(val bool) {
bm.setter_bool("LfeOnly", val)
// SetCenterOnly sets the value of the Mode.CenterOnly parameter
func (bm *busMode) SetCenterOnly(val bool) {
bm.setter_bool("CenterOnly", val)
}
// GetLfeOnly gets the value of the Mode.LFE parameter
func (bm *busMode) GetLfeOnly() bool {
return bm.getter_bool("LfeOnly")
}
func (bm *busMode) SetRearOnly(val bool) {
bm.setter_bool("RearOnly", val)
// SetLfeOnly sets the value of the Mode.LFE parameter
func (bm *busMode) SetLfeOnly(val bool) {
bm.setter_bool("LfeOnly", val)
}
// GetRearOnly gets the value of the Mode.RearOnly parameter
func (bm *busMode) GetRearOnly() bool {
return bm.getter_bool("RearOnly")
}
func newBusLevels(i int, k *kind) levels {
init := i * 8
return levels{iRemote{fmt.Sprintf("bus[%d]", i), i}, k, init, 8}
// SetRearOnly sets the value of the Mode.RearOnly parameter
func (bm *busMode) SetRearOnly(val bool) {
bm.setter_bool("RearOnly", val)
}
func (l *levels) All() []float32 {
var levels []float32
// newBusLevels represents the levels field for a channel
func newBusLevels(i int, k *kind) levels {
init := i * 8
return levels{iRemote{fmt.Sprintf("bus[%d]", i), i}, k, init, 8, "bus"}
}
// All returns the level values for a bus
func (l *levels) All() []float64 {
var levels []float64
for i := l.init; i < l.init+l.offset; i++ {
levels = append(levels, l.convertLevel(getLevel(3, i)))
levels = append(levels, convertLevel(_levelCache.busLevels[i]))
}
return levels
}

View File

@@ -2,7 +2,7 @@ package voicemeeter
import "fmt"
// custom strip type, struct forwarding channel
// button represents a single macrobuttton
type button struct {
index int
}
@@ -14,7 +14,11 @@ func newButton(i int) button {
// getter returns the value of a macrobutton parameter
func (m *button) getter(mode int) bool {
return getMacroStatus(m.index, mode) == 1
val, err := getMacroStatus(m.index, mode)
if err != nil {
fmt.Println(err)
}
return val == 1
}
// setter sets the value of a macrobutton parameter

View File

@@ -1,9 +1,11 @@
package voicemeeter
// command represents command (action) type parameters
type command struct {
iRemote
}
// newCommand returns a pointer to a command type
func newCommand() *command {
return &command{iRemote{"command", 0}}
}
@@ -29,9 +31,8 @@ func (c *command) Restart() {
}
// Lock locks or unlocks the Voiceemeter GUI
// it accepts a boolean value
func (c *command) Lock(val bool) {
var value float32
var value float64
if val {
value = 1
} else {

View File

@@ -1,5 +1,7 @@
package voicemeeter
import "fmt"
type devDesc struct {
Name, Type, Hwid string
}
@@ -13,16 +15,19 @@ func newDevice() *device {
// Ins returns the total number of physical input devices
func (d *device) Ins() int {
return int(get_num_devices("in"))
return int(getNumDevices("in"))
}
// Ins returns the total number of physical input devices
func (d *device) Outs() int {
return int(get_num_devices("out"))
return int(getNumDevices("out"))
}
func (d *device) Input(i int) devDesc {
n, t_, id := get_device_description(i, "in")
n, t_, id, err := getDeviceDescription(i, "in")
if err != nil {
fmt.Println(err)
}
vals := map[uint64]string{
1: "mme",
3: "wdm",
@@ -33,7 +38,10 @@ func (d *device) Input(i int) devDesc {
}
func (d *device) Output(i int) devDesc {
n, t_, id := get_device_description(i, "out")
n, t_, id, err := getDeviceDescription(i, "out")
if err != nil {
fmt.Println(err)
}
vals := map[uint64]string{
1: "mme",
3: "wdm",

11
examples/hotkeys/go.mod Normal file
View File

@@ -0,0 +1,11 @@
module main
go 1.19
require (
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203
github.com/onyx-and-iris/voicemeeter v1.10.0
github.com/sirupsen/logrus v1.9.0
)
require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect

21
examples/hotkeys/go.sum Normal file
View File

@@ -0,0 +1,21 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203 h1:XBBHcIb256gUJtLmY22n99HaZTz+r2Z51xUPi01m3wg=
github.com/eiannone/keyboard v0.0.0-20220611211555-0d226195f203/go.mod h1:E1jcSv8FaEny+OP/5k9UxZVw9YFWGj7eI4KR/iOBqCg=
github.com/onyx-and-iris/voicemeeter v1.9.0 h1:4LbPP9iUj/P6FPymYqyh94ysfdvOunex+i2wUDSDn6s=
github.com/onyx-and-iris/voicemeeter v1.9.0/go.mod h1:bBWCYzL//4QmH4VL3TNpNIj9W89Oa+E6J5hgyt27mKg=
github.com/onyx-and-iris/voicemeeter v1.10.0 h1:FIavzL0p33c2fyzOCtuofFOCvIv+8DrL3jFMoTr5/io=
github.com/onyx-and-iris/voicemeeter v1.10.0/go.mod h1:LayUoN/MWSqKXSOGQ7AcLvwoefsL+zQ9CjncLs3WqsU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

71
examples/hotkeys/main.go Normal file
View File

@@ -0,0 +1,71 @@
package main
import (
"fmt"
log "github.com/sirupsen/logrus"
"github.com/eiannone/keyboard"
"github.com/onyx-and-iris/voicemeeter"
)
func init() {
log.SetLevel(log.InfoLevel)
}
func main() {
if err := keyboard.Open(); err != nil {
log.Fatal(err)
}
defer func() {
_ = keyboard.Close()
}()
vm, err := vmConnect()
if err != nil {
log.Fatal(err)
}
defer vm.Logout()
fmt.Println("Press ESC to quit")
Loop:
for {
char, key, err := keyboard.GetKey()
if err != nil {
panic(err)
}
switch char {
case '0':
fmt.Printf("Logged into Voicemeeter %s, version %s\n", vm.Type(), vm.Version())
case '1':
vm.Strip[0].SetMute(!vm.Strip[0].GetMute())
case '2':
if vm.Strip[3].GetGain() == -12.8 {
vm.Strip[3].FadeBy(-8.3, 500)
} else {
vm.Strip[3].FadeTo(-12.8, 500)
}
case '3':
vm.Strip[5].AppMute("Spotify", true)
default:
if key == keyboard.KeyEsc {
break Loop
}
}
}
}
func vmConnect() (*voicemeeter.Remote, error) {
vm, err := voicemeeter.NewRemote("potato", 0)
if err != nil {
return nil, err
}
err = vm.Login()
if err != nil {
return nil, err
}
return vm, nil
}

19
examples/obs/README.md Normal file
View File

@@ -0,0 +1,19 @@
## Requirements
- [OBS Studio](https://obsproject.com/)
- [GOOBS Go Client for Websocket v5](https://github.com/andreykaipov/goobs)
## About
A simple demonstration showing how to sync OBS scene switches to Voicemeeter states. The script assumes you have connection info saved in
a config file named `config.toml` placed next to `main.go`. It also assumes you have scenes named `START` `BRB` `END` and `LIVE`.
A valid `config.toml` file might look like this:
```toml
[connection]
Host="localhost"
Port=4455
Password="mystrongpass"
```

18
examples/obs/go.mod Normal file
View File

@@ -0,0 +1,18 @@
module main
go 1.18
require (
github.com/BurntSushi/toml v1.2.0
github.com/andreykaipov/goobs v0.10.0
github.com/onyx-and-iris/voicemeeter v1.10.0
github.com/sirupsen/logrus v1.9.0
)
require (
github.com/buger/jsonparser v1.1.1 // indirect
github.com/gorilla/websocket v1.5.0 // indirect
github.com/hashicorp/logutils v1.0.0 // indirect
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d // indirect
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect
)

31
examples/obs/go.sum Normal file
View File

@@ -0,0 +1,31 @@
github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/BurntSushi/toml v1.2.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
github.com/andreykaipov/goobs v0.10.0 h1:wa4CxbYu/NqwUmx5E4/baDqYRYEmfHwg2T23RAg3jlU=
github.com/andreykaipov/goobs v0.10.0/go.mod h1:EqG73Uu/4npyhXIWWszgRelNkEeIz+d0slUT6NKWYs4=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y=
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d h1:VhgPp6v9qf9Agr/56bj7Y/xa04UccTW04VP0Qed4vnQ=
github.com/nu7hatch/gouuid v0.0.0-20131221200532-179d4d0c4d8d/go.mod h1:YUTz3bUH2ZwIWBy3CJBeOBEugqcmXREj14T+iG/4k4U=
github.com/onyx-and-iris/voicemeeter v1.9.0 h1:4LbPP9iUj/P6FPymYqyh94ysfdvOunex+i2wUDSDn6s=
github.com/onyx-and-iris/voicemeeter v1.9.0/go.mod h1:bBWCYzL//4QmH4VL3TNpNIj9W89Oa+E6J5hgyt27mKg=
github.com/onyx-and-iris/voicemeeter v1.10.0 h1:FIavzL0p33c2fyzOCtuofFOCvIv+8DrL3jFMoTr5/io=
github.com/onyx-and-iris/voicemeeter v1.10.0/go.mod h1:LayUoN/MWSqKXSOGQ7AcLvwoefsL+zQ9CjncLs3WqsU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

131
examples/obs/main.go Normal file
View File

@@ -0,0 +1,131 @@
package main
import (
"fmt"
"os"
"time"
log "github.com/sirupsen/logrus"
"github.com/onyx-and-iris/voicemeeter"
"github.com/andreykaipov/goobs"
"github.com/andreykaipov/goobs/api/events"
"github.com/BurntSushi/toml"
)
func onStart(vm *voicemeeter.Remote) {
vm.Strip[0].SetMute(true)
vm.Strip[1].SetB1(true)
vm.Strip[2].SetB1(true)
}
func onBrb(vm *voicemeeter.Remote) {
vm.Strip[7].FadeTo(0, 500)
vm.Bus[0].SetMute(true)
}
func onLive(vm *voicemeeter.Remote) {
vm.Strip[0].SetMute(false)
vm.Strip[7].FadeTo(-6, 500)
vm.Strip[7].SetA3(true)
vm.Vban.InStream[0].SetOn(true)
}
func onEnd(vm *voicemeeter.Remote) {
vm.Strip[0].SetMute(true)
vm.Strip[1].SetMute(true)
vm.Strip[1].SetB1(false)
vm.Strip[2].SetMute(true)
vm.Strip[2].SetB1(false)
vm.Vban.InStream[0].SetOn(false)
}
func init() {
log.SetLevel(log.InfoLevel)
}
func main() {
vm, err := vmConnect()
if err != nil {
log.Fatal(err)
}
defer vm.Logout()
obs, err := obsConnect()
if err != nil {
log.Fatal(err)
}
defer obs.Disconnect()
version, _ := obs.General.GetVersion()
fmt.Printf("OBS Studio version: %s\n", version.ObsVersion)
fmt.Printf("Websocket server version: %s\n", version.ObsWebSocketVersion)
go obs.Listen(func(event any) {
switch e := event.(type) {
case *events.CurrentProgramSceneChanged:
fmt.Printf("Switched to scene %s\n", e.SceneName)
switch e.SceneName {
case "START":
onStart(vm)
case "BRB":
onBrb(vm)
case "LIVE":
onLive(vm)
case "END":
onEnd(vm)
}
}
})
time.Sleep(30 * time.Second)
}
func vmConnect() (*voicemeeter.Remote, error) {
vm, err := voicemeeter.NewRemote("potato", 0)
if err != nil {
return nil, err
}
err = vm.Login()
if err != nil {
return nil, err
}
return vm, nil
}
func obsConnect() (*goobs.Client, error) {
type (
connection struct {
Host string
Port int
Password string
}
config struct {
Connection map[string]connection
}
)
f := "config.toml"
if _, err := os.Stat(f); err != nil {
err := fmt.Errorf("unable to locate %s", f)
return nil, err
}
var c config
_, err := toml.DecodeFile(f, &c.Connection)
if err != nil {
return nil, err
}
conn := c.Connection["connection"]
obs, err := goobs.New(fmt.Sprintf("%s:%d", conn.Host, conn.Port), goobs.WithPassword(conn.Password))
if err != nil {
return nil, err
}
return obs, nil
}

85
examples/observer/main.go Normal file
View File

@@ -0,0 +1,85 @@
package main
import (
"fmt"
"time"
log "github.com/sirupsen/logrus"
"github.com/onyx-and-iris/voicemeeter"
)
// observer represents a single receiver of updates
type observer struct {
vm *voicemeeter.Remote
}
// newObserver add ldirty events to the eventlist and returns an observer type
func newObserver(vm *voicemeeter.Remote) *observer {
vm.EventAdd("ldirty")
return &observer{vm}
}
// register registers this observer to receive updates
func (o observer) register() {
o.vm.Register(o)
}
// deregister deregisters this observer to receive updates
func (o observer) deregister() {
o.vm.Deregister(o)
}
// OnUpdate satisfies the observer interface defined in publisher.go
// for each event type an action is triggered when the event occurs.
func (o observer) OnUpdate(subject string) {
if subject == "pdirty" {
fmt.Println("pdirty!")
} else if subject == "mdirty" {
fmt.Println("mdirty!")
} else if subject == "midi" {
var current = o.vm.Midi.Current()
var val = o.vm.Midi.Get(current)
fmt.Printf("Value of midi button %d: %d\n", current, val)
} else if subject == "ldirty" {
for _, bus := range o.vm.Bus {
if bus.Levels().IsDirty() {
fmt.Println(bus, bus.Levels().All())
}
}
}
}
func init() {
log.SetLevel(log.InfoLevel)
}
// main connects to Voiceemeter, registers observer for updates
// runs updates for 30 seconds and then deregisters observer.
func main() {
vm, err := vmConnect()
if err != nil {
log.Fatal(err)
}
defer vm.Logout()
o := newObserver(vm)
o.register()
time.Sleep(30 * time.Second)
o.deregister()
}
// vmConnect connects to Voicemeeter potato and logs into the API
func vmConnect() (*voicemeeter.Remote, error) {
vm, err := voicemeeter.NewRemote("potato", 0)
if err != nil {
return nil, err
}
err = vm.Login()
if err != nil {
return nil, err
}
return vm, nil
}

View File

@@ -1,38 +0,0 @@
package main
import (
"fmt"
"time"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter"
)
type observer struct {
i int
}
func (o observer) OnUpdate(subject string) {
fmt.Println(o.i, subject)
}
func main() {
vmRem := voicemeeter.GetRemote("banana")
vmRem.Login()
o := observer{1}
o2 := observer{2}
o3 := observer{3}
o4 := observer{4}
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)
vmRem.Logout()
}

33
examples/vm-cli/README.md Normal file
View File

@@ -0,0 +1,33 @@
## About
A simple voicemeeter-cli program. Offers ability to toggle, get and set parameters.
## Install
First build and install it with `go install` (skip this step if using binary from [Releases](https://github.com/onyx-and-iris/voicemeeter/releases))
## Use
Toggle with `!` prefix, get by excluding `=` and set by including `=`. Mix and match arguments.
You may pass the following optional flags:
- -v: (-verbose) to toggle console output.
- -i: (-interactive) to toggle interactive mode.
- -k: (-kind) to set the kind of Voicemeeter. Defaults to banana.
- -d: (-delay) to set a delay on the getters. Defaults to 20ms.
for example:
`vm-cli.exe -v -k=potato -d=25 strip[0].mute=0 strip[0].mute !strip[0].mute strip[0].mute bus[0].gain=-8.8 command.lock=1`
Expected output:
```
Running command strip[0].mute=0
Value of strip[0].mute is: 0
Toggling strip[0].mute
Value of strip[0].mute is: 1
Running command bus[0].gain=-8.8
Running command command.lock=1
```

152
examples/vm-cli/main.go Normal file
View File

@@ -0,0 +1,152 @@
package main
import (
"bufio"
"flag"
"fmt"
"log"
"os"
"strings"
"github.com/onyx-and-iris/voicemeeter"
)
func main() {
var (
kind string
delay int
verbose bool
interactive bool
)
flag.StringVar(&kind, "kind", "banana", "kind of voicemeeter")
flag.StringVar(&kind, "k", "banana", "kind of voicemeeter (shorthand)")
flag.IntVar(&delay, "delay", 20, "delay between commands")
flag.IntVar(&delay, "d", 20, "delay between commands (shorthand)")
flag.BoolVar(&verbose, "verbose", false, "toggle console output")
flag.BoolVar(&verbose, "v", false, "toggle console output (shorthand)")
flag.BoolVar(&interactive, "interactive", false, "toggle interactive mode")
flag.BoolVar(&interactive, "i", false, "toggle interactive mode (shorthand)")
flag.Parse()
vm, err := vmConnect(kind, delay)
if err != nil {
log.Fatal(err)
}
defer vm.Logout()
err = runCommands(vm, verbose, interactive)
if err != nil {
fmt.Println(err)
}
}
func vmConnect(kind string, delay int) (*voicemeeter.Remote, error) {
vm, err := voicemeeter.NewRemote(kind, delay)
if err != nil {
return nil, err
}
err = vm.Login()
if err != nil {
return nil, err
}
return vm, nil
}
func runCommands(vm *voicemeeter.Remote, verbose, interactive bool) error {
if interactive {
return interactiveMode(vm, verbose)
}
for _, arg := range flag.Args() {
err := parse(vm, arg, verbose)
if err != nil {
return err
}
}
return nil
}
func interactiveMode(vm *voicemeeter.Remote, verbose bool) error {
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
input := scanner.Text()
if input == "q" || input == "quit" || input == "" {
return nil
}
for _, cmd := range strings.Split(input, " ") {
err := parse(vm, cmd, verbose)
if err != nil {
return err
}
}
}
return nil
}
func parse(vm *voicemeeter.Remote, cmd string, verbose bool) error {
if cmd[0] == '!' {
err := toggleCmd(vm, cmd[1:], verbose)
if err != nil {
return err
}
} else {
if strings.Contains(cmd, "=") {
err := setCmd(vm, cmd, verbose)
if err != nil {
return err
}
} else {
err := getCmd(vm, cmd, verbose)
if err != nil {
return err
}
}
}
return nil
}
func toggleCmd(vm *voicemeeter.Remote, cmd string, verbose bool) error {
val, err := vm.GetFloat(cmd)
if err != nil {
err = fmt.Errorf("unable to toggle %s", cmd)
return err
}
vm.SetFloat(cmd[1:], 1-val)
if verbose {
fmt.Println("Toggling", cmd)
}
return nil
}
func setCmd(vm *voicemeeter.Remote, cmd string, verbose bool) error {
if verbose {
fmt.Println("Running command", cmd)
}
err := vm.SendText(cmd)
if err != nil {
err = fmt.Errorf("unable to set %s", cmd)
return err
}
return nil
}
func getCmd(vm *voicemeeter.Remote, cmd string, verbose bool) error {
valF, err := vm.GetFloat(cmd)
if err != nil {
valS, err := vm.GetString(cmd)
if err != nil {
err = fmt.Errorf("unable to get %s", cmd)
return err
}
if verbose {
fmt.Println("Value of", cmd, "is:", valS)
}
} else {
if verbose {
fmt.Println("Value of", cmd, "is:", valF)
}
}
return nil
}

10
go.mod
View File

@@ -1,10 +1,14 @@
module github.com/onyx-and-iris/voicemeeter-api-go
module github.com/onyx-and-iris/voicemeeter
go 1.18
// package files moved into root of repository
retract [v1.0.0, v1.1.0]
require (
github.com/stretchr/testify v1.7.5
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664
github.com/sirupsen/logrus v1.9.0
github.com/stretchr/testify v1.8.0
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8
)
require (

11
go.sum
View File

@@ -3,13 +3,16 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
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.0/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.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-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/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=

22
helper_test.go Normal file
View File

@@ -0,0 +1,22 @@
package voicemeeter
import (
"log"
"os"
"testing"
)
var (
vm, err = NewRemote("potato", 30)
)
func TestMain(m *testing.M) {
if err != nil {
log.Fatal(err)
}
vm.Login()
code := m.Run()
vm.Logout()
os.Exit(code)
}

View File

@@ -2,15 +2,16 @@ package voicemeeter
import (
"fmt"
"math"
)
// iRemote provides a set of common forwarding methods
// iRemote provides an interface between higher methods and lower functions
// expected to be embedded
type iRemote struct {
_identifier string
index int
}
// identifier returns a string identifier
func (ir *iRemote) identifier() string {
return ir._identifier
}
@@ -18,7 +19,11 @@ func (ir *iRemote) identifier() string {
// getter_bool returns the value of a boolean parameter
func (ir *iRemote) getter_bool(p string) bool {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
return getParameterFloat(param) == 1
val, err := getParameterFloat(param)
if err != nil {
fmt.Println(err)
}
return val == 1
}
// setter_bool sets the value of a boolean parameter
@@ -30,56 +35,65 @@ func (ir *iRemote) setter_bool(p string, v bool) {
} else {
value = 0
}
setParameterFloat(param, float32(value))
err := setParameterFloat(param, float64(value))
if err != nil {
fmt.Println(err)
}
}
// getter_int returns the value of an int parameter p
func (ir *iRemote) getter_int(p string) int {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
return int(getParameterFloat(param))
val, err := getParameterFloat(param)
if err != nil {
fmt.Println(err)
}
return int(val)
}
// setter_int sets the value v of an int parameter p
func (ir *iRemote) setter_int(p string, v int) {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
setParameterFloat(param, float32(v))
err := setParameterFloat(param, float64(v))
if err != nil {
fmt.Println(err)
}
}
// getter_float returns the value of an int parameter p
func (ir *iRemote) getter_float(p string) float64 {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
return getParameterFloat(param)
val, err := getParameterFloat(param)
if err != nil {
fmt.Println(err)
}
return val
}
// setter_float sets the value v of an int parameter p
func (ir *iRemote) setter_float(p string, v float32) {
func (ir *iRemote) setter_float(p string, v float64) {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
setParameterFloat(param, float32(v))
err := setParameterFloat(param, float64(v))
if err != nil {
fmt.Println(err)
}
}
// getter_string returns the value of a string parameter p
func (ir *iRemote) getter_string(p string) string {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
return getParameterString(param)
val, err := getParameterString(param)
if err != nil {
fmt.Println(err)
}
return val
}
// setter_string sets the value v of a string parameter p
func (ir *iRemote) setter_string(p, v string) {
param := fmt.Sprintf("%s.%s", ir.identifier(), p)
setParameterString(param, v)
err := setParameterString(param, v)
if err != nil {
fmt.Println(err)
}
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
}

View File

@@ -9,25 +9,25 @@ var basic, banana, potato *kind
// A kind represents a Voicemeeter kinds layout
type kind struct {
name string
physIn, virtIn, physOut, virtOut, vbanIn, vbanOut int
Name string
PhysIn, VirtIn, PhysOut, VirtOut, VbanIn, VbanOut int
}
// numStrip returns the total number of strips for a kind
func (k *kind) numStrip() int {
n := k.physIn + k.virtIn
func (k *kind) NumStrip() int {
n := k.PhysIn + k.VirtIn
return n
}
// numBus returns the total number of buses for a kind
func (k *kind) numBus() int {
n := k.physOut + k.virtOut
func (k *kind) NumBus() int {
n := k.PhysOut + k.VirtOut
return n
}
// String implements the fmt.stringer interface
func (k *kind) String() string {
return fmt.Sprintf("%s%s", strings.ToUpper(k.name[:1]), k.name[1:])
return fmt.Sprintf("%s%s", strings.ToUpper(k.Name[:1]), k.Name[1:])
}
// newBasicKind returns a basic kind struct address

46
levels.go Normal file
View File

@@ -0,0 +1,46 @@
package voicemeeter
// levels represents the levels field for a channel
type levels struct {
iRemote
k *kind
init int
offset int
id string
}
// returns true if any levels value for a strip/bus have been updated
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)
}
var _levelCache *levelCache
// levelCache defines level slices used by the pooler to track updates
type levelCache struct {
stripMode int
stripLevels []float64
busLevels []float64
stripLevelsBuff []float64
busLevelsBuff []float64
stripComp []bool
busComp []bool
}
// newLevelCache returns a levelCache struct address
func newLevelCache(k *kind) *levelCache {
stripLevels := make([]float64, (2*k.PhysIn)+(8*k.VirtIn))
busLevels := make([]float64, 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
}

28
midi.go Normal file
View File

@@ -0,0 +1,28 @@
package voicemeeter
var midi *midi_t
type midi_t struct {
channel int
current int
cache map[int]int
}
func newMidi() *midi_t {
if midi == nil {
midi = &midi_t{0, 0, map[int]int{}}
}
return midi
}
func (m *midi_t) Channel() int {
return m.channel
}
func (m *midi_t) Current() int {
return m.current
}
func (m *midi_t) Get(key int) int {
return m.cache[key]
}

View File

@@ -1,6 +1,7 @@
package voicemeeter
type t_outputs interface {
// iOutputs defines the interface outputs type must satisfy
type iOutputs interface {
GetA1() bool
SetA1(val bool)
GetA2() bool
@@ -19,10 +20,13 @@ type t_outputs interface {
SetB3(val bool)
}
// outputs represents the outputs field (A1 - A5, B1 - B3)
// expected to be embedded
type outputs struct {
iRemote
}
// newOutputs returns an outputs type
func newOutputs(id string, i int) outputs {
o := outputs{iRemote{id, i}}
return o

View File

@@ -3,7 +3,7 @@ package voicemeeter
import (
"errors"
"fmt"
"os"
"log"
"path/filepath"
"runtime"
"strings"
@@ -49,8 +49,7 @@ func dllPath() (string, error) {
func getDllPath() string {
path, err := dllPath()
if err != nil {
fmt.Println(err)
os.Exit(1)
log.Fatal(err)
}
return path
}

140
publisher.go Normal file
View File

@@ -0,0 +1,140 @@
package voicemeeter
import (
"time"
)
// observer defines the interface any registered observers must satisfy
type observer interface {
OnUpdate(subject string)
}
// publisher defines methods that support observers
type publisher struct {
observerList []observer
}
// Register adds an observer to observerList
func (p *publisher) Register(o observer) {
p.observerList = append(p.observerList, o)
}
// Deregister removes an observer from observerList
func (p *publisher) Deregister(o observer) {
var indexToRemove int
for i, observer := range p.observerList {
if observer == o {
indexToRemove = i
break
}
}
p.observerList = append(p.observerList[:indexToRemove], p.observerList[indexToRemove+1:]...)
}
// notify updates observers of any changes
func (p *publisher) notify(subject string) {
for _, observer := range p.observerList {
observer.OnUpdate(subject)
}
}
type event struct {
pdirty bool
mdirty bool
midi bool
ldirty bool
}
func newEvent() *event {
return &event{true, true, true, false}
}
func (e *event) Add(ev string) {
switch ev {
case "pdirty":
e.pdirty = true
case "mdirty":
e.mdirty = true
case "midi":
e.midi = true
case "ldirty":
e.ldirty = true
}
}
func (e *event) Remove(ev string) {
switch ev {
case "pdirty":
e.pdirty = false
case "mdirty":
e.mdirty = false
case "midi":
e.midi = false
case "ldirty":
e.ldirty = false
}
}
// pooler continuously polls the dirty paramters
// it is expected to be run in a goroutine
type pooler struct {
k *kind
run bool
event *event
publisher
}
func newPooler(k *kind) *pooler {
p := &pooler{
k: k,
run: true,
event: newEvent(),
}
go p.parameters()
go p.macrobuttons()
go p.midi()
go p.levels()
return p
}
func (p *pooler) parameters() {
for p.run {
if p.event.pdirty && pdirty() {
p.notify("pdirty")
}
time.Sleep(33 * time.Millisecond)
}
}
func (p *pooler) macrobuttons() {
for p.run {
if p.event.mdirty && mdirty() {
p.notify("mdirty")
}
time.Sleep(33 * time.Millisecond)
}
}
func (p *pooler) midi() {
for p.run {
if p.event.midi && getMidiMessage() {
p.notify("midi")
}
time.Sleep(33 * time.Millisecond)
}
}
func (p *pooler) levels() {
_levelCache = newLevelCache(p.k)
for p.run {
if p.event.ldirty && 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

@@ -1,10 +1,12 @@
package voicemeeter
// recorder represents the recorder
type recorder struct {
iRemote
outputs
}
// newRecorder returns an address to a recorder struct
func newRecorder() *recorder {
o := newOutputs("recorder", 0)
return &recorder{iRemote{"recorder", 0}, o}

372
remote.go Normal file
View File

@@ -0,0 +1,372 @@
package voicemeeter
import (
"fmt"
"os"
"time"
log "github.com/sirupsen/logrus"
)
// A Remote type represents the API for a kind
type Remote struct {
Kind *kind
Strip []iStrip
Bus []iBus
Button []button
Command *command
Vban *vban
Device *device
Recorder *recorder
Midi *midi_t
pooler *pooler
}
// String implements the fmt.stringer interface
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() error {
err := login(r.Kind.Name)
if err != nil {
return err
}
r.pooler = newPooler(r.Kind)
return nil
}
// Logout logs out of the API
// it also terminates the pooler
func (r *Remote) Logout() error {
r.pooler.run = false
err := logout(r.Kind.Name)
if err != nil {
return err
}
return nil
}
// Type returns the type of Voicemeeter (basic, banana, potato)
func (r *Remote) Type() string {
val, err := getVMType()
if err != nil {
fmt.Println(err)
}
return val
}
// Version returns the version of Voicemeeter as a string
func (r *Remote) Version() string {
val, err := getVersion()
if err != nil {
fmt.Println(err)
}
return val
}
// Pdirty returns true iff a parameter value has changed
func (r *Remote) Pdirty() bool {
return pdirty()
}
// Mdirty returns true iff a macrobutton value has changed
func (r *Remote) Mdirty() bool {
return mdirty()
}
// Sync is a helper method that waits for dirty parameters to clear
func (r *Remote) Sync() {
time.Sleep(time.Duration(vmdelay) * time.Millisecond)
for r.Pdirty() || r.Mdirty() {
}
}
// Gets a float parameter value
func (r *Remote) GetFloat(name string) (float64, error) {
val, err := getParameterFloat(name)
if err != nil {
return 0, err
}
return val, nil
}
// Sets a float paramter value
func (r *Remote) SetFloat(name string, value float64) error {
err := setParameterFloat(name, value)
if err != nil {
return err
}
return nil
}
// Gets a string parameter value
func (r *Remote) GetString(name string) (string, error) {
val, err := getParameterString(name)
if err != nil {
return "", err
}
return val, nil
}
// Sets a string paramter value
func (r *Remote) SetString(name, value string) error {
err := setParameterString(name, value)
if err != nil {
return err
}
return nil
}
// SendText sets multiple parameters by script
func (r *Remote) SendText(script string) error {
err := setParametersMulti(script)
if err != nil {
return err
}
return nil
}
// Register forwards the register method to Pooler
func (r *Remote) Register(o observer) {
r.pooler.Register(o)
}
// Deregister forwards the deregister method to Pooler
func (r *Remote) Deregister(o observer) {
r.pooler.Deregister(o)
}
// EventAdd adds an event to the Pooler
func (r *Remote) EventAdd(event string) {
r.pooler.event.Add(event)
}
// EventRemove removes an event from the Pooler
func (r *Remote) EventRemove(event string) {
r.pooler.event.Remove(event)
}
// remoteBuilder defines the interface builder types must satisfy
type remoteBuilder interface {
setKind() remoteBuilder
makeStrip() remoteBuilder
makeBus() remoteBuilder
makeButton() remoteBuilder
makeCommand() remoteBuilder
makeVban() remoteBuilder
makeDevice() remoteBuilder
makeRecorder() remoteBuilder
makeMidi() remoteBuilder
Build() remoteBuilder
Get() *Remote
}
// directory is responsible for directing the genericBuilder
type director struct {
builder remoteBuilder
}
// SetBuilder sets the appropriate builder type for a kind
func (d *director) SetBuilder(b remoteBuilder) {
d.builder = b
}
// Construct calls the build function for the specific builder
func (d *director) Construct() {
d.builder.Build()
}
// Get forwards the Get method to the builder
func (d *director) Get() *Remote {
return d.builder.Get()
}
// genericBuilder represents a generic builder type
type genericBuilder struct {
k *kind
r Remote
}
// setKind sets the kind for a builder of a kind
func (b *genericBuilder) setKind() remoteBuilder {
b.r.Kind = b.k
return b
}
// makeStrip makes a strip slice and assigns it to remote.Strip
// []iStrip comprises of both physical and virtual strip types
func (b *genericBuilder) makeStrip() remoteBuilder {
log.Info("building strip")
_strip := make([]iStrip, b.k.NumStrip())
for i := 0; i < b.k.NumStrip(); i++ {
if i < b.k.PhysIn {
_strip[i] = newPhysicalStrip(i, b.k)
} else {
_strip[i] = newVirtualStrip(i, b.k)
}
}
b.r.Strip = _strip
return b
}
// makeBus makes a bus slice and assigns it to remote.Bus
// []t_bus comprises of both physical and virtual bus types
func (b *genericBuilder) makeBus() remoteBuilder {
log.Info("building bus")
_bus := make([]iBus, b.k.NumBus())
for i := 0; i < b.k.NumBus(); i++ {
if i < b.k.PhysOut {
_bus[i] = newPhysicalBus(i, b.k)
} else {
_bus[i] = newVirtualBus(i, b.k)
}
}
b.r.Bus = _bus
return b
}
// makeButton makes a button slice and assigns it to remote.Button
func (b *genericBuilder) makeButton() remoteBuilder {
log.Info("building button")
_button := make([]button, 80)
for i := 0; i < 80; i++ {
_button[i] = newButton(i)
}
b.r.Button = _button
return b
}
// makeCommand makes a command type and assigns it to remote.Command
func (b *genericBuilder) makeCommand() remoteBuilder {
log.Info("building command")
b.r.Command = newCommand()
return b
}
// makeVban makes a vban type and assigns it to remote.Vban
func (b *genericBuilder) makeVban() remoteBuilder {
log.Info("building vban")
b.r.Vban = newVban(b.k)
return b
}
// makeDevice makes a device type and assigns it to remote.Device
func (b *genericBuilder) makeDevice() remoteBuilder {
log.Info("building device")
b.r.Device = newDevice()
return b
}
// makeRecorder makes a recorder type and assigns it to remote.Recorder
func (b *genericBuilder) makeRecorder() remoteBuilder {
log.Info("building recorder")
b.r.Recorder = newRecorder()
return b
}
// makeMidi makes a midi type and assigns it to remote.Midi
func (b *genericBuilder) makeMidi() remoteBuilder {
log.Info("building midi")
b.r.Midi = newMidi()
return b
}
// Get returns a fully constructed remote type for a kind
func (b *genericBuilder) Get() *Remote {
return &b.r
}
// basicBuilder represents a builder specific to basic type
type basicBuilder struct {
genericBuilder
}
// Build defines the steps required to build a basic type
func (basb *genericBuilder) Build() remoteBuilder {
return basb.setKind().
makeStrip().
makeBus().
makeButton().
makeCommand().
makeVban().
makeDevice().
makeMidi()
}
// bananaBuilder represents a builder specific to banana type
type bananaBuilder struct {
genericBuilder
}
// Build defines the steps required to build a banana type
func (banb *bananaBuilder) Build() remoteBuilder {
return banb.setKind().
makeStrip().
makeBus().
makeButton().
makeCommand().
makeVban().
makeDevice().
makeRecorder().
makeMidi()
}
// potatoBuilder represents a builder specific to potato type
type potatoBuilder struct {
genericBuilder
}
// Build defines the steps required to build a potato type
func (potb *potatoBuilder) Build() remoteBuilder {
return potb.setKind().
makeStrip().
makeBus().
makeButton().
makeCommand().
makeVban().
makeDevice().
makeRecorder().
makeMidi()
}
var (
vmsync bool
vmdelay int
)
func init() {
log.SetOutput(os.Stdout)
log.SetLevel(log.WarnLevel)
}
// NewRemote returns a Remote type for a kind
// this is the interface entry point
func NewRemote(kindId string, delay int) (*Remote, error) {
_kind, ok := kindMap[kindId]
if !ok {
err := fmt.Errorf("unknown Voicemeeter kind '%s'", kindId)
return nil, err
}
if delay < 0 {
err := fmt.Errorf("invalid delay value. should be >= 0")
return nil, err
}
vmsync = delay > 0
vmdelay = delay
director := director{}
switch _kind.Name {
case "basic":
director.SetBuilder(&basicBuilder{genericBuilder{_kind, Remote{}}})
case "banana":
director.SetBuilder(&bananaBuilder{genericBuilder{_kind, Remote{}}})
case "potato":
director.SetBuilder(&potatoBuilder{genericBuilder{_kind, Remote{}}})
}
director.Construct()
return director.Get(), nil
}

View File

@@ -8,7 +8,7 @@ import (
func TestGetBasicRemote(t *testing.T) {
//t.Skip("skipping test")
__rem := GetRemote("basic")
__rem, _ := NewRemote("basic", 0)
t.Run("Should return a remote basic type", func(t *testing.T) {
assert.NotNil(t, __rem)
})
@@ -34,7 +34,7 @@ func TestGetBasicRemote(t *testing.T) {
func TestGetBananaRemote(t *testing.T) {
//t.Skip("skipping test")
__rem := GetRemote("banana")
__rem, _ := NewRemote("banana", 0)
t.Run("Should return a remote banana type", func(t *testing.T) {
assert.NotNil(t, __rem)
})
@@ -60,7 +60,7 @@ func TestGetBananaRemote(t *testing.T) {
func TestGetPotatoRemote(t *testing.T) {
//t.Skip("skipping test")
__rem := GetRemote("potato")
__rem, _ := NewRemote("potato", 0)
t.Run("Should return a remote basic type", func(t *testing.T) {
assert.NotNil(t, __rem)
})
@@ -83,3 +83,25 @@ func TestGetPotatoRemote(t *testing.T) {
assert.NotNil(t, __rem.Recorder)
})
}
func TestSetAndGetFloatParameter(t *testing.T) {
//t.Skip("skipping test")
var param = "strip[0].mute"
var exp = float64(1)
vm.SetFloat(param, 1)
t.Run("Should get a float parameter", func(t *testing.T) {
val, _ := vm.GetFloat(param)
assert.Equal(t, exp, val)
})
}
func TestSetAndGetStringParameter(t *testing.T) {
//t.Skip("skipping test")
var param = "strip[0].label"
var exp = "test0"
vm.SetString(param, exp)
t.Run("Should get a string parameter", func(t *testing.T) {
val, _ := vm.GetString(param)
assert.Equal(t, exp, val)
})
}

View File

@@ -2,9 +2,11 @@ package voicemeeter
import (
"fmt"
"time"
)
type t_strip interface {
// iStrip defines the interface bus types must satisfy
type iStrip interface {
String() string
GetMute() bool
SetMute(val bool)
@@ -17,18 +19,22 @@ type t_strip interface {
GetLabel() string
SetLabel(val string)
GetGain() float64
SetGain(val float32)
SetGain(val float64)
GetMc() bool
SetMc(val bool)
GetComp() float64
SetComp(val float32)
SetComp(val float64)
GetGate() float64
SetGate(val float32)
SetGate(val float64)
GetAudibility() float64
SetAudibility(val float32)
SetAudibility(val float64)
GainLayer() []gainLayer
Levels() *levels
t_outputs
FadeTo(target float64, time_ int)
FadeBy(change float64, time_ int)
AppGain(name string, gain float64)
AppMute(name string, val bool)
iOutputs
}
// strip represents a strip channel
@@ -95,7 +101,7 @@ func (s *strip) GetGain() float64 {
}
// SetGain sets the value of the Gain parameter
func (s *strip) SetGain(val float32) {
func (s *strip) SetGain(val float64) {
s.setter_float("Gain", val)
}
@@ -104,16 +110,30 @@ func (s *strip) GainLayer() []gainLayer {
return s.gainLayer
}
// Levels returns the gainlayer field
// Levels returns the levels field
func (s *strip) Levels() *levels {
return &s.levels
}
// FadeTo sets the value of gain to target over at time interval of time_
func (s *strip) FadeTo(target float64, time_ int) {
s.setter_string("FadeTo", fmt.Sprintf("(\"%f\", %d)", target, time_))
time.Sleep(time.Millisecond)
}
// FadeBy adjusts the value of gain by change over a time interval of time_
func (s *strip) FadeBy(change float64, time_ int) {
s.setter_string("FadeBy", fmt.Sprintf("(\"%f\", %d)", change, time_))
time.Sleep(time.Millisecond)
}
// physicalStrip represents a single physical strip
type physicalStrip struct {
strip
}
func newPhysicalStrip(i int, k *kind) t_strip {
// newPhysicalStrip returns a physicalStrip type cast to an iStrip
func newPhysicalStrip(i int, k *kind) iStrip {
o := newOutputs(fmt.Sprintf("strip[%d]", i), i)
gl := make([]gainLayer, 8)
for j := 0; j < 8; j++ {
@@ -121,10 +141,10 @@ func newPhysicalStrip(i int, k *kind) t_strip {
}
l := newStripLevels(i, k)
ps := physicalStrip{strip{iRemote{fmt.Sprintf("strip[%d]", i), i}, o, gl, l}}
return t_strip(&ps)
return iStrip(&ps)
}
// implement fmt.stringer interface in fmt
// String implements fmt.stringer interface
func (p *physicalStrip) String() string {
return fmt.Sprintf("PhysicalStrip%d", p.index)
}
@@ -135,7 +155,7 @@ func (p *physicalStrip) GetComp() float64 {
}
// SetComp sets the value of the Comp parameter
func (p *physicalStrip) SetComp(val float32) {
func (p *physicalStrip) SetComp(val float64) {
p.setter_float("Comp", val)
}
@@ -145,7 +165,7 @@ func (p *physicalStrip) GetGate() float64 {
}
// SetGate sets the value of the Gate parameter
func (p *physicalStrip) SetGate(val float32) {
func (p *physicalStrip) SetGate(val float64) {
p.setter_float("Gate", val)
}
@@ -155,7 +175,7 @@ func (p *physicalStrip) GetAudibility() float64 {
}
// SetAudibility sets the value of the Audibility parameter
func (p *physicalStrip) SetAudibility(val float32) {
func (p *physicalStrip) SetAudibility(val float64) {
p.setter_float("Audibility", val)
}
@@ -169,11 +189,13 @@ func (p *physicalStrip) SetMc(val bool) {
panic("invalid parameter MC for physicalStrip")
}
// virtualStrip represents a single virtual strip
type virtualStrip struct {
strip
}
func newVirtualStrip(i int, k *kind) t_strip {
// newVirtualStrip returns a virtualStrip type cast to an iStrip
func newVirtualStrip(i int, k *kind) iStrip {
o := newOutputs(fmt.Sprintf("strip[%d]", i), i)
gl := make([]gainLayer, 8)
for j := 0; j < 8; j++ {
@@ -181,10 +203,10 @@ func newVirtualStrip(i int, k *kind) t_strip {
}
l := newStripLevels(i, k)
vs := virtualStrip{strip{iRemote{fmt.Sprintf("strip[%d]", i), i}, o, gl, l}}
return t_strip(&vs)
return iStrip(&vs)
}
// implement fmt.stringer interface in fmt
// String implements fmt.stringer interface
func (v *virtualStrip) String() string {
return fmt.Sprintf("VirtualStrip%d", v.index)
}
@@ -205,7 +227,7 @@ func (v *virtualStrip) GetComp() float64 {
}
// SetComp panics reason invalid parameter
func (v *virtualStrip) SetComp(val float32) {
func (v *virtualStrip) SetComp(val float64) {
panic("invalid parameter Comp for virtualStrip")
}
@@ -215,7 +237,7 @@ func (v *virtualStrip) GetGate() float64 {
}
// SetGate panics reason invalid parameter
func (v *virtualStrip) SetGate(val float32) {
func (v *virtualStrip) SetGate(val float64) {
panic("invalid parameter Gate for virtualStrip")
}
@@ -225,60 +247,87 @@ func (v *virtualStrip) GetAudibility() float64 {
}
// SetAudibility panics reason invalid parameter
func (v *virtualStrip) SetAudibility(val float32) {
func (v *virtualStrip) SetAudibility(val float64) {
panic("invalid parameter Audibility for virtualStrip")
}
// AppGain sets the gain in db by val for the app matching name.
func (v *strip) AppGain(name string, val float64) {
v.setter_string("AppGain", fmt.Sprintf("(\"%s\", %f)", name, val))
}
// AppMute sets mute state as val for the app matching name.
func (v *strip) AppMute(name string, val bool) {
var value int
if val {
value = 1
} else {
value = 0
}
v.setter_string("AppMute", fmt.Sprintf("(\"%s\", %f)", name, float64(value)))
}
// gainLayer represents the 8 gainlayers for a single strip
type gainLayer struct {
iRemote
index int
}
// newGainLayer returns a gainlayer struct
func newGainLayer(i, j int) gainLayer {
return gainLayer{iRemote{fmt.Sprintf("strip[%d]", i), i}, j}
}
// Get gets the gain value for a single gainlayer
func (gl *gainLayer) Get() float64 {
return gl.getter_float(fmt.Sprintf("gainlayer[%d]", gl.index))
}
func (gl *gainLayer) Set(val float32) {
// Set sets the gain value for a single gainlayer
func (gl *gainLayer) Set(val float64) {
gl.setter_float(fmt.Sprintf("gainlayer[%d]", gl.index), val)
}
// newStripLevels returns a levels struct
func newStripLevels(i int, k *kind) levels {
var init int
var os int
if i < k.physIn {
if i < k.PhysIn {
init = i * 2
os = 2
} else {
init = (k.physIn * 2) + ((i - k.physIn) * 8)
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 {
var levels []float32
// PreFader returns the level values for this strip, PREFADER mode
func (l *levels) PreFader() []float64 {
_levelCache.stripMode = 0
var levels []float64
for i := l.init; i < l.init+l.offset; i++ {
levels = append(levels, l.convertLevel(getLevel(0, i)))
levels = append(levels, convertLevel(_levelCache.stripLevels[i]))
}
return levels
}
func (l *levels) PostFader() []float32 {
var levels []float32
// PostFader returns the level values for this strip, POSTFADER mode
func (l *levels) PostFader() []float64 {
_levelCache.stripMode = 1
var levels []float64
for i := l.init; i < l.init+l.offset; i++ {
levels = append(levels, l.convertLevel(getLevel(1, i)))
levels = append(levels, convertLevel(_levelCache.stripLevels[i]))
}
return levels
}
func (l *levels) PostMute() []float32 {
var levels []float32
// PostMute returns the level values for this strip, POSTMUTE mode
func (l *levels) PostMute() []float64 {
_levelCache.stripMode = 2
var levels []float64
for i := l.init; i < l.init+l.offset; i++ {
levels = append(levels, l.convertLevel(getLevel(2, i)))
levels = append(levels, convertLevel(_levelCache.stripLevels[i]))
}
return levels
}

View File

@@ -1,26 +1,26 @@
package voicemeeter_test
import (
"os"
"log"
"testing"
"time"
"github.com/onyx-and-iris/voicemeeter-api-go/voicemeeter"
"github.com/onyx-and-iris/voicemeeter"
)
var (
vmRem = voicemeeter.GetRemote("potato")
vm, err = voicemeeter.NewRemote("potato", 30)
)
func TestMain(m *testing.M) {
vmRem.Login()
code := m.Run()
vmRem.Logout()
os.Exit(code)
if err != nil {
log.Fatal(err)
}
func sync() {
time.Sleep(30 * time.Millisecond)
for vmRem.Pdirty() || vmRem.Mdirty() {
err = vm.Login()
if err != nil {
log.Fatal(err)
}
defer vm.Logout()
m.Run()
}

View File

@@ -8,206 +8,179 @@ import (
func TestStrip0Mute(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[0].SetMute(true)
sync()
vm.Strip[0].SetMute(true)
t.Run("Should return true when SetMute(true)", func(t *testing.T) {
assert.True(t, vmRem.Strip[0].GetMute())
assert.True(t, vm.Strip[0].GetMute())
})
vmRem.Strip[0].SetMute(false)
sync()
vm.Strip[0].SetMute(false)
t.Run("Should return false when SetMute(false)", func(t *testing.T) {
assert.False(t, vmRem.Strip[0].GetMute())
assert.False(t, vm.Strip[0].GetMute())
})
}
func TestStrip3A1(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[3].SetA1(true)
sync()
vm.Strip[3].SetA1(true)
t.Run("Should return true when SetA1(true)", func(t *testing.T) {
assert.True(t, vmRem.Strip[3].GetA1())
assert.True(t, vm.Strip[3].GetA1())
})
vmRem.Strip[3].SetA1(false)
sync()
vm.Strip[3].SetA1(false)
t.Run("Should return false when SetA1(false)", func(t *testing.T) {
assert.False(t, vmRem.Strip[3].GetA1())
assert.False(t, vm.Strip[3].GetA1())
})
}
func TestStrip2Limit(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[2].SetLimit(-8)
sync()
vm.Strip[2].SetLimit(-8)
t.Run("Should return -8 when SetLimit(-8)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[2].GetLimit(), -8)
assert.Equal(t, vm.Strip[2].GetLimit(), -8)
})
vmRem.Strip[2].SetLimit(-32)
sync()
vm.Strip[2].SetLimit(-32)
t.Run("Should return -32 when SetLimit(-8)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[2].GetLimit(), -32)
assert.Equal(t, vm.Strip[2].GetLimit(), -32)
})
}
func TestStrip4Label(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[4].SetLabel("test0")
sync()
vm.Strip[4].SetLabel("test0")
t.Run("Should return test0 when SetLimit('test0')", func(t *testing.T) {
assert.Equal(t, "test0", vmRem.Strip[4].GetLabel())
assert.Equal(t, "test0", vm.Strip[4].GetLabel())
})
vmRem.Strip[4].SetLabel("test1")
sync()
vm.Strip[4].SetLabel("test1")
t.Run("Should return test1 when SetLimit('test1')", func(t *testing.T) {
assert.Equal(t, "test1", vmRem.Strip[4].GetLabel())
assert.Equal(t, "test1", vm.Strip[4].GetLabel())
})
}
func TestStrip5Gain(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[4].SetGain(-20.8)
sync()
vm.Strip[4].SetGain(-20.8)
t.Run("Should return -20.8 when SetGain(-20.8)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[4].GetGain(), -20.8)
assert.Equal(t, vm.Strip[4].GetGain(), -20.8)
})
vmRem.Strip[4].SetGain(-3.6)
sync()
vm.Strip[4].SetGain(-3.6)
t.Run("Should return -3.6 when SetGain(-3.6)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[4].GetGain(), -3.6)
assert.Equal(t, vm.Strip[4].GetGain(), -3.6)
})
}
func TestStrip3Comp(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[4].SetComp(8.1)
sync()
vm.Strip[4].SetComp(8.1)
t.Run("Should return 8.1 when SetGain(8.1)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[4].GetComp(), 8.1)
assert.Equal(t, vm.Strip[4].GetComp(), 8.1)
})
vmRem.Strip[4].SetComp(1.6)
sync()
vm.Strip[4].SetComp(1.6)
t.Run("Should return 1.6 when SetGain(1.6)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[4].GetComp(), 1.6)
assert.Equal(t, vm.Strip[4].GetComp(), 1.6)
})
}
func TestStrip5Mc(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[5].SetMc(true)
sync()
vm.Strip[5].SetMc(true)
t.Run("Should return true when SetMc(true)", func(t *testing.T) {
assert.True(t, vmRem.Strip[5].GetMc())
assert.True(t, vm.Strip[5].GetMc())
})
vmRem.Strip[5].SetMc(false)
sync()
vm.Strip[5].SetMc(false)
t.Run("Should return false when SetMc(false)", func(t *testing.T) {
assert.False(t, vmRem.Strip[5].GetMc())
assert.False(t, vm.Strip[5].GetMc())
})
}
func TestStrip2GainLayer3(t *testing.T) {
//t.Skip("skipping test")
vmRem.Strip[2].GainLayer()[3].Set(-18.3)
sync()
vm.Strip[2].GainLayer()[3].Set(-18.3)
t.Run("Should return -18.3 when SetMc(true)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[2].GainLayer()[3].Get(), -18.3)
assert.Equal(t, vm.Strip[2].GainLayer()[3].Get(), -18.3)
})
vmRem.Strip[2].GainLayer()[3].Set(-25.6)
sync()
vm.Strip[2].GainLayer()[3].Set(-25.6)
t.Run("Should return -25.6 when SetMc(true)", func(t *testing.T) {
assert.Equal(t, vmRem.Strip[2].GainLayer()[3].Get(), -25.6)
assert.Equal(t, vm.Strip[2].GainLayer()[3].Get(), -25.6)
})
}
func TestBus3Eq(t *testing.T) {
//t.Skip("skipping test")
vmRem.Bus[3].SetEq(true)
sync()
vm.Bus[3].SetEq(true)
t.Run("Should return true when SetEq(true)", func(t *testing.T) {
assert.True(t, vmRem.Bus[3].GetEq())
assert.True(t, vm.Bus[3].GetEq())
})
vmRem.Bus[3].SetEq(false)
sync()
vm.Bus[3].SetEq(false)
t.Run("Should return false when SetEq(false)", func(t *testing.T) {
assert.False(t, vmRem.Bus[3].GetEq())
assert.False(t, vm.Bus[3].GetEq())
})
}
func TestBus4Label(t *testing.T) {
//t.Skip("skipping test")
vmRem.Bus[4].SetLabel("test0")
sync()
vm.Bus[4].SetLabel("test0")
t.Run("Should return test0 when SetEq('test0')", func(t *testing.T) {
assert.Equal(t, "test0", vmRem.Bus[4].GetLabel())
assert.Equal(t, "test0", vm.Bus[4].GetLabel())
})
vmRem.Bus[4].SetLabel("test1")
sync()
vm.Bus[4].SetLabel("test1")
t.Run("Should return test1 when SetEq('test1')", func(t *testing.T) {
assert.Equal(t, "test1", vmRem.Bus[4].GetLabel())
assert.Equal(t, "test1", vm.Bus[4].GetLabel())
})
}
func TestBus3ModeAmix(t *testing.T) {
//t.Skip("skipping test")
vmRem.Bus[3].Mode().SetAmix(true)
sync()
vm.Bus[3].Mode().SetAmix(true)
t.Run("Should return true when Mode().SetAmix(true)", func(t *testing.T) {
assert.True(t, vmRem.Bus[3].Mode().GetAmix())
assert.True(t, vm.Bus[3].Mode().GetAmix())
})
}
func TestVbanInStream0On(t *testing.T) {
//t.Skip("skipping test")
vmRem.Vban.InStream[0].SetOn(true)
sync()
vm.Vban.InStream[0].SetOn(true)
t.Run("Should return true when SetOn(true)", func(t *testing.T) {
assert.True(t, vmRem.Vban.InStream[0].GetOn())
assert.True(t, vm.Vban.InStream[0].GetOn())
})
vmRem.Vban.InStream[0].SetOn(false)
sync()
vm.Vban.InStream[0].SetOn(false)
t.Run("Should return false when SetOn(false)", func(t *testing.T) {
assert.False(t, vmRem.Vban.InStream[0].GetOn())
assert.False(t, vm.Vban.InStream[0].GetOn())
})
}
func TestVbanOutStream6On(t *testing.T) {
//t.Skip("skipping test")
vmRem.Vban.OutStream[6].SetOn(true)
sync()
vm.Vban.OutStream[6].SetOn(true)
t.Run("Should return true when SetOn(true)", func(t *testing.T) {
assert.True(t, vmRem.Vban.OutStream[6].GetOn())
assert.True(t, vm.Vban.OutStream[6].GetOn())
})
vmRem.Vban.OutStream[6].SetOn(false)
sync()
vm.Vban.OutStream[6].SetOn(false)
t.Run("Should return false when SetOn(false)", func(t *testing.T) {
assert.False(t, vmRem.Vban.OutStream[6].GetOn())
assert.False(t, vm.Vban.OutStream[6].GetOn())
})
}
func TestVbanOutStream3Name(t *testing.T) {
t.Skip("skipping test")
vmRem.Vban.OutStream[3].SetName("test0")
sync()
vm.Vban.OutStream[3].SetName("test0")
t.Run("Should return test0 when SetName('test0')", func(t *testing.T) {
assert.Equal(t, "test0", vmRem.Vban.OutStream[3].GetName())
assert.Equal(t, "test0", vm.Vban.OutStream[3].GetName())
})
vmRem.Vban.OutStream[3].SetName("test1")
sync()
vm.Vban.OutStream[3].SetName("test1")
t.Run("Should return test1 when SetName('test1')", func(t *testing.T) {
assert.Equal(t, "test1", vmRem.Vban.OutStream[3].GetName())
assert.Equal(t, "test1", vm.Vban.OutStream[3].GetName())
})
}
@@ -219,21 +192,19 @@ func TestVbanInStream4Bit(t *testing.T) {
t.Error("expected panic")
}
}()
vmRem.Vban.InStream[4].SetBit(16)
vm.Vban.InStream[4].SetBit(16)
})
}
func TestVbanOutStream4Bit(t *testing.T) {
//t.Skip("skipping test")
vmRem.Vban.OutStream[4].SetBit(16)
sync()
vm.Vban.OutStream[4].SetBit(16)
t.Run("Should return 16 when SetBit(16)", func(t *testing.T) {
assert.Equal(t, vmRem.Vban.OutStream[4].GetBit(), 16)
assert.Equal(t, vm.Vban.OutStream[4].GetBit(), 16)
})
vmRem.Vban.OutStream[4].SetBit(24)
sync()
vm.Vban.OutStream[4].SetBit(24)
t.Run("Should return 24 when SetBit(24)", func(t *testing.T) {
assert.Equal(t, vmRem.Vban.OutStream[4].GetBit(), 24)
assert.Equal(t, vm.Vban.OutStream[4].GetBit(), 24)
})
}

View File

@@ -1,6 +1,6 @@
Function RunTests {
$run_int_tests = "go clean -testcache; go test -v ."
$run_ext_tests = "go clean -testcache; go test -v .\tests\"
$run_int_tests = "go clean -testcache; go test -v .\voicemeeter\"
Invoke-Expression $run_ext_tests
Invoke-Expression $run_int_tests

35
util.go Normal file
View File

@@ -0,0 +1,35 @@
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
}
// update copies the contents of one float slice into another
func update(s1 []float64, s2 []float64, sz int) {
for i := 0; i < sz; i++ {
s1[i] = s2[i]
}
}
// roundFloat rounds a float value to a given precision
func roundFloat(val float64, precision uint) float64 {
ratio := math.Pow(10, float64(precision))
return math.Round(val*ratio) / ratio
}
// convertLevel performs the necessary math for a channel level
func convertLevel(i float64) float64 {
if i > 0 {
val := 20 * math.Log10(i)
return roundFloat(val, 1)
}
return -200.0
}

41
util_test.go Normal file
View File

@@ -0,0 +1,41 @@
package voicemeeter
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestAllTrue(t *testing.T) {
//t.Skip("skipping test")
s := []bool{true, true, true, true, true, true}
t.Run("Should return true", func(t *testing.T) {
assert.True(t, allTrue(s, len(s)))
})
s = []bool{true, true, true, true, false, true}
t.Run("Should return false", func(t *testing.T) {
assert.False(t, allTrue(s, len(s)))
})
}
func TestUpdate(t *testing.T) {
//t.Skip("skipping test")
s1 := []float64{3.6, 8.7, 1.8, 18.2}
s2 := make([]float64, len(s1))
update(s2, s1, len(s1))
t.Run("Should return true", func(t *testing.T) {
assert.Equal(t, s1, s2)
})
}
func TestConvertLevel(t *testing.T) {
//t.Skip("skipping test")
res := convertLevel(0.02)
t.Run("Should be equal", func(t *testing.T) {
assert.Equal(t, float64(-34), res)
})
res = convertLevel(-0.02)
t.Run("Should be equal", func(t *testing.T) {
assert.Equal(t, float64(-200), res)
})
}

View File

@@ -2,7 +2,8 @@ package voicemeeter
import "fmt"
type t_vban interface {
// iVban defines the interface vban types must satisfy
type iVban interface {
GetOn() bool
SetOn(val bool)
GetName() string
@@ -133,9 +134,9 @@ type vbanInStream struct {
vbanStream
}
func newVbanInStream(i int) t_vban {
func newVbanInStream(i int) iVban {
vbi := vbanInStream{vbanStream{iRemote{fmt.Sprintf("vban.instream[%d]", i), i}}}
return t_vban(&vbi)
return iVban(&vbi)
}
// SetSr panics reason read only
@@ -157,23 +158,23 @@ type vbanOutStream struct {
vbanStream
}
func newVbanOutStream(i int) t_vban {
func newVbanOutStream(i int) iVban {
vbo := vbanOutStream{vbanStream{iRemote{fmt.Sprintf("vban.outstream[%d]", i), i}}}
return t_vban(&vbo)
return iVban(&vbo)
}
type vban struct {
InStream []t_vban
OutStream []t_vban
InStream []iVban
OutStream []iVban
}
func newVban(k *kind) *vban {
_vbanIn := make([]t_vban, k.vbanIn)
for i := 0; i < k.vbanIn; i++ {
_vbanIn := make([]iVban, k.VbanIn)
for i := 0; i < k.VbanIn; i++ {
_vbanIn[i] = newVbanInStream(i)
}
_vbanOut := make([]t_vban, k.vbanOut)
for i := 0; i < k.vbanOut; i++ {
_vbanOut := make([]iVban, k.VbanOut)
for i := 0; i < k.VbanOut; i++ {
_vbanOut[i] = newVbanOutStream(i)
}
return &vban{

View File

@@ -1,68 +0,0 @@
package voicemeeter
import (
"time"
)
// observer defines the interface any registered observers must satisfy
type observer interface {
OnUpdate(subject string)
}
// publisher defines methods that support observers
type publisher struct {
observerList []observer
}
// Register adds an observer to observerList
func (p *publisher) Register(o observer) {
p.observerList = append(p.observerList, o)
}
// Deregister removes an observer from observerList
func (p *publisher) Deregister(o observer) {
var indexToRemove int
for i, observer := range p.observerList {
if observer == o {
indexToRemove = i
break
}
}
p.observerList = append(p.observerList[:indexToRemove], p.observerList[indexToRemove+1:]...)
}
// notify updates observers of any changes
func (p *publisher) notify(subject string) {
for _, observer := range p.observerList {
observer.OnUpdate(subject)
}
}
// pooler continuously polls the dirty paramters
// it is expected to be run in a goroutine
type pooler struct {
run bool
publisher
}
func newPooler() *pooler {
p := &pooler{
run: true,
}
go p.runner()
return p
}
func (p *pooler) runner() {
for p.run {
if pdirty() {
p.notify("pdirty")
}
if mdirty() {
p.notify("mdirty")
}
time.Sleep(33 * time.Millisecond)
}
}

View File

@@ -1,241 +0,0 @@
package voicemeeter
import (
"fmt"
"os"
)
// A remote type represents the API for a kind,
// comprised of slices representing each member
type remote struct {
kind *kind
Strip []t_strip
Bus []t_bus
Button []button
Command *command
Vban *vban
Device *device
Recorder *recorder
pooler *pooler
}
// String implements the fmt.stringer interface
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()
login(r.kind.name)
}
// Logout logs out of the API
// it also terminates the pooler
func (r *remote) Logout() {
r.pooler.run = false
logout()
}
func (r *remote) Type() string {
return getVMType()
}
func (r *remote) Version() string {
return getVersion()
}
// Pdirty returns true iff a parameter value has changed
func (r *remote) Pdirty() bool {
return pdirty()
}
// Mdirty returns true iff a macrobutton value has changed
func (r *remote) Mdirty() bool {
return mdirty()
}
func (r *remote) SendText(script string) {
setParametersMulti(script)
}
// Register forwards the register method to Pooler
func (r *remote) Register(o observer) {
r.pooler.Register(o)
}
// Register forwards the deregister method to Pooler
func (r *remote) Deregister(o observer) {
r.pooler.Deregister(o)
}
type remoteBuilder interface {
setKind() remoteBuilder
makeStrip() remoteBuilder
makeBus() remoteBuilder
makeButton() remoteBuilder
makeCommand() remoteBuilder
makeVban() remoteBuilder
makeDevice() remoteBuilder
makeRecorder() remoteBuilder
Build() remoteBuilder
Get() *remote
}
// directory is responsible for directing the genericBuilder
type director struct {
builder remoteBuilder
}
// SetBuilder sets the appropriate builder type for a kind
func (d *director) SetBuilder(b remoteBuilder) {
d.builder = b
}
// Construct calls the build function for the specific builder
func (d *director) Construct() {
d.builder.Build()
}
// Get forwards the Get method to the builder
func (d *director) Get() *remote {
return d.builder.Get()
}
type genericBuilder struct {
k *kind
r remote
}
func (b *genericBuilder) setKind() remoteBuilder {
b.r.kind = b.k
return b
}
// makeStrip makes a strip slice and assigns it to remote.Strip
// []t_strip comprises of both physical and virtual strip types
func (b *genericBuilder) makeStrip() remoteBuilder {
fmt.Println("building strip")
_strip := make([]t_strip, b.k.numStrip())
for i := 0; i < b.k.numStrip(); i++ {
if i < b.k.physIn {
_strip[i] = newPhysicalStrip(i, b.k)
} else {
_strip[i] = newVirtualStrip(i, b.k)
}
}
b.r.Strip = _strip
return b
}
// makeBus makes a bus slice and assigns it to remote.Bus
// []t_bus comprises of both physical and virtual bus types
func (b *genericBuilder) makeBus() remoteBuilder {
fmt.Println("building bus")
_bus := make([]t_bus, b.k.numBus())
for i := 0; i < b.k.numBus(); i++ {
if i < b.k.physOut {
_bus[i] = newPhysicalBus(i, b.k)
} else {
_bus[i] = newVirtualBus(i, b.k)
}
}
b.r.Bus = _bus
return b
}
// makeButton makes a button slice and assigns it to remote.Button
func (b *genericBuilder) makeButton() remoteBuilder {
fmt.Println("building button")
_button := make([]button, 80)
for i := 0; i < 80; i++ {
_button[i] = newButton(i)
}
b.r.Button = _button
return b
}
// 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
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
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
func (b *genericBuilder) makeRecorder() remoteBuilder {
fmt.Println("building recorder")
b.r.Recorder = newRecorder()
return b
}
// Get returns a fully constructed remote type for a kind
func (b *genericBuilder) Get() *remote {
return &b.r
}
type basicBuilder struct {
genericBuilder
}
// Build defines the steps required to build a basic type
func (basb *genericBuilder) Build() remoteBuilder {
return basb.setKind().makeStrip().makeBus().makeButton().makeCommand().makeVban().makeDevice()
}
type bananaBuilder struct {
genericBuilder
}
// Build defines the steps required to build a banana type
func (banb *bananaBuilder) Build() remoteBuilder {
return banb.setKind().makeStrip().makeBus().makeButton().makeCommand().makeVban().makeDevice().makeRecorder()
}
type potatoBuilder struct {
genericBuilder
}
// Build defines the steps required to build a potato type
func (potb *potatoBuilder) Build() remoteBuilder {
return potb.setKind().makeStrip().makeBus().makeButton().makeCommand().makeVban().makeDevice().makeRecorder()
}
// GetRemote returns a remote type for a kind
// this is the interface entry point
func GetRemote(kindId string) *remote {
_kind, ok := kindMap[kindId]
if !ok {
err := fmt.Errorf("unknown Voicemeeter kind '%s'", kindId)
fmt.Println(err)
os.Exit(1)
}
director := director{}
switch _kind.name {
case "basic":
director.SetBuilder(&basicBuilder{genericBuilder{_kind, remote{}}})
case "banana":
director.SetBuilder(&bananaBuilder{genericBuilder{_kind, remote{}}})
case "potato":
director.SetBuilder(&potatoBuilder{genericBuilder{_kind, remote{}}})
}
director.Construct()
return director.Get()
}