19 Commits

Author SHA1 Message Date
bd0779add2 add Taskfile
upd tasks in launch.json

add with Task to Run tests in README
2025-06-06 13:50:16 +01:00
a0a2c72634 run through formatter
rename pre-commit to run

remove num and log parameters
2025-06-06 13:49:35 +01:00
0f68a2373d run through formatter 2025-06-06 13:48:24 +01:00
2d6437d37b run through formatter 2025-06-06 13:48:11 +01:00
f199fa587f add Voicemeeter + OBS button example 2025-06-05 20:19:16 +01:00
b6c9c65390 update requirements with note about different scriptdeck versions 2025-06-05 20:10:19 +01:00
436b47a5db upd example 2025-06-05 16:43:30 +01:00
0cdd71600f reword 2025-06-05 01:58:02 +01:00
41529c0d58 add module installation note 2025-06-05 01:55:53 +01:00
b1a6ac68c1 add stream deck example README 2025-06-05 01:51:15 +01:00
fbfab5b4aa 3.3.0 section added to CHANGELOG
added dates for past versions
2024-06-29 10:03:32 +01:00
4d371a7582 Remove the 1 second wait from RunVoicemeeter
Write exception message to Debug
2024-06-29 07:13:11 +01:00
15b3b375bd md fix 2024-06-29 06:55:48 +01:00
c8abc6964a update RunVoicemeeter to launch x64 bit GUIs for all kinds
Keep testing login for up to 2 seconds.
If timeout exceeded throw VMRemoteError
2024-06-29 06:53:20 +01:00
907ee3e63b upd tested against 2024-06-28 11:12:01 +01:00
f3ed9c28c7 upd doc link 2024-01-03 09:38:22 +00:00
d305a4048d "\" -Join path parts 2023-08-17 15:02:03 +01:00
108731b4cf add RunMacrobuttons(), CloseMacrobuttons() 2023-08-17 03:19:05 +01:00
e7c648f1d0 fix function names 2023-08-17 03:05:32 +01:00
25 changed files with 405 additions and 288 deletions

5
.gitignore vendored
View File

@@ -1,7 +1,6 @@
# quick test
quick.ps1
lib/*.psd1
**/*.log
config.psd1
test-*.ps1

14
.vscode/launch.json vendored
View File

@@ -51,21 +51,9 @@
"type": "PowerShell",
"request": "launch",
"cwd": "${workspaceRoot}",
"script": "${workspaceFolder}/tests/pre-commit.ps1",
"script": "${workspaceFolder}/tests/run.ps1",
"args": [],
"createTemporaryIntegratedConsole": true
},
{
"name": "PowerShell: Launch Quick Test",
"type": "PowerShell",
"request": "launch",
"cwd": "${workspaceRoot}",
"script": "${workspaceFolder}/quick.ps1",
"args": [
"-Verbose",
"-Debug"
],
"createTemporaryIntegratedConsole": true
}
]
}

View File

@@ -9,7 +9,19 @@ Before any major/minor/patch is released all test units will be run to verify th
## [Unreleased] These changes have not been added to PSGallery yet
## [3.2.0]
- [ ]
## [3.3.0] - 2024-06-29
### Added
- Add a timeout (2s) to the login function. If timeout exceeded a VMRemoteError is thrown.
### Changed
- Launch x64 bit GUIs for all kinds if on 64 bit system.
## [3.2.0] - 2023-08-17
### Added
@@ -21,10 +33,10 @@ Before any major/minor/patch is released all test units will be run to verify th
- All CAPIErrors are now exposed to the consumer.
- The function name and error code can be retrieved using [CAPIError].function and [CAPIError].code
- Set_By_Script now throws [VMError] if script length exceeds 48kB.
- Set_By_Script now throws [VMRemoteError] if script length exceeds 48kB.
- parameter range checks in Vban class.
## [3.1.0]
## [3.1.0] - 2023-08-15
### Added
@@ -33,7 +45,7 @@ Before any major/minor/patch is released all test units will be run to verify th
- More Recorder commands implemented. See Recorder section in README.
- RunMacrobuttons, CloseMacrobuttons added to Special class
## [3.0.0]
## [3.0.0] - 2023-08-09
v3 introduces some breaking changes. They are as follows:

View File

