mirror of
https://github.com/onyx-and-iris/voicemeeter-api-powershell.git
synced 2026-04-20 06:23:32 +00:00
Compare commits
30 Commits
dfa044d853
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| f6c750fe56 | |||
| 685854c35a | |||
|
|
68cf0cef37 | ||
|
|
91e798caa1 | ||
|
|
d1dfe2de52 | ||
|
|
ed3b7be904 | ||
|
|
33dcc98c8f | ||
|
|
55ade960f2 | ||
|
|
7d9615d760 | ||
|
|
4ea371af2f | ||
|
|
6b2031de99 | ||
|
|
8d267078ff | ||
|
|
1f5b52b439 | ||
|
|
defb2b68c0 | ||
|
|
2f2d4af848 | ||
|
|
abd792acd5 | ||
| b41001f4a9 | |||
| fc75bc2020 | |||
| d51ffacfaf | |||
| fe540895b3 | |||
| 7dd4c9db24 | |||
| 3119b1080e | |||
| a45f7a93af | |||
| 0e552873f0 | |||
| 3d87f5c03f | |||
| aaa4672cbc | |||
| f5dead51df | |||
| 259c7309dc | |||
| 6542473394 | |||
| 448aa01ad2 |
16
CHANGELOG.md
16
CHANGELOG.md
@@ -9,6 +9,22 @@ Before any major/minor/patch is released all unit tests will be run to verify th
|
|||||||
|
|
||||||
## [Unreleased] These changes have not been added to PSGallery yet
|
## [Unreleased] These changes have not been added to PSGallery yet
|
||||||
|
|
||||||
|
## [4.2.0] - 2026-03-15
|
||||||
|
|
||||||
|
### Added
|
||||||
|
|
||||||
|
- New Remote methods for device enumeration:
|
||||||
|
- GetInputCount()
|
||||||
|
- GetOutputCount()
|
||||||
|
- GetInputDevice($index)
|
||||||
|
- GetOutputDevice($index)
|
||||||
|
|
||||||
|
- New IODevice property `driver` to get the driver type of the current device (e.g. 'wdm', 'mme', etc.)
|
||||||
|
|
||||||
|
- New IODevice methods to get, set, or clear the current device for a strip or bus:
|
||||||
|
- Get(): returns a PSObject with properties Driver, Name, HardwareId, and IsOutput
|
||||||
|
- Set($device): accepts a PSObject with properties Driver, Name, and IsOutput
|
||||||
|
- Clear()
|
||||||
|
|
||||||
## [4.1.0] - 2025-12-23
|
## [4.1.0] - 2025-12-23
|
||||||
|
|
||||||
|
|||||||
29
README.md
29
README.md
@@ -368,20 +368,32 @@ $vmr.bus[0].FadeBy(-10, 500)
|
|||||||
The following Strip.device | Bus.device properties are available:
|
The following Strip.device | Bus.device properties are available:
|
||||||
|
|
||||||
- name: string
|
- name: string
|
||||||
|
- driver: string
|
||||||
- sr: int
|
- sr: int
|
||||||
- wdm: string
|
- wdm: string
|
||||||
- ks: string
|
- ks: string
|
||||||
- mme: string
|
- mme: string
|
||||||
- asio: string
|
- asio: string
|
||||||
|
|
||||||
|
The following Strip.device | Bus.device methods are available:
|
||||||
|
|
||||||
|
- Set($device) : PSObject, where device is a PSObject with properties Driver, Name, and IsOutput
|
||||||
|
- Get() : PSObject, returns a PSObject with properties Driver, Name, HardwareId, and IsOutput
|
||||||
|
- Clear() : Clears the currently selected device
|
||||||
|
|
||||||
for example:
|
for example:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
$vmr.strip[0].device.wdm = "Mic|Line|Instrument 1 (Audient EVO4)"
|
$vmr.strip[0].device.wdm = "Mic|Line|Instrument 1 (Audient EVO4)"
|
||||||
$vmr.bus[0].device.name | Write-Host
|
$vmr.bus[0].device.name | Write-Host
|
||||||
|
|
||||||
|
$device = $vmr.strip[3].device.Get()
|
||||||
|
$vmr.strip[1].device.Set($device) # moves the device selected for strip 4 to strip 2
|
||||||
|
|
||||||
|
$vmr.bus[2].device.Clear()
|
||||||
```
|
```
|
||||||
|
|
||||||
name, sr are defined as read only.
|
name, driver, sr are defined as read only.
|
||||||
wdm, ks, mme, asio are defined as write only.
|
wdm, ks, mme, asio are defined as write only.
|
||||||
asio only defined for Bus[0].Device
|
asio only defined for Bus[0].Device
|
||||||
|
|
||||||
@@ -793,6 +805,21 @@ Access to lower level polling functions are provided with these functions:
|
|||||||
- `$vmr.PDirty`: Returns true if a parameter has been updated.
|
- `$vmr.PDirty`: Returns true if a parameter has been updated.
|
||||||
- `$vmr.MDirty`: Returns true if a macrobutton has been updated.
|
- `$vmr.MDirty`: Returns true if a macrobutton has been updated.
|
||||||
|
|
||||||
|
Access to lower level device enumeration functions are provided with these functions:
|
||||||
|
|
||||||
|
- `$vmr.GetInputCount()`: Returns the number of available input devices.
|
||||||
|
- `$vmr.GetOutputCount()`: Returns the number of available output devices.
|
||||||
|
- `$vmr.GetInputDevice($index)`: Returns a PSObject with properties Driver, Name, HardwareId, and IsOutput for the input device at the given index.
|
||||||
|
- `$vmr.GetOutputDevice($index)`: Returns a PSObject with properties Driver, Name, HardwareId, and IsOutput for the output device at the given index.
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
$count = $vmr.GetInputCount()
|
||||||
|
for ($i = 0; $i -lt $count; $i++) {
|
||||||
|
$device = $vmr.GetInputDevice($i)
|
||||||
|
Write-Host "Input Device $i: $($device.Driver) - $($device.Name)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Errors
|
### Errors
|
||||||
|
|
||||||
- `VMRemoteError`: Base custom error class.
|
- `VMRemoteError`: Base custom error class.
|
||||||
|
|||||||
@@ -9,3 +9,16 @@ tasks:
|
|||||||
cmds:
|
cmds:
|
||||||
- echo "Running tests..."
|
- echo "Running tests..."
|
||||||
- pwsh -c "tests\run.ps1 {{.CLI_ARGS}}"
|
- pwsh -c "tests\run.ps1 {{.CLI_ARGS}}"
|
||||||
|
|
||||||
|
bump:
|
||||||
|
desc: 'Bump the module version in the .psd1 file. Usage: "task bump -- show" or "task bump -- [patch|minor|major]".'
|
||||||
|
preconditions:
|
||||||
|
- sh: 'pwsh -c "if (Get-Command bump) { exit 0 } else { exit 1 }"'
|
||||||
|
msg: "The 'bump' command is not available. Please install the required tools to use this command."
|
||||||
|
cmds:
|
||||||
|
- |
|
||||||
|
{{if eq .CLI_ARGS "show"}}
|
||||||
|
pwsh -c "bump show -f lib/Voicemeeter.psd1 -p \"ModuleVersion\s*=\s'(\d+\.\d+\.\d+)'\""
|
||||||
|
{{else}}
|
||||||
|
pwsh -c "bump {{.CLI_ARGS}} -w -f lib/Voicemeeter.psd1 -p \"ModuleVersion\s*=\s'(\d+\.\d+\.\d+)'\""
|
||||||
|
{{end}}
|
||||||
|
|||||||
@@ -1,68 +1,129 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Command Line Interface for Voicemeeter control.
|
||||||
|
.DESCRIPTION
|
||||||
|
This script provides a command line interface to interact with Voicemeeter. It supports both interactive mode and scripted commands.
|
||||||
|
Users can specify the type of Voicemeeter (banana or potato) and execute commands to get or set parameters.
|
||||||
|
.PARAMETER help
|
||||||
|
Displays help information.
|
||||||
|
.PARAMETER interactive
|
||||||
|
Starts the CLI in interactive mode.
|
||||||
|
.PARAMETER kind
|
||||||
|
Specifies the type of Voicemeeter to connect to (banana or potato). Default is 'banana'.
|
||||||
|
.PARAMETER script
|
||||||
|
A list of commands to execute in sequence.
|
||||||
|
.EXAMPLE
|
||||||
|
.\CLI.ps1 -interactive -kind banana
|
||||||
|
Starts the CLI in interactive mode for Voicemeeter Banana.
|
||||||
|
.EXAMPLE
|
||||||
|
.\CLI.ps1 -script "Strip[0].Gain=3", "!Bus[1].Mute", "Bus[0].Gain"
|
||||||
|
Executes a series of commands: sets Strip 0 Gain to 3, toggles Bus 1 Mute, and retrieves Bus 0 Gain.
|
||||||
|
#>
|
||||||
|
|
||||||
[cmdletbinding()]
|
[cmdletbinding()]
|
||||||
param(
|
param(
|
||||||
|
[switch]$help,
|
||||||
[switch]$interactive,
|
[switch]$interactive,
|
||||||
|
[ValidateSet('basic', 'banana', 'potato')]
|
||||||
[String]$kind = 'banana',
|
[String]$kind = 'banana',
|
||||||
[String[]]$script = @()
|
[String[]]$script = @()
|
||||||
)
|
)
|
||||||
|
|
||||||
Import-Module ..\..\lib\Voicemeeter.psm1
|
Import-Module ..\..\lib\Voicemeeter.psm1
|
||||||
|
|
||||||
function get-value {
|
if ($help -or ($script.Count -eq 0 -and -not $interactive)) {
|
||||||
param([object]$vmr, [string]$line)
|
Write-Host 'Voicemeeter CLI'
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Usage:'
|
||||||
|
Write-Host ' CLI.ps1 [-interactive] [-kind <basic|banana|potato>] [-script <command1>, <command2>, ...]'
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Options:'
|
||||||
|
Write-Host ' -help Display this help message.'
|
||||||
|
Write-Host ' -interactive Start in interactive mode.'
|
||||||
|
Write-Host ' -kind <type> Specify the Voicemeeter type (basic, banana or potato). Default is banana.'
|
||||||
|
Write-Host ' -script <commands> Provide a list of commands to execute in sequence.'
|
||||||
|
Write-Host ''
|
||||||
|
Write-Host 'Commands can be of the form:'
|
||||||
|
Write-Host ' Parameter=Value Set a parameter to a specific value.'
|
||||||
|
Write-Host ' !Parameter Toggle a boolean parameter.'
|
||||||
|
Write-Host ' Parameter Get the current value of a parameter.'
|
||||||
|
exit 0
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ParameterValue {
|
||||||
|
param(
|
||||||
|
[object]$vmr,
|
||||||
|
[string]$Parameter
|
||||||
|
)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$retval = $vmr.Getter($line)
|
$retval = $vmr.Getter($Parameter)
|
||||||
}
|
}
|
||||||
catch {
|
catch {
|
||||||
$retval = $vmr.Getter_String($line)
|
$retval = $vmr.Getter_String($Parameter)
|
||||||
}
|
}
|
||||||
$retval
|
$retval
|
||||||
}
|
}
|
||||||
|
|
||||||
function msgHandler {
|
|
||||||
param([object]$vmr, [string]$line)
|
function Invoke-VoicemeeterCLICommand {
|
||||||
$line + ' passed to handler' | Write-Debug
|
param(
|
||||||
if ($line[0] -eq '!') {
|
[object]$vmr,
|
||||||
'Toggling ' + $line.substring(1) | Write-Debug
|
[string]$Command
|
||||||
$retval = get-value -vmr $vmr -line $line.substring(1)
|
)
|
||||||
$vmr.Setter($line.substring(1), 1 - $retval)
|
|
||||||
|
# Toggle command
|
||||||
|
if ($Command[0] -eq '!') {
|
||||||
|
$parameter = $Command.Substring(1).Trim()
|
||||||
|
$currentValue = Get-ParameterValue -vmr $vmr -Parameter $parameter
|
||||||
|
|
||||||
|
if ($currentValue -ne 0 -and $currentValue -ne 1) {
|
||||||
|
throw "Cannot toggle non-boolean parameter '$parameter' with value '$currentValue'"
|
||||||
|
}
|
||||||
|
|
||||||
|
$newValue = 1 - $currentValue
|
||||||
|
$vmr.SendText("$parameter=$newValue")
|
||||||
|
Write-Host "Toggled $parameter to $newValue"
|
||||||
}
|
}
|
||||||
elseif ($line.Contains('=')) {
|
# Set command
|
||||||
"Setting $line" | Write-Debug
|
elseif ($Command.Contains('=')) {
|
||||||
$vmr.SendText($line)
|
$vmr.SendText("$Command")
|
||||||
|
Write-Host "Set $Command"
|
||||||
|
}
|
||||||
|
# Get command
|
||||||
|
else {
|
||||||
|
$parameter = $Command.Trim()
|
||||||
|
$value = Get-ParameterValue -vmr $vmr -Parameter $parameter
|
||||||
|
Write-Host "$parameter = $value"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Start-VoicemeeterCLI {
|
||||||
|
param(
|
||||||
|
[object]$vmr
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host "Connected to Voicemeeter. Type 'Q' to quit." -ForegroundColor Green
|
||||||
|
while (($CommandFromInput = Read-Host 'command') -ne 'Q') {
|
||||||
|
try {
|
||||||
|
Invoke-VoicemeeterCLICommand -vmr $vmr -Command $CommandFromInput
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Host "Error: $_" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$vmr = Connect-Voicemeeter -Kind $kind
|
||||||
|
|
||||||
|
if ($interactive) {
|
||||||
|
Start-VoicemeeterCLI -vmr $vmr
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
"Getting $line" | Write-Debug
|
foreach ($command in $script) {
|
||||||
$retval = get-value -vmr $vmr -line $line
|
Invoke-VoicemeeterCLICommand -vmr $vmr -Command $command
|
||||||
$line + ' = ' + $retval | Write-Host
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function read-hostuntilempty {
|
|
||||||
param([object]$vmr)
|
|
||||||
while (($line = Read-Host) -cne [string]::Empty) { msgHandler -vmr $vmr -line $line }
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
function main {
|
|
||||||
[object]$vmr
|
|
||||||
|
|
||||||
try {
|
|
||||||
$vmr = Connect-Voicemeeter -Kind $kind
|
|
||||||
|
|
||||||
if ($interactive) {
|
|
||||||
'Press <Enter> to exit' | Write-Host
|
|
||||||
read-hostuntilempty -vmr $vmr
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if ($script.Count -eq 0) {
|
|
||||||
'No script provided, exiting' | Write-Host
|
|
||||||
return
|
|
||||||
}
|
|
||||||
$script | ForEach-Object {
|
|
||||||
msgHandler -vmr $vmr -line $_
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally { Disconnect-Voicemeeter }
|
|
||||||
}
|
}
|
||||||
|
finally { Disconnect-Voicemeeter }
|
||||||
main
|
|
||||||
@@ -1,34 +1,39 @@
|
|||||||
## About
|
## About
|
||||||
|
|
||||||
A simple voicemeeter-cli script. Offers ability to toggle, get and set parameters.
|
A basic command-line interface
|
||||||
|
|
||||||
## Use
|
## Use
|
||||||
|
|
||||||
Toggle with `!` prefix, get by excluding `=` and set by including `=`. Mix and match arguments.
|
```console
|
||||||
|
Voicemeeter CLI
|
||||||
|
|
||||||
You may pass the following optional flags:
|
Usage:
|
||||||
|
CLI.ps1 [-interactive] [-kind <basic|banana|potato>] [-script <command1>, <command2>, ...]
|
||||||
|
|
||||||
- -o: (-output) to toggle console output.
|
Options:
|
||||||
- -i: (-interactive) to toggle interactive mode.
|
-help Display this help message.
|
||||||
- -k: (-kind) to set the kind of Voicemeeter. Defaults to banana.
|
-interactive Start in interactive mode.
|
||||||
- -s: (script) a string array, one string for each command.
|
-kind <type> Specify the Voicemeeter type (basic, banana or potato). Default is banana.
|
||||||
|
-script <commands> Provide a list of commands to execute in sequence.
|
||||||
|
|
||||||
|
Commands can be of the form:
|
||||||
|
Parameter=Value Set a parameter to a specific value.
|
||||||
|
!Parameter Toggle a boolean parameter.
|
||||||
|
Parameter Get the current value of a parameter.
|
||||||
|
```
|
||||||
|
|
||||||
for example:
|
for example:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
.\CLI.ps1 -o -k "banana" -s "strip[0].mute", "!strip[0].mute", "strip[0].mute", "bus[2].eq.on=1", "command.lock=1"
|
.\CLI.ps1 -script strip[0].mute, !strip[0].mute, strip[0].mute, bus[2].eq.on=1, command.lock=1
|
||||||
```
|
```
|
||||||
|
|
||||||
Expected output:
|
should produce the output:
|
||||||
|
|
||||||
```powershell
|
```console
|
||||||
Getting strip[0].mute
|
|
||||||
strip[0].mute = 0
|
|
||||||
Toggling strip[0].mute
|
|
||||||
Getting strip[0].mute
|
|
||||||
strip[0].mute = 1
|
strip[0].mute = 1
|
||||||
Setting bus[2].eq.on=1
|
Toggled strip[0].mute to 0
|
||||||
Setting command.lock=1
|
strip[0].mute = 0
|
||||||
```
|
Set bus[2].eq.on=1
|
||||||
|
Set command.lock=1
|
||||||
If running in interactive mode press `<Enter>` to exit.
|
```
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<#
|
|
||||||
1) Loop through an array of bus objects.
|
|
||||||
2) Mute first unmuted bus
|
|
||||||
3) If next bus in array exists, unmute it, otherwise clear unmuted variable.
|
|
||||||
4) If every bus in array is muted, unmute the first bus specified in array.
|
|
||||||
|
|
||||||
Credits go to @bobsupercow
|
|
||||||
#>
|
|
||||||
|
|
||||||
[cmdletbinding()]
|
|
||||||
param()
|
|
||||||
|
|
||||||
Import-Module ..\..\lib\Voicemeeter.psm1
|
|
||||||
|
|
||||||
try {
|
|
||||||
$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
|
|
||||||
foreach ($bus in $buses) {
|
|
||||||
# 2)
|
|
||||||
if (-not $bus.mute) {
|
|
||||||
"Bus $($bus.index) is unmuted... muting it" | Write-Host
|
|
||||||
$unmutedIndex = $buses.IndexOf($bus)
|
|
||||||
$bus.mute = $true
|
|
||||||
|
|
||||||
# 3)
|
|
||||||
if ($buses[++$unmutedIndex]) {
|
|
||||||
"Unmuting Bus $($buses[$unmutedIndex].index)" | Write-Host
|
|
||||||
$buses[$unmutedIndex].mute = $false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
else { Clear-Variable unmutedIndex }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
# 4)
|
|
||||||
if ($null -eq $unmutedIndex) {
|
|
||||||
$buses[0].mute = $false
|
|
||||||
"Unmuting Bus $($buses[0].index)" | Write-Host
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
finally { Disconnect-Voicemeeter }
|
|
||||||
70
examples/nextbus/Rotate-Buses.ps1
Normal file
70
examples/nextbus/Rotate-Buses.ps1
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Rotates through specified Voicemeeter buses, unmuting one at a time.
|
||||||
|
.DESCRIPTION
|
||||||
|
This script connects to Voicemeeter Potato and allows the user to rotate through a set
|
||||||
|
of buses (1, 2, 4, and 6). When the user presses Enter, the next bus in the sequence is unmuted,
|
||||||
|
while all other specified buses are muted. The user can exit the rotation by typing 'Q'.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[cmdletbinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
Import-Module ..\..\lib\Voicemeeter.psm1
|
||||||
|
|
||||||
|
class BusRotator {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Class to manage rotating through Voicemeeter buses.
|
||||||
|
#>
|
||||||
|
[object]$vmr = $null
|
||||||
|
[int]$CurrentIndex = -1
|
||||||
|
[object[]]$Buses
|
||||||
|
|
||||||
|
BusRotator([object]$vmr, [object[]]$buses) {
|
||||||
|
$this.vmr = $vmr
|
||||||
|
$this.Buses = $buses
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden [object] GetNextBus() {
|
||||||
|
# Mute all buses in the list
|
||||||
|
foreach ($bus in $this.Buses) {
|
||||||
|
$bus.mute = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
# Determine the next bus to unmute
|
||||||
|
$this.CurrentIndex = ($this.CurrentIndex + 1) % $this.Buses.Count
|
||||||
|
|
||||||
|
return $this.Buses[$this.CurrentIndex]
|
||||||
|
}
|
||||||
|
|
||||||
|
[object] UnmuteNextBus() {
|
||||||
|
$nextBus = $this.GetNextBus()
|
||||||
|
$nextBus.mute = $false
|
||||||
|
return $nextBus
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
$vmr = Connect-Voicemeeter -Kind 'potato'
|
||||||
|
|
||||||
|
# Mute all buses initially
|
||||||
|
foreach ($bus in $vmr.bus) {
|
||||||
|
$bus.mute = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
$busesToRotate = @(
|
||||||
|
$vmr.bus[1],
|
||||||
|
$vmr.bus[2],
|
||||||
|
$vmr.bus[4],
|
||||||
|
$vmr.bus[6]
|
||||||
|
)
|
||||||
|
|
||||||
|
$rotator = [BusRotator]::new($vmr, $busesToRotate)
|
||||||
|
while ((Read-Host "Press Enter to rotate buses or type 'Q' to quit.") -ne 'Q') {
|
||||||
|
$nextBus = $rotator.UnmuteNextBus()
|
||||||
|
Write-Host "Bus $nextBus is now unmuted."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally { Disconnect-Voicemeeter }
|
||||||
293
examples/obs/Sync-OBS-Voicemeeter.ps1
Normal file
293
examples/obs/Sync-OBS-Voicemeeter.ps1
Normal file
@@ -0,0 +1,293 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Synchronizes OBS Studio scene changes with Voicemeeter audio settings.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script monitors OBS Studio for scene changes via WebSocket connection and
|
||||||
|
automatically adjusts Voicemeeter audio settings based on the active scene.
|
||||||
|
|
||||||
|
.PARAMETER ConfigPath
|
||||||
|
Path to the configuration file. Defaults to 'config.psd1' in the script directory.
|
||||||
|
|
||||||
|
.PARAMETER VoicemeeterKind
|
||||||
|
Type of Voicemeeter to connect to. Defaults to 'basic'.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\Vm-Obs-Sync.ps1
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
.\Vm-Obs-Sync.ps1 -ConfigPath "C:\myconfig.psd1" -VoicemeeterKind "banana"
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param(
|
||||||
|
[string]$ConfigPath = (Join-Path $PSScriptRoot 'config.psd1'),
|
||||||
|
[ValidateSet('basic', 'banana', 'potato')]
|
||||||
|
[string]$VoicemeeterKind = 'basic'
|
||||||
|
)
|
||||||
|
|
||||||
|
#Requires -Modules obs-powershell
|
||||||
|
|
||||||
|
# Import required modules
|
||||||
|
try {
|
||||||
|
Import-Module ..\..\lib\Voicemeeter.psm1
|
||||||
|
Import-Module obs-powershell
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "Failed to import required modules: $($_.Exception.Message)"
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Script-level variables
|
||||||
|
$script:vmr = $null
|
||||||
|
$script:obsJob = $null
|
||||||
|
$script:shouldExit = $false
|
||||||
|
|
||||||
|
#region Helper Functions
|
||||||
|
|
||||||
|
function Write-Log {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Writes timestamped log messages to the console.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory, ValueFromPipeline)]
|
||||||
|
[string]$Message,
|
||||||
|
[ValidateSet('Info', 'Warning', 'Error')]
|
||||||
|
[string]$Level = 'Info'
|
||||||
|
)
|
||||||
|
|
||||||
|
$timestamp = Get-Date -Format 'yyyy-MM-dd HH:mm:ss'
|
||||||
|
$logMessage = "[$timestamp] [$Level] $Message"
|
||||||
|
|
||||||
|
switch ($Level) {
|
||||||
|
'Info' { Write-Information $logMessage -InformationAction Continue }
|
||||||
|
'Warning' { Write-Warning $logMessage }
|
||||||
|
'Error' { Write-Error $logMessage }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-ConnectionConfig {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Loads OBS connection configuration from file.
|
||||||
|
#>
|
||||||
|
param([string]$Path = $ConfigPath)
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (-not (Test-Path $Path)) {
|
||||||
|
throw "Configuration file not found: $Path"
|
||||||
|
}
|
||||||
|
|
||||||
|
$config = Import-PowerShellDataFile -Path $Path -ErrorAction Stop
|
||||||
|
|
||||||
|
# Validate required properties
|
||||||
|
$requiredProperties = @('host', 'port', 'password')
|
||||||
|
foreach ($prop in $requiredProperties) {
|
||||||
|
if (-not $config.ContainsKey($prop)) {
|
||||||
|
throw "Missing required configuration property: $prop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Configuration loaded successfully from: $Path"
|
||||||
|
return $config
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to load configuration: $($_.Exception.Message)" -Level Error
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Initialize-Connections {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Initializes connections to Voicemeeter and OBS.
|
||||||
|
#>
|
||||||
|
try {
|
||||||
|
$script:vmr = Connect-Voicemeeter -Kind $VoicemeeterKind -ErrorAction Stop
|
||||||
|
Write-Log 'Voicemeeter connection established'
|
||||||
|
|
||||||
|
$obsConfig = Get-ConnectionConfig
|
||||||
|
|
||||||
|
|
||||||
|
$webSocketUri = "ws://$($obsConfig.host):$($obsConfig.port)"
|
||||||
|
$script:obsJob = Watch-OBS -WebSocketURI $webSocketUri -WebSocketToken $obsConfig.password -ErrorAction Stop
|
||||||
|
Write-Log "OBS connection at $webSocketUri established"
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Failed to initialize connections: $($_.Exception.Message)" -Level Error
|
||||||
|
throw
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Disconnect-All {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Safely disconnects from all services.
|
||||||
|
#>
|
||||||
|
Write-Log 'Cleaning up connections...'
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($script:obsJob) {
|
||||||
|
Remove-Job -Job $script:obsJob -Force -ErrorAction SilentlyContinue
|
||||||
|
Disconnect-OBS -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Error disconnecting from OBS: $($_.Exception.Message)" -Level Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($script:vmr) {
|
||||||
|
Disconnect-Voicemeeter -ErrorAction SilentlyContinue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Error disconnecting from Voicemeeter: $($_.Exception.Message)" -Level Warning
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log 'Cleanup completed'
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Event Handlers
|
||||||
|
|
||||||
|
function Invoke-CurrentProgramSceneChanged {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Handles OBS scene change events.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[System.Object]$EventData
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $EventData.sceneName) {
|
||||||
|
Write-Log 'Scene change event received but no scene name provided' -Level Warning
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log "Scene changed to: $($EventData.sceneName)"
|
||||||
|
|
||||||
|
try {
|
||||||
|
switch ($EventData.sceneName) {
|
||||||
|
'START' {
|
||||||
|
Write-Log 'Toggling mute for strip 0'
|
||||||
|
$script:vmr.strip[0].mute = !$script:vmr.strip[0].mute
|
||||||
|
}
|
||||||
|
'BRB' {
|
||||||
|
Write-Log 'Setting gain to -8.3dB for strip 0'
|
||||||
|
$script:vmr.strip[0].gain = -8.3
|
||||||
|
}
|
||||||
|
'END' {
|
||||||
|
Write-Log 'Enabling mono for strip 0'
|
||||||
|
$script:vmr.strip[0].mono = $true
|
||||||
|
}
|
||||||
|
'LIVE' {
|
||||||
|
Write-Log 'Setting color_x to 0.3 for strip 0'
|
||||||
|
$script:vmr.strip[0].color_x = 0.3
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
Write-Log "Unknown scene '$($EventData.sceneName)'. Expected: START, BRB, END, or LIVE" -Level Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Error processing scene change: $($_.Exception.Message)" -Level Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-ExitStarted {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Handles OBS exit events.
|
||||||
|
#>
|
||||||
|
param([System.Object]$EventData)
|
||||||
|
|
||||||
|
Write-Log 'OBS shutdown detected - initiating graceful exit'
|
||||||
|
$script:shouldExit = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Invoke-EventDispatcher {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Dispatches OBS events to appropriate handlers.
|
||||||
|
#>
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[System.Object]$EventData
|
||||||
|
)
|
||||||
|
|
||||||
|
if (-not $EventData.eventType) {
|
||||||
|
Write-Log 'Event received without eventType property' -Level Warning
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
$handlerName = "Invoke-$($EventData.eventType)"
|
||||||
|
|
||||||
|
if (Get-Command $handlerName -ErrorAction SilentlyContinue) {
|
||||||
|
try {
|
||||||
|
& $handlerName -EventData $EventData.eventData
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Error in event handler '$handlerName': $($_.Exception.Message)" -Level Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Log "No handler found for event type: $($EventData.eventType)" -Level Warning
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Main Execution
|
||||||
|
|
||||||
|
function Start-VoicemeeterObsSync {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Main execution function for the sync process.
|
||||||
|
#>
|
||||||
|
Write-Log 'Starting Voicemeeter-OBS synchronization service'
|
||||||
|
|
||||||
|
try {
|
||||||
|
Initialize-Connections
|
||||||
|
|
||||||
|
Write-Log 'Monitoring OBS events... Press Ctrl+C to stop'
|
||||||
|
|
||||||
|
while (-not $script:shouldExit) {
|
||||||
|
try {
|
||||||
|
$obsEvents = Receive-Job -Job $script:obsJob -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
foreach ($obsEvent in $obsEvents) {
|
||||||
|
if ($obsEvent.MessageData.op -eq 5) {
|
||||||
|
Invoke-EventDispatcher -EventData $obsEvent.MessageData.d
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-Sleep -Milliseconds 100
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Error processing OBS events: $($_.Exception.Message)" -Level Error
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Log "Fatal error: $($_.Exception.Message)" -Level Error
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
Disconnect-All
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Log 'Voicemeeter-OBS synchronization service stopped'
|
||||||
|
}
|
||||||
|
|
||||||
|
# Handle Ctrl+C gracefully
|
||||||
|
$null = Register-EngineEvent -SourceIdentifier PowerShell.Exiting -Action {
|
||||||
|
$script:shouldExit = $true
|
||||||
|
}
|
||||||
|
|
||||||
|
Start-VoicemeeterObsSync
|
||||||
|
|
||||||
|
#endregion
|
||||||
@@ -1,68 +0,0 @@
|
|||||||
[cmdletbinding()]
|
|
||||||
param()
|
|
||||||
|
|
||||||
Import-Module ..\..\lib\Voicemeeter.psm1
|
|
||||||
Import-Module obs-powershell
|
|
||||||
|
|
||||||
function CurrentProgramSceneChanged {
|
|
||||||
param([System.Object]$data)
|
|
||||||
Write-Host 'Switched to scene', $data.sceneName
|
|
||||||
|
|
||||||
switch ($data.sceneName) {
|
|
||||||
'START' {
|
|
||||||
$vmr.strip[0].mute = !$vmr.strip[0].mute
|
|
||||||
}
|
|
||||||
'BRB' {
|
|
||||||
$vmr.strip[0].gain = -8.3
|
|
||||||
}
|
|
||||||
'END' {
|
|
||||||
$vmr.strip[0].mono = $true
|
|
||||||
}
|
|
||||||
'LIVE' {
|
|
||||||
$vmr.strip[0].color_x = 0.3
|
|
||||||
}
|
|
||||||
default { 'Expected START, BRB, END or LIVE scene' | Write-Warning; return }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ExitStarted {
|
|
||||||
param([System.Object]$data)
|
|
||||||
'OBS shutdown has begun!' | Write-Host
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
function eventHandler($data) {
|
|
||||||
if (Get-Command $data.eventType -ErrorAction SilentlyContinue) {
|
|
||||||
& $data.eventType -data $data.eventData
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ConnFromFile {
|
|
||||||
$configpath = Join-Path $PSScriptRoot 'config.psd1'
|
|
||||||
return Import-PowerShellDataFile -Path $configpath
|
|
||||||
}
|
|
||||||
|
|
||||||
function main {
|
|
||||||
$vmr = Connect-Voicemeeter -Kind 'basic'
|
|
||||||
|
|
||||||
$conn = ConnFromFile
|
|
||||||
$job = Watch-OBS -WebSocketURI "ws://$($conn.host):$($conn.port)" -WebSocketToken $conn.password
|
|
||||||
|
|
||||||
try {
|
|
||||||
while ($true) {
|
|
||||||
Receive-Job -Job $job | ForEach-Object {
|
|
||||||
$data = $_.MessageData
|
|
||||||
|
|
||||||
if ($data.op -eq 5) {
|
|
||||||
eventHandler($data.d)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
Disconnect-OBS
|
|
||||||
Disconnect-Voicemeeter
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main
|
|
||||||
@@ -75,6 +75,22 @@ class Remote {
|
|||||||
[void] PDirty() { P_Dirty }
|
[void] PDirty() { P_Dirty }
|
||||||
|
|
||||||
[void] MDirty() { M_Dirty }
|
[void] MDirty() { M_Dirty }
|
||||||
|
|
||||||
|
[int] GetOutputCount() {
|
||||||
|
return Device_Count -IS_OUT $true
|
||||||
|
}
|
||||||
|
|
||||||
|
[int] GetInputCount() {
|
||||||
|
return Device_Count
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSObject] GetOutputDevice([int]$index) {
|
||||||
|
return Device_Desc -INDEX $index -IS_OUT $true
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSObject] GetInputDevice([int]$index) {
|
||||||
|
return Device_Desc -INDEX $index
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class RemoteBasic : Remote {
|
class RemoteBasic : Remote {
|
||||||
|
|||||||
55
lib/base.ps1
55
lib/base.ps1
@@ -225,4 +225,59 @@ function Get_Level {
|
|||||||
throw [CAPIError]::new($retval, 'VBVMR_GetLevel')
|
throw [CAPIError]::new($retval, 'VBVMR_GetLevel')
|
||||||
}
|
}
|
||||||
[float]$ptr
|
[float]$ptr
|
||||||
|
}
|
||||||
|
|
||||||
|
function Device_Count {
|
||||||
|
param(
|
||||||
|
[bool]$IS_OUT = $false
|
||||||
|
)
|
||||||
|
if ($IS_OUT) {
|
||||||
|
$retval = [int][Voicemeeter.Remote]::VBVMR_Output_GetDeviceNumber()
|
||||||
|
if ($retval -lt 0) {
|
||||||
|
throw [CAPIError]::new($retval, 'VBVMR_Output_GetDeviceNumber')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$retval = [int][Voicemeeter.Remote]::VBVMR_Input_GetDeviceNumber()
|
||||||
|
if ($retval -lt 0) {
|
||||||
|
throw [CAPIError]::new($retval, 'VBVMR_Input_GetDeviceNumber')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$retval
|
||||||
|
}
|
||||||
|
|
||||||
|
function Device_Desc {
|
||||||
|
param(
|
||||||
|
[int]$INDEX, [bool]$IS_OUT = $false
|
||||||
|
)
|
||||||
|
$driver = 0
|
||||||
|
$name = [System.Byte[]]::new(512)
|
||||||
|
$hardwareid = [System.Byte[]]::new(512)
|
||||||
|
|
||||||
|
if ($IS_OUT) {
|
||||||
|
$retval = [int][Voicemeeter.Remote]::VBVMR_Output_GetDeviceDescA($INDEX, [ref]$driver, $name, $hardwareid)
|
||||||
|
if ($retval -notin @(0)) {
|
||||||
|
throw [CAPIError]::new($retval, 'VBVMR_Output_GetDeviceDescA')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$retval = [int][Voicemeeter.Remote]::VBVMR_Input_GetDeviceDescA($INDEX, [ref]$driver, $name, $hardwareid)
|
||||||
|
if ($retval -notin @(0)) {
|
||||||
|
throw [CAPIError]::new($retval, 'VBVMR_Input_GetDeviceDescA')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$drivers = @{
|
||||||
|
1 = 'mme'
|
||||||
|
3 = 'wdm'
|
||||||
|
4 = 'ks'
|
||||||
|
5 = 'asio'
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSCustomObject]@{
|
||||||
|
Driver = $drivers[$driver]
|
||||||
|
Name = [System.Text.Encoding]::ASCII.GetString($name).Trim([char]0)
|
||||||
|
HardwareId = [System.Text.Encoding]::ASCII.GetString($hardwareid).Trim([char]0)
|
||||||
|
IsOutput = $IS_OUT
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -43,6 +43,15 @@ function Setup_DLL {
|
|||||||
|
|
||||||
[DllImport(@"$dll")]
|
[DllImport(@"$dll")]
|
||||||
public static extern int VBVMR_GetLevel(Int64 mode, Int64 index, ref float ptr);
|
public static extern int VBVMR_GetLevel(Int64 mode, Int64 index, ref float ptr);
|
||||||
|
|
||||||
|
[DllImport(@"$dll")]
|
||||||
|
public static extern int VBVMR_Output_GetDeviceNumber();
|
||||||
|
[DllImport(@"$dll")]
|
||||||
|
public static extern int VBVMR_Input_GetDeviceNumber();
|
||||||
|
[DllImport(@"$dll")]
|
||||||
|
public static extern int VBVMR_Output_GetDeviceDescA(Int64 index, ref int type, byte[] name, byte[] hardwareid);
|
||||||
|
[DllImport(@"$dll")]
|
||||||
|
public static extern int VBVMR_Input_GetDeviceDescA(Int64 index, ref int type, byte[] name, byte[] hardwareid);
|
||||||
"@
|
"@
|
||||||
|
|
||||||
Add-Type -MemberDefinition $Signature -Name Remote -Namespace Voicemeeter -PassThru | Out-Null
|
Add-Type -MemberDefinition $Signature -Name Remote -Namespace Voicemeeter -PassThru | Out-Null
|
||||||
|
|||||||
10
lib/bus.ps1
10
lib/bus.ps1
@@ -97,7 +97,7 @@ class VirtualBus : Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class BusDevice : IODevice {
|
class BusDevice : IODevice {
|
||||||
BusDevice ([int]$index, [Object]$remote) : base ($index, $remote) {
|
BusDevice ([int]$index, [Object]$remote) : base ($index, $remote, 'Output') {
|
||||||
if ($this.index -eq 0) {
|
if ($this.index -eq 0) {
|
||||||
AddStringMembers -PARAMS @('asio') -WriteOnly
|
AddStringMembers -PARAMS @('asio') -WriteOnly
|
||||||
}
|
}
|
||||||
@@ -106,6 +106,14 @@ class BusDevice : IODevice {
|
|||||||
[string] identifier () {
|
[string] identifier () {
|
||||||
return 'Bus[' + $this.index + '].Device'
|
return 'Bus[' + $this.index + '].Device'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[int] EnumCount () {
|
||||||
|
return $this.remote.GetOutputCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSObject] EnumDevice ([int]$eIndex) {
|
||||||
|
return $this.remote.GetOutputDevice($eIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Make_Buses ([Object]$remote) {
|
function Make_Buses ([Object]$remote) {
|
||||||
|
|||||||
116
lib/io.ps1
116
lib/io.ps1
@@ -100,9 +100,123 @@ class EqCell : IRemote {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class IODevice : IRemote {
|
class IODevice : IRemote {
|
||||||
IODevice ([int]$index, [Object]$remote) : base ($index, $remote) {
|
[string]$kindOfDevice
|
||||||
|
[Hashtable]$drivers
|
||||||
|
|
||||||
|
IODevice ([int]$index, [Object]$remote, [string]$kindOfDevice) : base ($index, $remote) {
|
||||||
|
$this.kindOfDevice = $kindOfDevice
|
||||||
|
|
||||||
AddStringMembers -WriteOnly -PARAMS @('wdm', 'ks', 'mme')
|
AddStringMembers -WriteOnly -PARAMS @('wdm', 'ks', 'mme')
|
||||||
AddStringMembers -ReadOnly -PARAMS @('name')
|
AddStringMembers -ReadOnly -PARAMS @('name')
|
||||||
AddIntMembers -ReadOnly -PARAMS @('sr')
|
AddIntMembers -ReadOnly -PARAMS @('sr')
|
||||||
|
|
||||||
|
$this.drivers = @{
|
||||||
|
'1' = 'mme'
|
||||||
|
'4' = 'wdm'
|
||||||
|
'8' = 'ks'
|
||||||
|
'256' = 'asio'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[int] EnumCount () {
|
||||||
|
throw [System.NotImplementedException]::new("$($this.GetType().Name) must override EnumCount()")
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSObject] EnumDevice ([int]$eIndex) {
|
||||||
|
throw [System.NotImplementedException]::new("$($this.GetType().Name) must override EnumDevice()")
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSObject] Get () {
|
||||||
|
$device = [PSCustomObject]@{
|
||||||
|
Driver = $this.driver
|
||||||
|
Name = $this.name
|
||||||
|
HardwareId = ''
|
||||||
|
IsOutput = $this.kindOfDevice -eq 'Output'
|
||||||
|
}
|
||||||
|
if (-not [string]::IsNullOrEmpty($device.Name)) {
|
||||||
|
for ($i = 0; $i -lt $this.EnumCount(); $i++) {
|
||||||
|
$eDevice = $this.EnumDevice($i)
|
||||||
|
if ($eDevice.Name -eq $device.Name -and $eDevice.Driver -eq $device.Driver) {
|
||||||
|
$device = $eDevice
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $device
|
||||||
|
}
|
||||||
|
|
||||||
|
[void] Set ([PSObject]$device) {
|
||||||
|
$required = 'IsOutput', 'Driver', 'Name'
|
||||||
|
$missing = $required | Where-Object { $null -eq $device.PSObject.Properties[$_] }
|
||||||
|
|
||||||
|
if ($missing) {
|
||||||
|
throw [System.ArgumentException]::new(("Invalid device object. Missing member(s): {0}" -f ($missing -join ', ')), 'device')
|
||||||
|
}
|
||||||
|
|
||||||
|
$expectsOutput = ($this.kindOfDevice -eq 'Output')
|
||||||
|
if ([bool]$device.IsOutput -ne $expectsOutput) {
|
||||||
|
throw [System.ArgumentException]::new(("Device direction mismatch. Expected IsOutput={0}." -f $expectsOutput), 'device')
|
||||||
|
}
|
||||||
|
|
||||||
|
$d = $device.Driver
|
||||||
|
$n = $device.Name
|
||||||
|
|
||||||
|
if (-not ($d -is [string])) {
|
||||||
|
throw [System.ArgumentException]::new('Invalid device object. Driver must be a string.', 'device')
|
||||||
|
}
|
||||||
|
if (-not ($n -is [string])) {
|
||||||
|
throw [System.ArgumentException]::new('Invalid device object. Name must be a string.', 'device')
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($d -eq '' -and $n -eq '') { $this.Clear(); return }
|
||||||
|
if ($d -notin $this.drivers.Values) {
|
||||||
|
throw [System.ArgumentOutOfRangeException]::new('device.Driver', $d, 'Invalid device driver provided to Set method.')
|
||||||
|
}
|
||||||
|
|
||||||
|
$this.Setter($d, $n)
|
||||||
|
}
|
||||||
|
|
||||||
|
[void] Clear () {
|
||||||
|
$this.Setter('mme', '')
|
||||||
|
}
|
||||||
|
|
||||||
|
hidden $_driver = $($this | Add-Member ScriptProperty 'driver' `
|
||||||
|
{
|
||||||
|
if ([string]::IsNullOrEmpty($this.name)) { return '' }
|
||||||
|
|
||||||
|
$type = $null
|
||||||
|
try {
|
||||||
|
$tmp = [System.IO.Path]::Combine([System.IO.Path]::GetTempPath(), "vmrtmp-$(New-Guid).xml")
|
||||||
|
$this.remote.Setter('Command.Save', $tmp)
|
||||||
|
|
||||||
|
$timeout = New-TimeSpan -Seconds 2
|
||||||
|
$sw = [Diagnostics.Stopwatch]::StartNew()
|
||||||
|
$line = $null
|
||||||
|
do {
|
||||||
|
if (Test-Path $tmp) {
|
||||||
|
try {
|
||||||
|
$line = Get-Content $tmp | Select-String -Pattern "<$($this.kindOfDevice)Dev index='$($this.index + 1)'" -List
|
||||||
|
if ($line) { break }
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
}
|
||||||
|
Start-Sleep -Milliseconds 20
|
||||||
|
} while ($sw.elapsed -lt $timeout)
|
||||||
|
if ($line -and $line.ToString() -match "type='(?<type>\d+)'") {
|
||||||
|
$type = $matches['type']
|
||||||
|
}
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
if (Test-Path $tmp) {
|
||||||
|
Remove-Item $tmp -Force
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($type -notin $this.drivers.Keys) { return 'unknown' }
|
||||||
|
return $this.drivers[$type]
|
||||||
|
} `
|
||||||
|
{
|
||||||
|
Write-Warning ("ERROR: $($this.identifier()).driver is read only")
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
@@ -155,12 +155,20 @@ class StripEq : IOEq {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class StripDevice : IODevice {
|
class StripDevice : IODevice {
|
||||||
StripDevice ([int]$index, [Object]$remote) : base ($index, $remote) {
|
StripDevice ([int]$index, [Object]$remote) : base ($index, $remote, 'Input') {
|
||||||
}
|
}
|
||||||
|
|
||||||
[string] identifier () {
|
[string] identifier () {
|
||||||
return 'Strip[' + $this.index + '].Device'
|
return 'Strip[' + $this.index + '].Device'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[int] EnumCount () {
|
||||||
|
return $this.remote.GetInputCount()
|
||||||
|
}
|
||||||
|
|
||||||
|
[PSObject] EnumDevice ([int]$eIndex) {
|
||||||
|
return $this.remote.GetInputDevice($eIndex)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class VirtualStrip : Strip {
|
class VirtualStrip : Strip {
|
||||||
|
|||||||
@@ -850,24 +850,47 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
|
|||||||
@{ Index = $phys_in }
|
@{ Index = $phys_in }
|
||||||
) {
|
) {
|
||||||
Context 'Device' -ForEach @(
|
Context 'Device' -ForEach @(
|
||||||
@{ Value = 'testInput' }, @{ Value = '' }
|
@{ Driver = 'mme'; Value = 'testMme'; Expected = 'mme' }
|
||||||
|
@{ Driver = 'wdm'; Value = 'testWdm'; Expected = 'wdm' }
|
||||||
|
@{ Driver = 'ks'; Value = 'testKs'; Expected = 'ks' }
|
||||||
|
@{ Driver = 'mme'; Value = ''; Expected = '' }
|
||||||
) {
|
) {
|
||||||
It "Should set Strip[$index].Device.wdm" {
|
BeforeEach {
|
||||||
$vmr.strip[$index].device.wdm = $value
|
$vmr.strip[$index].device.Clear()
|
||||||
|
Start-Sleep -Milliseconds 800
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should set Strip[$index].Device.$($driver)" {
|
||||||
|
$vmr.strip[$index].device.name | Should -Be ''
|
||||||
|
$vmr.strip[$index].device.driver | Should -Be ''
|
||||||
|
|
||||||
|
$vmr.strip[$index].device.$($driver) = $value
|
||||||
Start-Sleep -Milliseconds 800
|
Start-Sleep -Milliseconds 800
|
||||||
$vmr.strip[$index].device.name | Should -Be $value
|
$vmr.strip[$index].device.name | Should -Be $value
|
||||||
|
$vmr.strip[$index].device.driver | Should -Be $expected
|
||||||
}
|
}
|
||||||
|
|
||||||
It "Should set Strip[$index].Device.ks" {
|
It "Should set Strip[$index].Device" -ForEach @(
|
||||||
$vmr.strip[$index].device.ks = $value
|
@{
|
||||||
|
Clear = [PSCustomObject]@{ Driver = ''; Name = ''; HardwareId = ''; IsOutput = $false }
|
||||||
|
Device = [PSCustomObject]@{ Driver = $expected; Name = $value; HardwareId = ''; IsOutput = $false }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
$initial = $vmr.strip[$index].device.Get()
|
||||||
|
|
||||||
|
$initial.Driver | Should -Be $clear.Driver
|
||||||
|
$initial.Name | Should -Be $clear.Name
|
||||||
|
$initial.HardwareId | Should -Be $clear.HardwareId
|
||||||
|
$initial.IsOutput | Should -Be $clear.IsOutput
|
||||||
|
|
||||||
|
$vmr.strip[$index].device.Set($device)
|
||||||
Start-Sleep -Milliseconds 800
|
Start-Sleep -Milliseconds 800
|
||||||
$vmr.strip[$index].device.name | Should -Be $value
|
$result = $vmr.strip[$index].device.Get()
|
||||||
}
|
|
||||||
|
$result.Driver | Should -Be $device.Driver
|
||||||
It "Should set Strip[$index].Device.mme" {
|
$result.Name | Should -Be $device.Name
|
||||||
$vmr.strip[$index].device.mme = $value
|
$result.HardwareId | Should -Be $device.HardwareId
|
||||||
Start-Sleep -Milliseconds 800
|
$result.IsOutput | Should -Be $device.IsOutput
|
||||||
$vmr.strip[$index].device.name | Should -Be $value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -983,24 +1006,47 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
|
|||||||
@{ Index = $phys_out }
|
@{ Index = $phys_out }
|
||||||
) {
|
) {
|
||||||
Context 'Device' -ForEach @(
|
Context 'Device' -ForEach @(
|
||||||
@{ Value = 'testOutput' }, @{ Value = '' }
|
@{ Driver = 'mme'; Value = 'testMme'; Expected = 'mme' }
|
||||||
|
@{ Driver = 'wdm'; Value = 'testWdm'; Expected = 'wdm' }
|
||||||
|
@{ Driver = 'ks'; Value = 'testKs'; Expected = 'ks' }
|
||||||
|
@{ Driver = 'mme'; Value = ''; Expected = '' }
|
||||||
) {
|
) {
|
||||||
It "Should set Bus[$index].Device.wdm" {
|
BeforeEach {
|
||||||
$vmr.bus[$index].device.wdm = $value
|
$vmr.bus[$index].device.Clear()
|
||||||
|
Start-Sleep -Milliseconds 800
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should set Bus[$index].Device.$($driver)" {
|
||||||
|
$vmr.bus[$index].device.name | Should -Be ''
|
||||||
|
$vmr.bus[$index].device.driver | Should -Be ''
|
||||||
|
|
||||||
|
$vmr.bus[$index].device.$($driver) = $value
|
||||||
Start-Sleep -Milliseconds 800
|
Start-Sleep -Milliseconds 800
|
||||||
$vmr.bus[$index].device.name | Should -Be $value
|
$vmr.bus[$index].device.name | Should -Be $value
|
||||||
|
$vmr.bus[$index].device.driver | Should -Be $expected
|
||||||
}
|
}
|
||||||
|
|
||||||
It "Should set Bus[$index].Device.ks" {
|
It "Should set Bus[$index].Device" -ForEach @(
|
||||||
$vmr.bus[$index].device.ks = $value
|
@{
|
||||||
|
Clear = [PSCustomObject]@{ Driver = ''; Name = ''; HardwareId = ''; IsOutput = $true }
|
||||||
|
Device = [PSCustomObject]@{ Driver = $expected; Name = $value; HardwareId = ''; IsOutput = $true }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
$initial = $vmr.bus[$index].device.Get()
|
||||||
|
|
||||||
|
$initial.Driver | Should -Be $clear.Driver
|
||||||
|
$initial.Name | Should -Be $clear.Name
|
||||||
|
$initial.HardwareId | Should -Be $clear.HardwareId
|
||||||
|
$initial.IsOutput | Should -Be $clear.IsOutput
|
||||||
|
|
||||||
|
$vmr.bus[$index].device.Set($device)
|
||||||
Start-Sleep -Milliseconds 800
|
Start-Sleep -Milliseconds 800
|
||||||
$vmr.bus[$index].device.name | Should -Be $value
|
$result = $vmr.bus[$index].device.Get()
|
||||||
}
|
|
||||||
|
$result.Driver | Should -Be $device.Driver
|
||||||
It "Should set Bus[$index].Device.mme" {
|
$result.Name | Should -Be $device.Name
|
||||||
$vmr.bus[$index].device.mme = $value
|
$result.HardwareId | Should -Be $device.HardwareId
|
||||||
Start-Sleep -Milliseconds 800
|
$result.IsOutput | Should -Be $device.IsOutput
|
||||||
$vmr.bus[$index].device.name | Should -Be $value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1009,24 +1055,47 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
|
|||||||
@{ Index = $virt_out }
|
@{ Index = $virt_out }
|
||||||
) {
|
) {
|
||||||
Context 'Device' -Skip:$ifNotBasic -ForEach @(
|
Context 'Device' -Skip:$ifNotBasic -ForEach @(
|
||||||
@{ Value = 'testOutput' }, @{ Value = '' }
|
@{ Driver = 'mme'; Value = 'testMme'; Expected = 'mme' }
|
||||||
|
@{ Driver = 'wdm'; Value = 'testWdm'; Expected = 'wdm' }
|
||||||
|
@{ Driver = 'ks'; Value = 'testKs'; Expected = 'ks' }
|
||||||
|
@{ Driver = 'mme'; Value = ''; Expected = '' }
|
||||||
) {
|
) {
|
||||||
It "Should set Bus[$index].Device.wdm" {
|
BeforeEach {
|
||||||
$vmr.bus[$index].device.wdm = $value
|
$vmr.bus[$index].device.Clear()
|
||||||
|
Start-Sleep -Milliseconds 800
|
||||||
|
}
|
||||||
|
|
||||||
|
It "Should set Bus[$index].Device.$($driver)" {
|
||||||
|
$vmr.bus[$index].device.name | Should -Be ''
|
||||||
|
$vmr.bus[$index].device.driver | Should -Be ''
|
||||||
|
|
||||||
|
$vmr.bus[$index].device.$($driver) = $value
|
||||||
Start-Sleep -Milliseconds 800
|
Start-Sleep -Milliseconds 800
|
||||||
$vmr.bus[$index].device.name | Should -Be $value
|
$vmr.bus[$index].device.name | Should -Be $value
|
||||||
|
$vmr.bus[$index].device.driver | Should -Be $expected
|
||||||
}
|
}
|
||||||
|
|
||||||
It "Should set Bus[$index].Device.ks" {
|
It "Should set Bus[$index].Device" -ForEach @(
|
||||||
$vmr.bus[$index].device.ks = $value
|
@{
|
||||||
|
Clear = [PSCustomObject]@{ Driver = ''; Name = ''; HardwareId = ''; IsOutput = $true }
|
||||||
|
Device = [PSCustomObject]@{ Driver = $expected; Name = $value; HardwareId = ''; IsOutput = $true }
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
$initial = $vmr.bus[$index].device.Get()
|
||||||
|
|
||||||
|
$initial.Driver | Should -Be $clear.Driver
|
||||||
|
$initial.Name | Should -Be $clear.Name
|
||||||
|
$initial.HardwareId | Should -Be $clear.HardwareId
|
||||||
|
$initial.IsOutput | Should -Be $clear.IsOutput
|
||||||
|
|
||||||
|
$vmr.bus[$index].device.Set($device)
|
||||||
Start-Sleep -Milliseconds 800
|
Start-Sleep -Milliseconds 800
|
||||||
$vmr.bus[$index].device.name | Should -Be $value
|
$result = $vmr.bus[$index].device.Get()
|
||||||
}
|
|
||||||
|
$result.Driver | Should -Be $device.Driver
|
||||||
It "Should set Bus[$index].Device.mme" {
|
$result.Name | Should -Be $device.Name
|
||||||
$vmr.bus[$index].device.mme = $value
|
$result.HardwareId | Should -Be $device.HardwareId
|
||||||
Start-Sleep -Milliseconds 800
|
$result.IsOutput | Should -Be $device.IsOutput
|
||||||
$vmr.bus[$index].device.name | Should -Be $value
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user