@@ -8,9 +8,9 @@ For past/future changes to this project refer to: [CHANGELOG](CHANGELOG.md)
## Tested against
- Basic 1.0.8.8
- Banana 2.0.6.8
- Potato 3.0.2.8
- Basic 1.1.1.1
- Banana 2.1.1.1
- Potato 3.1.1.1
## Requirements
@@ -402,6 +402,8 @@ The following commands are available:
The following methods are available:
- Load($filepath): string
- RunMacrobuttons(): Launches the macrobuttons app
- CloseMacrobuttons(): Closes the macrobuttons app
example:
@@ -411,6 +413,8 @@ $vmr.command.show
$vmr.command.lock = $true
$vmr.command.Load("path/to/filename.xml")
$vmr.command.RunMacrobuttons()
```
### Recorder
@@ -553,17 +557,17 @@ Access to lower level polling functions are provided with these functions:
### Run tests
Run tests using .\tests\pre-commit.ps1 which accepts the following parameters:
Parameters:
- `kind`: Run tests of this kind
- `tag`: Run tests tagged with this marker (currently `higher` or `lower`)
- `num`: Run this number of tests
- `log`: Write summary log file
Run tests from repository root in a subshell and write logs, like so:
*with Task*
`powershell .\tests\pre-commit.ps1 -k "potato" -t "higher" -log`
```console
task test -- -t "higher" -k "banana"
```
### Official Documentation
- [Voicemeeter Remote C API](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/update-docs/VoicemeeterRemoteAPI.pdf)
- [Voicemeeter Remote C API](https://github.com/onyx-and-iris/Voicemeeter-SDK/blob/main/VoicemeeterRemoteAPI.pdf)

11
Taskfile.yaml Normal file
View File

@@ -0,0 +1,11 @@
version: '3'
tasks:
test:
desc: 'Run tests'
preconditions:
- sh: 'pwsh -c "if ([System.Version](Get-InstalledModule Pester).Version.ToString() -gt [System.Version]"5.7.0") { exit 0 } else { exit 1 }"'
msg: 'Pester version must be greater than 5.7.0'
cmds:
- echo "Running tests..."
- pwsh -c "tests\run.ps1 {{.CLI_ARGS}}"

View File

@@ -1,7 +1,7 @@
[cmdletbinding()]
param(
[switch]$interactive,
[String]$kind = "banana",
[String]$kind = 'banana',
[String[]]$script = @()
)
@@ -20,20 +20,20 @@ function get-value {
function msgHandler {
param([object]$vmr, [string]$line)
$line + " passed to handler" | Write-Debug
if ($line[0] -eq "!") {
"Toggling " + $line.substring(1) | Write-Debug
$line + ' passed to handler' | Write-Debug
if ($line[0] -eq '!') {
'Toggling ' + $line.substring(1) | Write-Debug
$retval = get-value -vmr $vmr -line $line.substring(1)
$vmr.Setter($line.substring(1), 1 - $retval)
}
elseif ($line.Contains("=")) {
elseif ($line.Contains('=')) {
"Setting $line" | Write-Debug
$vmr.SendText($line)
}
else {
"Getting $line" | Write-Debug
$retval = get-value -vmr $vmr -line $line
$line + " = " + $retval | Write-Host
$line + ' = ' + $retval | Write-Host
}
}
@@ -50,7 +50,7 @@ function main {
$vmr = Connect-Voicemeeter -Kind $kind
if ($interactive) {
"Press <Enter> to exit" | Write-Host
'Press <Enter> to exit' | Write-Host
read-hostuntilempty -vmr $vmr
return
}

View File

@@ -13,14 +13,14 @@ param()
Import-Module ..\..\lib\Voicemeeter.psm1
try {
$vmr = Connect-Voicemeeter -Kind "potato"
$vmr = Connect-Voicemeeter -Kind 'potato'
$buses = @($vmr.bus[1], $vmr.bus[2], $vmr.bus[4], $vmr.bus[6])
"Buses in selection: $($buses)"
$unmutedIndex = $null
# 1)
"Cycling through bus selection to check for first unmuted Bus..." | Write-Host
'Cycling through bus selection to check for first unmuted Bus...' | Write-Host
foreach ($bus in $buses) {
# 2)
if (-not $bus.mute) {

View File

@@ -6,28 +6,28 @@ Import-Module obs-powershell
function CurrentProgramSceneChanged {
param([System.Object]$data)
Write-Host "Switched to scene", $data.sceneName
Write-Host 'Switched to scene', $data.sceneName
switch ($data.sceneName) {
"START" {
'START' {
$vmr.strip[0].mute = !$vmr.strip[0].mute
}
"BRB" {
'BRB' {
$vmr.strip[0].gain = -8.3
}
"END" {
'END' {
$vmr.strip[0].mono = $true
}
"LIVE" {
'LIVE' {
$vmr.strip[0].color_x = 0.3
}
default { "Expected START, BRB, END or LIVE scene" | Write-Warning; return }
default { 'Expected START, BRB, END or LIVE scene' | Write-Warning; return }
}
}
function ExitStarted {
param([System.Object]$data)
"OBS shutdown has begun!" | Write-Host
'OBS shutdown has begun!' | Write-Host
break
}
@@ -38,12 +38,12 @@ function eventHandler($data) {
}
function ConnFromFile {
$configpath = Join-Path $PSScriptRoot "config.psd1"
$configpath = Join-Path $PSScriptRoot 'config.psd1'
return Import-PowerShellDataFile -Path $configpath
}
function main {
$vmr = Connect-Voicemeeter -Kind "basic"
$vmr = Connect-Voicemeeter -Kind 'basic'
$conn = ConnFromFile
$job = Watch-OBS -WebSocketURI "ws://$($conn.host):$($conn.port)" -WebSocketToken $conn.password

View File

@@ -0,0 +1,125 @@
# About
Thanks to the guys at [Start Automating](https://startautomating.com/) it's possible to use this module straight from your Stream Deck.
## Requirements
### ScriptDeck
*Windows Powershell*
- [Windows ScriptDeck](https://marketplace.elgato.com/product/windows-scriptdeck-857f01dd-8fd4-44d5-8ec7-67ac850b21d3)
*Powershell core*
- [ScriptDeck](https://marketplace.elgato.com/product/scriptdeck-927e59aa-b42d-4da7-84cc-8c78f4dd7e18)
Note, even though one of them is named Windows they both work on Windows for different powershell versions, see [this issue](https://github.com/StartAutomating/ScriptDeck/issues/120)
### Voicemeeter API Powershell
- Install it as a module, see [Installation](https://github.com/onyx-and-iris/voicemeeter-api-powershell?tab=readme-ov-file#installation)
## How
Once ScriptDeck is installed create a button using *Powershell Script*, then:
### On one button
Due to the design of Voicemeeter's API you may only login/logout once per session so in order to program multiple buttons you must do the following for just ONE button (it can be any button).
#### Button 1
*When Loaded*
```powershell
$global:vmr = Connect-Voicemeeter -Kind "banana"
```
*When Unloaded*
```powershell
Disconnect-Voicemeeter
```
*When Pressed*
```powershell
if ($vmr.strip[0].mute) {
$vmr.bus[0].mute=1
$vmr.bus[1].mute=1
} else {
$vmr.bus[0].mute=0
$vmr.bus[1].mute=0
}
```
### Other buttons
Then your other buttons can have any scripts using the `$vmr` object:
#### Button 2
*When Pressed*
```powershell
$vmr.strip[1].mute=1
$vmr.strip[2].mute=1
if (-not $vmr.strip[0].mute) {
$vmr.strip[0].mute=1
}
```
#### Button 3
*When Pressed*
```powershell
$vmr.strip[0].mute=$(-not $vmr.strip[0].mute)
$vmr.strip[1].mute=$(-not $vmr.strip[1].mute)
$vmr.strip[2].mute=$(-not $vmr.strip[2].mute)
```
---
Then let's say you have zillions of buttons you want to program, for each Stream Deck window configure ONE button as described above and the other buttons of the same window as described above.
If this explanation is unclear or you'd like me to add some screenshots just ask.
## Leveraging Powershell
Since we're now working with Powershell we can do some useful things, for example, lets create a button that interacts with Voicemeeter and OBS:
First make sure you've installed [obs-powershell](https://github.com/StartAutomating/obs-powershell).
Now let's create a button that only toggles some strip mutes if the current OBS scene is "LIVE".
#### Button
*When Loaded*
```powershell
$global:vmr = Connect-Voicemeeter -Kind "banana"
Connect-OBS -WebSocketToken <websocket token>
```
*When Unloaded*
```powershell
Disconnect-Voicemeeter
Disconnect-OBS
```
*When Pressed*
```powershell
$currentScene = $(Get-OBSCurrentProgramScene | Select-Object -ExpandProperty currentProgramSceneName)
if ($currentScene -eq "LIVE") {
$vmr.strip[0].mute=$(-not $vmr.strip[0].mute)
$vmr.strip[1].mute=$(-not $vmr.strip[1].mute)
$vmr.strip[2].mute=$(-not $vmr.strip[2].mute)
}
```

View File

@@ -22,7 +22,7 @@ class Remote {
}
[string] ToString() {
return "Voicemeeter " + $this.kind.name.substring(0, 1).toupper() + $this.kind.name.substring(1)
return 'Voicemeeter ' + $this.kind.name.substring(0, 1).toupper() + $this.kind.name.substring(1)
}
[Remote] Login() {
@@ -138,13 +138,13 @@ Function Get-RemotePotato {
Function Connect-Voicemeeter {
param([String]$Kind)
switch ($Kind) {
"basic" {
'basic' {
return Get-RemoteBasic
}
"banana" {
'banana' {
return Get-RemoteBanana
}
"potato" {
'potato' {
return Get-RemotePotato
}
default {

View File

@@ -7,30 +7,49 @@ function Login {
)
$retval = [int][Voicemeeter.Remote]::VBVMR_Login()
if ($retval -notin @(0, 1, -2)) {
throw [CAPIError]::new($retval, "VBVMR_Login")
throw [CAPIError]::new($retval, 'VBVMR_Login')
}
switch ($retval) {
1 {
"Voicemeeter Engine running but GUI not launched. Launching GUI now." | Write-Verbose
'Voicemeeter Engine running but GUI not launched. Launching GUI now.' | Write-Verbose
RunVoicemeeter -kindId $kindId
}
-2 {
throw [LoginError]::new("Login may only be called once per session.")
throw [LoginError]::new('Login may only be called once per session.')
}
}
$timeout = New-TimeSpan -Seconds 2
$sw = [diagnostics.stopwatch]::StartNew()
$exception = $null
do {
Start-Sleep -m 100
try {
'Successfully logged into Voicemeeter [' + $(VmType).ToUpper() + '] Version ' + $(VmVersion) | Write-Verbose
$exception = $null
break
}
catch [CAPIError] {
$exception = $_
$exception | Write-Debug
}
} while ($sw.elapsed -lt $timeout)
if ($null -ne $exception) {
throw [VMRemoteError]::new('Timeout logging into the API.')
}
while (P_Dirty -or M_Dirty) { Start-Sleep -m 1 }
"Successfully logged into Voicemeeter [" + $(VmType).ToUpper() + "] Version " + $(VmVersion) | Write-Verbose
}
function Logout {
Start-Sleep -m 100
$retval = [int][Voicemeeter.Remote]::VBVMR_Logout()
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_Logout")
throw [CAPIError]::new($retval, 'VBVMR_Logout')
}
if ($retval -eq 0) { "Sucessfully logged out" | Write-Verbose }
if ($retval -eq 0) { 'Sucessfully logged out' | Write-Verbose }
}
function RunVoicemeeter {
@@ -38,22 +57,21 @@ function RunVoicemeeter {
[string]$kindId
)
$kinds = @{
"basic" = 1
"banana" = 2
"potato" = $(if ([Environment]::Is64BitOperatingSystem) { 6 } else { 3 })
'basic' = $(if ([Environment]::Is64BitOperatingSystem) { 4 } else { 1 })
'banana' = $(if ([Environment]::Is64BitOperatingSystem) { 5 } else { 2 })
'potato' = $(if ([Environment]::Is64BitOperatingSystem) { 6 } else { 3 })
}
$retval = [int][Voicemeeter.Remote]::VBVMR_RunVoicemeeter([int64]$kinds[$kindId])
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_RunVoicemeeter")
throw [CAPIError]::new($retval, 'VBVMR_RunVoicemeeter')
}
Start-Sleep -s 1
}
function P_Dirty {
$retval = [Voicemeeter.Remote]::VBVMR_IsParametersDirty()
if ($retval -notin @(0, 1)) {
throw [CAPIError]::new($retval, "VBVMR_RunVoicemeeter")
throw [CAPIError]::new($retval, 'VBVMR_IsParametersDirty')
}
[bool]$retval
}
@@ -61,7 +79,7 @@ function P_Dirty {
function M_Dirty {
$retval = [Voicemeeter.Remote]::VBVMR_MacroButton_IsDirty()
if ($retval -notin @(0, 1)) {
throw [CAPIError]::new($retval, "VBVMR_RunVoicemeeter")
throw [CAPIError]::new($retval, 'VBVMR_MacroButton_IsDirty')
}
[bool]$retval
}
@@ -70,12 +88,12 @@ function VmType {
New-Variable -Name ptr -Value 0
$retval = [int][Voicemeeter.Remote]::VBVMR_GetVoicemeeterType([ref]$ptr)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_GetVoicemeeterType")
throw [CAPIError]::new($retval, 'VBVMR_GetVoicemeeterType')
}
switch ($ptr) {
1 { return "basic" }
2 { return "banana" }
3 { return "potato" }
1 { return 'basic' }
2 { return 'banana' }
3 { return 'potato' }
}
}
@@ -83,7 +101,7 @@ function VmVersion {
New-Variable -Name ptr -Value 0
$retval = [int][Voicemeeter.Remote]::VBVMR_GetVoicemeeterVersion([ref]$ptr)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_GetVoicemeeterVersion")
throw [CAPIError]::new($retval, 'VBVMR_GetVoicemeeterVersion')
}
$v1 = ($ptr -band 0xFF000000) -shr 24
$v2 = ($ptr -band 0x00FF0000) -shr 16
@@ -104,7 +122,7 @@ function Param_Get {
$BYTES = [System.Byte[]]::new(512)
$retval = [int][Voicemeeter.Remote]::VBVMR_GetParameterStringA($PARAM, $BYTES)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_GetParameterStringA")
throw [CAPIError]::new($retval, 'VBVMR_GetParameterStringA')
}
[System.Text.Encoding]::ASCII.GetString($BYTES).Trim([char]0)
}
@@ -112,7 +130,7 @@ function Param_Get {
New-Variable -Name ptr -Value 0.0
$retval = [int][Voicemeeter.Remote]::VBVMR_GetParameterFloat($PARAM, [ref]$ptr)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_GetParameterFloat")
throw [CAPIError]::new($retval, 'VBVMR_GetParameterFloat')
}
[single]$ptr
}
@@ -125,13 +143,13 @@ function Param_Set {
if ($VALUE -is [string]) {
$retval = [int][Voicemeeter.Remote]::VBVMR_SetParameterStringA($PARAM, $VALUE)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_SetParameterStringA")
throw [CAPIError]::new($retval, 'VBVMR_SetParameterStringA')
}
}
else {
$retval = [int][Voicemeeter.Remote]::VBVMR_SetParameterFloat($PARAM, $VALUE)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_SetParameterFloat")
throw [CAPIError]::new($retval, 'VBVMR_SetParameterFloat')
}
}
}
@@ -142,7 +160,7 @@ function MB_Set {
)
$retval = [int][Voicemeeter.Remote]::VBVMR_MacroButton_SetStatus($ID, $SET, $MODE)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_MacroButton_SetStatus")
throw [CAPIError]::new($retval, 'VBVMR_MacroButton_SetStatus')
}
}
@@ -156,7 +174,7 @@ function MB_Get {
New-Variable -Name ptr -Value 0.0
$retval = [int][Voicemeeter.Remote]::VBVMR_MacroButton_GetStatus($ID, [ref]$ptr, $MODE)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, $MyInvocation.MyCommand)
throw [CAPIError]::new($retval, 'VBVMR_MacroButton_GetStatus')
}
[int]$ptr
}
@@ -166,8 +184,8 @@ function Param_Set_Multi {
[hashtable]$HASH
)
foreach ($key in $HASH.keys) {
$classobj, $m2, $m3 = $key.Split("_")
if ($m2 -match "^\d+$") { $index = [int]$m2 } else { $index = [int]$m3 }
$classobj, $m2, $m3 = $key.Split('_')
if ($m2 -match '^\d+$') { $index = [int]$m2 } else { $index = [int]$m3 }
foreach ($h in $HASH[$key].GetEnumerator()) {
$property = $h.Name
@@ -189,11 +207,11 @@ function Set_By_Script {
[string]$script
)
if ($script.Length -gt 48000) {
throw [VMError]::new("Script size cannot be larger than 48kB")
throw [VMRemoteError]::new('Script size cannot be larger than 48kB')
}
$retval = [int][Voicemeeter.Remote]::VBVMR_SetParameters($script)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_SetParameters")
throw [CAPIError]::new($retval, 'VBVMR_SetParameters')
}
}
@@ -204,7 +222,7 @@ function Get_Level {
New-Variable -Name ptr -Value 0.0
$retval = [int][Voicemeeter.Remote]::VBVMR_GetLevel($MODE, $INDEX, [ref]$ptr)
if ($retval -notin @(0)) {
throw [CAPIError]::new($retval, "VBVMR_GetLevel")
throw [CAPIError]::new($retval, 'VBVMR_GetLevel')
}
[float]$ptr
}

View File

@@ -3,9 +3,9 @@
function Setup_DLL {
$VMPATH = Get_VMPath
$dll = Join-Path -Path $VMPATH -ChildPath ("VoicemeeterRemote" + `
(& { if ([Environment]::Is64BitOperatingSystem) { "64" } else { "" } }) + `
".dll")
$dll = Join-Path -Path $VMPATH -ChildPath ('VoicemeeterRemote' + `
(& { if ([Environment]::Is64BitOperatingSystem) { '64' } else { '' } }) + `
'.dll')
$Signature = @"
[DllImport(@"$dll")]

View File

@@ -8,7 +8,7 @@ class IBus {
}
[string] identifier () {
return "Bus[" + $this.index + "]"
return 'Bus[' + $this.index + ']'
}
[single] Getter ($param) {
@@ -106,7 +106,7 @@ class BusMode : IBus {
}
[string] identifier () {
return "Bus[" + $this.index + "].mode"
return 'Bus[' + $this.index + '].mode'
}
[string] Get () {
@@ -125,7 +125,7 @@ class BusEq : IBus {
}
[string] identifier () {
return "Bus[" + $this.index + "].EQ"
return 'Bus[' + $this.index + '].EQ'
}
}
@@ -142,7 +142,7 @@ class BusDevice : IBus {
}
[string] identifier () {
return "Bus[" + $this.index + "].Device"
return 'Bus[' + $this.index + '].Device'
}
hidden $_name = $($this | Add-Member ScriptProperty 'name' `

View File

@@ -8,7 +8,7 @@ class Special {
}
[string] identifier () {
return "Command"
return 'Command'
}
[string] ToString() {
@@ -29,13 +29,13 @@ class Special {
}
[void] RunMacrobuttons() {
"Launching the MacroButtons app" | Write-Verbose
Start-Process -FilePath $(Join-Path -Path $this.remote.vmpath -ChildPath "VoicemeeterMacroButtons.exe")
'Launching the MacroButtons app' | Write-Verbose
Start-Process -FilePath $(Join-Path -Path $this.remote.vmpath -ChildPath 'VoicemeeterMacroButtons.exe')
}
[void] CloseMacrobuttons() {
"Closing the MacroButtons app" | Write-Verbose
Stop-Process -Name "VoicemeeterMacroButtons"
'Closing the MacroButtons app' | Write-Verbose
Stop-Process -Name 'VoicemeeterMacroButtons'
}
hidden $_hide = $($this | Add-Member ScriptProperty 'hide' `

View File

@@ -1,13 +1,19 @@
function Get_VMPath {
$REG_KEY = "Registry::HKEY_LOCAL_MACHINE\Software" + `
(& { if ([Environment]::Is64BitOperatingSystem) { "\WOW6432Node" } else { "" } }) + `
"\Microsoft\Windows\CurrentVersion\Uninstall"
$VM_KEY = "\VB:Voicemeeter {17359A74-1236-5467}\"
$REG_KEY = @(
'Registry::HKEY_LOCAL_MACHINE',
'Software',
(& { if ([Environment]::Is64BitOperatingSystem) { 'WOW6432Node' } else { '' } }),
'Microsoft',
'Windows',
'CurrentVersion',
'Uninstall'
).Where({ $_ -ne '' }) -Join '\'
$VM_KEY = 'VB:Voicemeeter {17359A74-1236-5467}'
try {
return $(Get-ItemPropertyValue -Path ($REG_KEY + $VM_KEY) -Name UninstallString | Split-Path -Parent)
return $(Get-ItemPropertyValue -Path (@($REG_KEY, $VM_KEY) -Join '\') -Name UninstallString | Split-Path -Parent)
}
catch {
throw [VMRemoteError]::new("Unable to fetch Voicemeeter path from the Registry.")
throw [VMRemoteError]::new('Unable to fetch Voicemeeter path from the Registry.')
}
}

View File

@@ -1,30 +1,30 @@
$KindMap = @{
"basic" = @{
"name" = "basic"
"p_in" = 2
"v_in" = 1
"p_out" = 1
"v_out" = 1
"vban_in" = 4
"vban_out" = 4
'basic' = @{
'name' = 'basic'
'p_in' = 2
'v_in' = 1
'p_out' = 1
'v_out' = 1
'vban_in' = 4
'vban_out' = 4
};
"banana" = @{
"name" = "banana"
"p_in" = 3
"v_in" = 2
"p_out" = 3
"v_out" = 2
"vban_in" = 8
"vban_out" = 8
'banana' = @{
'name' = 'banana'
'p_in' = 3
'v_in' = 2
'p_out' = 3
'v_out' = 2
'vban_in' = 8
'vban_out' = 8
};
"potato" = @{
"name" = "potato"
"p_in" = 5
"v_in" = 3
"p_out" = 5
"v_out" = 3
"vban_in" = 8
"vban_out" = 8
'potato' = @{
'name' = 'potato'
'p_in' = 5
'v_in' = 3
'p_out' = 5
'v_out' = 3
'vban_in' = 8
'vban_out' = 8
};
}

View File

@@ -5,9 +5,9 @@ function AddBoolMembers () {
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures["Getter"] = "[bool]`$this.Getter('{0}')" -f $param
$Signatures['Getter'] = "[bool]`$this.Getter('{0}')" -f $param
# Define setter
$Signatures["Setter"] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
$Signatures['Setter'] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
@@ -21,9 +21,9 @@ function AddFloatMembers () {
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures["Getter"] = "[math]::Round(`$this.Getter('{0}'), 1)" -f $param
$Signatures['Getter'] = "[math]::Round(`$this.Getter('{0}'), 1)" -f $param
# Define setter
$Signatures["Setter"] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
$Signatures['Setter'] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
@@ -37,9 +37,9 @@ function AddIntMembers () {
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures["Getter"] = "[Int]`$this.Getter('{0}')" -f $param
$Signatures['Getter'] = "[Int]`$this.Getter('{0}')" -f $param
# Define setter
$Signatures["Setter"] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
$Signatures['Setter'] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
@@ -53,9 +53,9 @@ function AddStringMembers () {
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures["Getter"] = "[String]`$this.Getter_String('{0}')" -f $param
$Signatures['Getter'] = "[String]`$this.Getter_String('{0}')" -f $param
# Define setter
$Signatures["Setter"] = "param ( [String]`$arg )`n`$this.Setter('{0}', `$arg)" `
$Signatures['Setter'] = "param ( [String]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
@@ -69,9 +69,9 @@ function AddActionMembers () {
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures["Getter"] = "`$this.Setter('{0}', `$true)" -f $param
$Signatures['Getter'] = "`$this.Setter('{0}', `$true)" -f $param
# Define setter
$Signatures["Setter"] = ""
$Signatures['Setter'] = ''
Addmember
}
@@ -83,7 +83,7 @@ function AddChannelMembers () {
[System.Collections.ArrayList]$channels = @()
1..$($num_A + $num_B) | ForEach-Object {
if ($_ -le $num_A) { $channels.Add("A{0}" -f $_) } else { $channels.Add("B{0}" -f $($_ - $num_A)) }
if ($_ -le $num_A) { $channels.Add('A{0}' -f $_) } else { $channels.Add('B{0}' -f $($_ - $num_A)) }
}
AddBoolMembers -PARAMS $channels
@@ -93,11 +93,11 @@ function AddGainlayerMembers () {
[hashtable]$Signatures = @{}
0..7 | ForEach-Object {
# Define getter
$Signatures["Getter"] = "`$this.Getter('gainlayer[{0}]')" -f $_
$Signatures['Getter'] = "`$this.Getter('gainlayer[{0}]')" -f $_
# Define setter
$Signatures["Setter"] = "param ( [Single]`$arg )`n`$this.Setter('gainlayer[{0}]', `$arg)" `
$Signatures['Setter'] = "param ( [Single]`$arg )`n`$this.Setter('gainlayer[{0}]', `$arg)" `
-f $_
$param = "gainlayer{0}" -f $_
$param = 'gainlayer{0}' -f $_
$null = $param
Addmember
@@ -108,8 +108,8 @@ function Addmember {
$AddMemberParams = @{
Name = $param
MemberType = 'ScriptProperty'
Value = [scriptblock]::Create($Signatures["Getter"])
SecondValue = [scriptblock]::Create($Signatures["Setter"])
Value = [scriptblock]::Create($Signatures['Getter'])
SecondValue = [scriptblock]::Create($Signatures['Setter'])
}
$this | Add-Member @AddMemberParams
}

View File

@@ -1,5 +1,5 @@
function Get_Profiles ([string]$kind_id) {
$basepath = Join-Path -Path $(Split-Path -Path $PSScriptRoot) -ChildPath "profiles"
$basepath = Join-Path -Path $(Split-Path -Path $PSScriptRoot) -ChildPath 'profiles'
if (Test-Path $basepath) {
$fullpath = Join-Path -Path $basepath -ChildPath $kind_id
}
@@ -11,7 +11,7 @@ function Get_Profiles ([string]$kind_id) {
$filenames | ForEach-Object {
(Join-Path -Path $fullpath -ChildPath $_) | ForEach-Object {
$filename = [System.IO.Path]::GetFileNameWithoutExtension($_)
Write-Host ("Importing profile " + $kind_id + "/" + $filename)
Write-Host ('Importing profile ' + $kind_id + '/' + $filename)
$data[$filename] = Import-PowerShellDataFile -Path $_
}
}

View File

@@ -51,7 +51,7 @@ class Recorder : IRecorder {
}
[string] identifier () {
return "Recorder"
return 'Recorder'
}
[string] ToString() {
@@ -137,9 +137,9 @@ class Recorder : IRecorder {
[void] GoTo ([string]$timestring) {
try {
if ([datetime]::ParseExact($timestring, "HH:mm:ss", $null)) {
if ([datetime]::ParseExact($timestring, 'HH:mm:ss', $null)) {
$timespan = [timespan]::Parse($timestring)
$this.Setter("GoTo", $timespan.TotalSeconds)
$this.Setter('GoTo', $timespan.TotalSeconds)
}
}
catch [FormatException] {
@@ -150,13 +150,13 @@ class Recorder : IRecorder {
[void] FileType($format) {
[int]$val = 0
switch ($format) {
"wav" { $val = 1 }
"aiff" { $val = 2 }
"bwf" { $val = 3 }
"mp3" { $val = 100 }
'wav' { $val = 1 }
'aiff' { $val = 2 }
'bwf' { $val = 3 }
'mp3' { $val = 100 }
default { "Filetype() got: $format, expected one of 'wav', 'aiff', 'bwf', 'mp3'" }
}
$this.Setter("filetype", $val)
$this.Setter('filetype', $val)
}
}
@@ -166,7 +166,7 @@ class RecorderMode : IRecorder {
}
[string] identifier () {
return "Recorder.Mode"
return 'Recorder.Mode'
}
}
@@ -178,7 +178,7 @@ class RecorderArm : IRecorder {
}
Set ([bool]$val) {
$this.Setter("", $(if ($val) { 1 } else { 0 }))
$this.Setter('', $(if ($val) { 1 } else { 0 }))
}
}

View File

@@ -8,7 +8,7 @@ class IStrip {
}
[string] identifier () {
return "Strip[" + $this.index + "]"
return 'Strip[' + $this.index + ']'
}
[single] Getter ($param) {
@@ -135,7 +135,7 @@ class StripComp : IStrip {
}
[string] identifier () {
return "Strip[" + $this.index + "].Comp"
return 'Strip[' + $this.index + '].Comp'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
@@ -155,7 +155,7 @@ class StripGate : IStrip {
}
[string] identifier () {
return "Strip[" + $this.index + "].Gate"
return 'Strip[' + $this.index + '].Gate'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
@@ -174,7 +174,7 @@ class StripDenoiser : IStrip {
}
[string] identifier () {
return "Strip[" + $this.index + "].Denoiser"
return 'Strip[' + $this.index + '].Denoiser'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
@@ -194,7 +194,7 @@ class StripEq : IStrip {
}
[string] identifier () {
return "Strip[" + $this.index + "].EQ"
return 'Strip[' + $this.index + '].EQ'
}
}
@@ -203,7 +203,7 @@ class StripDevice : IStrip {
}
[string] identifier () {
return "Strip[" + $this.index + "].Device"
return 'Strip[' + $this.index + '].Device'
}
hidden $_name = $($this | Add-Member ScriptProperty 'name' `

View File

@@ -10,7 +10,7 @@ class IVban {
}
[string] identifier () {
return "vban." + $this.direction + "stream[" + $this.index + "]"
return 'vban.' + $this.direction + 'stream[' + $this.index + ']'
}
[single] Getter ($param) {
@@ -94,7 +94,7 @@ class Vban : IVban {
} `
{
param([int]$arg)
if ($this.direction -eq "in") { Write-Warning ('Error, read only value') }
if ($this.direction -eq 'in') { Write-Warning ('Error, read only value') }
else {
$opts = @(11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000)
if ($opts.Contains($arg)) {
@@ -113,7 +113,7 @@ class Vban : IVban {
} `
{
param([int]$arg)
if ($this.direction -eq "in") { Write-Warning ('Error, read only value') }
if ($this.direction -eq 'in') { Write-Warning ('Error, read only value') }
else {
if ($arg -ge 1 -and $arg -le 8) {
$this._channel = $this.Setter('channel', $arg)
@@ -132,7 +132,7 @@ class Vban : IVban {
} `
{
param([int]$arg)
if ($this.direction -eq "in") { Write-Warning ('Error, read only value') }
if ($this.direction -eq 'in') { Write-Warning ('Error, read only value') }
else {
if (@(16, 24).Contains($arg)) {
$val = if ($arg -eq 16) { 1 } else { 2 }
@@ -151,7 +151,7 @@ class Vban : IVban {
} `
{
param([int]$arg)
if ($this.direction -eq "in") { Write-Warning ('Error, read only value') }
if ($this.direction -eq 'in') { Write-Warning ('Error, read only value') }
else {
if ($arg -ge 0 -and $arg -le 4) {
$this._quality = $this.Setter('quality', $arg)
@@ -169,7 +169,7 @@ class Vban : IVban {
} `
{
param([int]$arg)
if ($this.direction -eq "in") { Write-Warning ('Error, read only value') }
if ($this.direction -eq 'in') { Write-Warning ('Error, read only value') }
else {
if ($arg -ge 0 -and $arg -le 8) {
$this._route = $this.Setter('route', $arg)
@@ -200,10 +200,10 @@ function Make_Vban ([Object]$remote) {
[System.Collections.ArrayList]$outstream = @()
0..$($remote.kind.vban_in - 1) | ForEach-Object {
[void]$instream.Add([VbanInstream]::new($_, $remote, "in"))
[void]$instream.Add([VbanInstream]::new($_, $remote, 'in'))
}
0..$($remote.kind.vban_out - 1) | ForEach-Object {
[void]$outstream.Add([VbanOutstream]::new($_, $remote, "out"))
[void]$outstream.Add([VbanOutstream]::new($_, $remote, 'out'))
}
$CustomObject = [pscustomobject]@{
@@ -213,7 +213,7 @@ function Make_Vban ([Object]$remote) {
$CustomObject | Add-Member ScriptProperty 'enable' `
{
return Write-Warning ("ERROR: vban.enable is write only")
return Write-Warning ('ERROR: vban.enable is write only')
} `
{
param([bool]$arg)

View File

@@ -90,17 +90,17 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
}
Context 'Recorder' -Skip:$ifBasic {
It "Should set and get Recorder.A3" {
It 'Should set and get Recorder.A3' {
$vmr.recorder.A3 = $value
$vmr.recorder.A3 | Should -Be $expected
}
It "Should set and get Recorder.B1" {
It 'Should set and get Recorder.B1' {
$vmr.recorder.B1 = $value
$vmr.recorder.B1 | Should -Be $expected
}
It "Should set and get Recorder.loop" {
It 'Should set and get Recorder.loop' {
$vmr.recorder.loop = $value
}
}
@@ -248,8 +248,8 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
@{ Index = $phys_in }, @{ Index = $virt_in }
) {
It "Should set Strip[$index].Label" -ForEach @(
@{ Value = "test0"; Expected = "test0" }
@{ Value = "test1"; Expected = "test1" }
@{ Value = 'test0'; Expected = 'test0' }
@{ Value = 'test1'; Expected = 'test1' }
) {
$vmr.strip[$index].label = $value
$vmr.strip[$index].label | Should -Be $expected
@@ -260,8 +260,8 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
@{ Index = $phys_out }, @{ Index = $virt_out }
) {
It "Should set Bus[$index].Label" -ForEach @(
@{ Value = "test0"; Expected = "test0" }
@{ Value = "test1"; Expected = "test1" }
@{ Value = 'test0'; Expected = 'test0' }
@{ Value = 'test1'; Expected = 'test1' }
) {
$vmr.bus[$index].label = $value
$vmr.bus[$index].label | Should -Be $expected
@@ -273,7 +273,7 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
) {
Context 'instream' {
Context 'ip' -ForEach @(
@{ Value = "0.0.0.0"; Expected = "0.0.0.0" }
@{ Value = '0.0.0.0'; Expected = '0.0.0.0' }
) {
It "Should set vban.instream[$index].name to $value" {
$vmr.vban.instream[$index].ip = $value
@@ -284,7 +284,7 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
Context 'outstream' {
Context 'ip' -ForEach @(
@{ Value = "0.0.0.0"; Expected = "0.0.0.0" }
@{ Value = '0.0.0.0'; Expected = '0.0.0.0' }
) {
It "Should set vban.outstream[$index].name to $value" {
$vmr.vban.outstream[$index].ip = $value

View File

@@ -29,7 +29,7 @@ Describe -Tag 'lower', -TestName 'All Lower Tests' {
@{ Index = $phys_in }, @{ Index = $virt_in }
) {
Context 'mute, mono, A1, B2' -ForEach @(
@{ param = "mute" }, @{ param = "A1" }
@{ param = 'mute' }, @{ param = 'A1' }
) {
It "Should set Strip[0].$param to 1" {
Param_Set -PARAM "Strip[$index].$param" -VALUE $value

View File

@@ -1,76 +0,0 @@
Param([String]$tag, [Int]$num = 1, [switch]$log, [string]$kind = "potato")
Import-Module .\lib\Voicemeeter.psm1
Function ParseLog {
Param([String]$logfile)
$summary_file = Join-Path $PSScriptRoot "_summary.log"
if (Test-Path $summary_file) { Clear-Content $summary_file }
$PASSED_PATTERN = "^PassedCount\s+:\s(\d+)"
$FAILED_PATTERN = "^FailedCount\s+:\s(\d+)"
$DATA = @{
"passed" = 0
"failed" = 0
}
ForEach ($line in `
$(Get-Content -Path "${logfile}")) {
if ($line -match $PASSED_PATTERN) {
$DATA["passed"] += $Matches[1]
}
elseif ($line -match $FAILED_PATTERN) {
$DATA["failed"] += $Matches[1]
}
}
"=========================`n" + `
"$num tests run:`n" + `
"=========================" | Tee-Object -FilePath $summary_file -Append
$DATA | ForEach-Object { $_ } | Tee-Object -FilePath $summary_file -Append
}
function main() {
try {
$vmr = Connect-Voicemeeter -Kind $kind
$vmr.command.RunMacrobuttons() # ensure macrobuttons is running before we begin
Write-Host "Running tests for $vmr"
# test boundaries by kind
$phys_in = $vmr.kind.p_in - 1
$virt_in = $vmr.kind.p_in + $vmr.kind.v_in - 1
$phys_out = $vmr.kind.p_out - 1
$virt_out = $vmr.kind.p_out + $vmr.kind.v_out - 1
$vban_in = $vmr.kind.vban_in - 1
$vban_out = $vmr.kind.vban_out - 1
# skip conditions by kind
$ifBasic = $vmr.kind.name -eq "basic"
$ifBanana = $vmr.kind.name -eq "banana"
$ifPotato = $vmr.kind.name -eq "potato"
$ifNotBasic = $vmr.kind.name -ne "basic"
$ifNotBanana = $vmr.kind.name -ne "banana"
$ifNotPotato = $vmr.kind.name -ne "potato"
$logfile = Join-Path $PSScriptRoot "_results.log"
if (Test-Path $logfile) { Clear-Content $logfile }
1..$num | ForEach-Object {
if ($log) {
"Running test $_ of $num" | Tee-Object -FilePath $logfile -Append
Invoke-Pester -Tag $tag -PassThru | Tee-Object -FilePath $logfile -Append
}
else {
"Running test $_ of $num"
Invoke-Pester -Tag $tag -PassThru
}
}
if ($log) { Parselog -logfile $logfile }
}
finally { Disconnect-Voicemeeter }
}
main

30
tests/run.ps1 Normal file
View File

@@ -0,0 +1,30 @@
[Diagnostics.CodeAnalysis.SuppressMessageAttribute("PSUseDeclaredVarsMoreThanAssignments", "", Target = "variablename")]
Param([String]$tag, [string]$kind = 'potato')
Import-Module .\lib\Voicemeeter.psm1
function main() {
try {
$vmr = Connect-Voicemeeter -Kind $kind
$vmr.command.RunMacrobuttons() # ensure macrobuttons is running before we begin
Write-Host "Running tests for $vmr"
# test boundaries by kind
$phys_in = $vmr.kind.p_in - 1
$virt_in = $vmr.kind.p_in + $vmr.kind.v_in - 1
$phys_out = $vmr.kind.p_out - 1
$virt_out = $vmr.kind.p_out + $vmr.kind.v_out - 1
$vban_in = $vmr.kind.vban_in - 1
$vban_out = $vmr.kind.vban_out - 1
# skip conditions by kind
$ifBasic = $vmr.kind.name -eq 'basic'
$ifNotPotato = $vmr.kind.name -ne 'potato'
Invoke-Pester -Tag $tag -PassThru | Out-Null
}
finally { Disconnect-Voicemeeter }
}
main