54 Commits

Author SHA1 Message Date
f6c750fe56 add 4.2.0 to CHANGELOG 2026-03-15 02:00:05 +00:00
685854c35a Merge pull request #36 from pblivingston/devices-enumerator
Device enumeration
2026-03-15 01:28:27 +00:00
pblivingston
68cf0cef37 IODevice.Set readability
manual and pester tests pass
2026-03-14 19:52:46 -04:00
pblivingston
91e798caa1 Revert "revert to user folder"
This reverts commit d1dfe2de52.
2026-03-14 18:32:11 -04:00
pblivingston
d1dfe2de52 revert to user folder
should be faster this way, and it wasn't actually causing the problems i thought it was causing

pester tests pass for all kinds
2026-03-04 21:15:46 -05:00
pblivingston
ed3b7be904 HardwareId
changed capitalization for consistency
2026-03-04 20:43:48 -05:00
pblivingston
33dcc98c8f small docs corrections 2026-03-04 20:39:57 -05:00
pblivingston
55ade960f2 update docs 2026-03-04 20:02:18 -05:00
pblivingston
7d9615d760 Get(), Set($device), Clear()
methods added to IODevice

manual and pester tests pass for all kinds
2026-03-04 04:23:10 -05:00
pblivingston
4ea371af2f more IODevice.driver tweaks
use temp file instead of persistent

manual and pester tests pass
2026-03-04 04:08:08 -05:00
pblivingston
6b2031de99 clean up IODevice.driver
'none' -> ''
early return if name is empty
2026-03-03 19:54:39 -05:00
pblivingston
8d267078ff drivers
switch -> hashtable
2026-03-02 11:31:30 -05:00
pblivingston
1f5b52b439 improve speed of Select-String
device config is at the very top of the xml, so this should be much faster
2026-03-02 06:55:55 -05:00
pblivingston
defb2b68c0 driver type capitalization 2026-03-02 05:24:30 -05:00
pblivingston
2f2d4af848 IODevice.driver
initial pester tests pass for all kinds
2026-02-28 20:35:12 -05:00
pblivingston
abd792acd5 add device enumeration
bindings, base functions, and Remote methods implemented

initial manual tests for potato pass
2026-02-28 03:13:59 -05:00
b41001f4a9 add bump task 2026-02-19 09:17:38 +00:00
fc75bc2020 update obs vm sync example 2026-01-20 19:53:15 +00:00
d51ffacfaf rename nextbus script 2026-01-20 19:47:35 +00:00
fe540895b3 add cli examples to script docstring 2026-01-20 19:47:14 +00:00
7dd4c9db24 add -kind validation 2026-01-20 19:16:44 +00:00
3119b1080e make help output a console block 2026-01-20 15:33:46 +00:00
a45f7a93af add -help option to help message 2026-01-20 15:31:03 +00:00
0e552873f0 rename input variable 2026-01-20 13:35:53 +00:00
3d87f5c03f fix interactive prompt 2026-01-20 13:20:43 +00:00
aaa4672cbc fix 2026-01-20 13:12:38 +00:00
f5dead51df fix 2026-01-20 13:12:20 +00:00
259c7309dc update CLI example 2026-01-20 13:08:43 +00:00
6542473394 fix method name 2026-01-19 22:18:26 +00:00
448aa01ad2 nextbus example 2026-01-19 22:12:00 +00:00
dfa044d853 add 4.1.0 to CHANGELOG 2025-12-23 11:41:37 +00:00
21eab2e528 pass parent object references to EqChannel and EqCell 2025-12-23 02:27:30 +00:00
8679b4199f Merge pull request #34 from pblivingston/voicemeeter-x-1-2-x
Voicemeeter x.1.2.x
2025-12-22 21:44:07 +00:00
pblivingston
c64d81c36f bus trim, delay
- now pass Eq object reference to Channel
- get remote from Eq
- added kindOfEq check for trim and delay

for safety, float pester tests for potato pass
2025-12-22 11:44:40 -05:00
pblivingston
797ab2e597 Update CHANGELOG.md
updated with x.1.2.x changes
2025-12-19 20:04:15 -05:00
pblivingston
55eb851729 recorder tests, tests pass
more reliable way to locate the recording

pester tests pass for all kinds
manual tests pass for all kinds
- video vban.outstream.route
- eq.channel.cell.gain range
2025-12-19 19:44:05 -05:00
pblivingston
ef1a583351 video, midi outstream routes
- VMR bug: video outstream route is write-only; test commented
- midi outstream route test was improperly int; moved to string

prelim tests for potato pass
2025-12-19 19:44:05 -05:00
pblivingston
0d303e20be vban x.1.2.0
changes staged
- on, name, ip partial write-only resolved
- simple ranges of consecutive integers moved to AddIntMembers
- audio instream/outstream divergent behavior separated
- midi outstream route, string
- new video outstream
2025-12-19 19:44:05 -05:00
pblivingston
c446ad8c93 eq x.1.2.0
changes staged
- bus channel trim
- bus channel delay
- cell gain range
2025-12-19 19:44:04 -05:00
f5bdeb6d57 add recorder.loop removal to breaking changes. 2025-12-16 20:29:04 +00:00
be58d6a9e5 if not in interactive, check a script was passed 2025-12-16 16:45:15 +00:00
eb3af982da add 4.0.0 to CHANGELOG 2025-12-16 15:50:49 +00:00
83c81384be remove comments, they are redundant. 2025-12-14 22:05:30 +00:00
e848037b30 Merge pull request #33 from pblivingston/read-write-only
Meta functions read/write only
2025-12-14 22:03:28 +00:00
pblivingston
66b3fb355c Update CHANGELOG.md
pester tests pass for all kinds

manual tests pass for all kinds
- bus.device.asio
2025-12-13 16:39:01 -05:00
pblivingston
30da69469b asio, recorder.prefix
string pester tests for potato pass

manual test passes
- bus.device.asio
2025-12-13 16:39:01 -05:00
pblivingston
59f3168436 device properties
implemented here first because string pester tests can confirm the behavior works

string pester tests for potato pass
manual tests to confirm error behavior pass
2025-12-13 16:39:01 -05:00
pblivingston
b273aa7a51 ReadOnly, WriteOnly
added ReadOnly and WriteOnly params to meta functions that will override the setter and getter, respectively

prelim pester tests for potato for safety pass
2025-12-13 16:39:01 -05:00
b128ee0625 Merge pull request #32 from pblivingston/io-control
IO Classes
2025-12-13 21:23:59 +00:00
pblivingston
9d3f58f6f2 knob threshold, audibility
move threshold back to comp/gate/denoiser so audibility can be derived from StripKnob

pester tests for safety pass
2025-12-13 11:42:23 -05:00
pblivingston
ea6192ba5f io classes, stripknob to CHANGELOG
pester tests pass for all kinds
2025-12-12 04:35:21 -05:00
pblivingston
ea780f6595 stripknob
centralizes threshold, knob
- stripcomp
- stripgate
- stripdenoiser

prelim pester tests for potato pass
2025-12-12 04:35:21 -05:00
pblivingston
126e6172cb implement io classes
prelim pester tests for potato pass
2025-12-12 04:35:20 -05:00
pblivingston
0c60c5e6c5 Create io.ps1
- IOControl
- IOLevels
- IOEq
- IODevice
2025-12-12 04:35:20 -05:00
23 changed files with 1447 additions and 724 deletions

View File

@@ -5,29 +5,97 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Before any major/minor/patch is released all test units will be run to verify they pass.
Before any major/minor/patch is released all unit tests will be run to verify they pass.
## [Unreleased] These changes have not been added to PSGallery yet
### Breaking Changes
AddActionMembers now adds ScriptMethods instead of ScriptProperties:
- See Command section of README for details on using special commands
- See Recorder section of README for details on using playback/record actions
Deprecated Recorder.Loop removed: use Recorder.Mode.Loop
Recorder.FileType changed from method to write-only property
Strip Gainlayers are now FloatArrayMember objects, see README for details
## [4.2.0] - 2026-03-15
### Added
- IRemote base class
- ArrayMember classes for array-like properties
- 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
### Added
- Bus.EQ.Channel.Trim
- Bus.EQ.Channel.Delay
- MIDI Vban.Outstream.Route: 'none', 'midi_in', 'aux_in', 'vban_in', 'all_in', 'midi_out'
- Video Vban.Outstream
- Vban.Outstream.Vfps
- Vban.Outstream.Vformat: 'png', 'jpg'
- Vban.Outstream.Vquality
- Vban.Outstream.Vcursor
- Vban.Outstream.Route (VMR bug: currently write-only)
### Changed
- `on`, `name`, `ip` now read/write on all streams
- Simple ranges of consecutive integers moved to `AddIntMembers`
- Retained warning for `sr` and anything with atypical behavior, consistent with the rest of the module
- Retained warning for `port` because Voicemeeter will allow this to be set below 1024
- Audio instream/outstream divergent behavior via 'if' dropped in favor of explicit separation in derived classes
- Updated EQ.Channel.Cell.Gain range in README
## [4.0.0] - 2025-12-16
This release introduces some breaking changes, the affected classes are Command, Recorder and Strip.
### Breaking Changes
- Some of the Command properties have been converted to methods. See Command section in README for more details.
- Recorder.loop removed, it can be accessed through {Recorder}.{Mode}.loop.
- Recorder.FileType changed from method to write-only property.
- Strip Gainlayers are now defined by their own class and may be accessed through {Strip}.Gainlayer[i].
- The previous {Strip}.gainlayer{i} properties have been removed.
### Changed
- Meta functions can now take a -ReadOnly or -WriteOnly switch parameter
- Meta: AddBoolMembers, AddIntMembers $arg types for consistency
- Float getters/setters now default to two decimal places.
- Device: explicit $arg types for consistency
- Device members now added with meta functions
- some vban.instream | vban.outstream commands now added with meta functions
- on
- name
- ip
- cast vban getters to types for consistency
- Recorder.Armstrip|Armbus -> BoolArrayMember: now have .Get()
- Cast Recorder getters to types for consistency
- Recorder.Prefix now added with AddStringMembers
- Strip.Mono is now an alias for Strip.MC on virtual strips
- Strip.AppMute|AppGain can now take an app index; see README for details
- Strip Knob setters: explicit $arg types for consistency
### Added
- IRemote abstract base class serves as a launching point for the higher classes.
- ArrayMember classes offer a common interface for array-like properties
- Patch class
- Option class
- Device class
- EQ class
- IO classes to centralize controls common to both Strip and Bus
- IOControl
- IOLevels
- IOEq
- IODevice
- FX class
- AddAliasMembers meta function takes a hashtable `-MAP` of `alias = property`
@@ -41,8 +109,8 @@ Strip Gainlayers are now FloatArrayMember objects, see README for details
- Recorder.Armedbus
- Recorder.PreRecTime
- Recorder.Prefix
- Recorder.Eject() references 'Command.Eject'
- Recorder.State
- Recorder.Eject()
- Command.Reset()
- Command.Save($filepath)
@@ -54,9 +122,10 @@ Strip Gainlayers are now FloatArrayMember objects, see README for details
- Strip.Karaoke alias for Strip.K
- Strip.EQGain1|EQGain2|EQGain3 with bass/low, mid/med, treble/high aliases, respectively
- StripAudibility class with Strip.Audibility.Knob
- StripKnob base class which defines a knob property.
- StripAudbility class which represents the audibility knob for basic kind, it subclasses StripKnob.
- Strip.Denoiser.Threshold
- Strip.VAIO
- Strip.VAIO.
- Strip.Pitch, StripPitch class
- on
- drywet
@@ -66,28 +135,6 @@ Strip Gainlayers are now FloatArrayMember objects, see README for details
- hiformant
- recallpreset($presetIndex)
### Changed
- Device: explicit $arg types for consistency
- Meta: AddBoolMembers, AddIntMembers $arg types for consistency
- Float getters/setters now default to two decimal places.
- some vban.instream | vban.outstream commands now added with meta functions
- on
- name
- ip
- cast vban getters to types for consistency
- Recorder.Armstrip|Armbus -> BoolArrayMember: now have .Get()
- Cast Recorder getters to types for consistency
- Bus.Levels.Convert hidden and return type [float] -> [single] for naming consistency
- Strip.Mono is now an alias for Strip.MC on virtual strips
- Strip.AppMute|AppGain can now take an app index; see README for details
- Strip Knob setters: explicit $arg types for consistency
- Strip.Levels.Convert hidden and return type [float] -> [single] for naming consistency
### Fixed
- some vban commands incorrectly read-only/write-only
@@ -100,7 +147,8 @@ Strip Gainlayers are now FloatArrayMember objects, see README for details
- Recorder.channel values: 1..8 -> (2, 4, 6, 8)
- Bus.Mono -> [int] for stereo reverse
- Bus.Mono -> [int]
- This gives the user access to set stereo reverse.
- Strip.Limit type [int] -> [float]
- Missing closing parenthesis in AppMute value string

102
README.md
View File

@@ -8,9 +8,9 @@ For past/future changes to this project refer to: [CHANGELOG](CHANGELOG.md)
## Tested against
- Basic 1.1.1.9
- Banana 2.1.1.9
- Potato 3.1.1.9
- Basic 1.1.2.2
- Banana 2.1.2.2
- Potato 3.1.2.2
## Requirements
@@ -368,20 +368,32 @@ $vmr.bus[0].FadeBy(-10, 500)
The following Strip.device | Bus.device properties are available:
- name: string
- driver: string
- sr: int
- wdm: string
- ks: string
- mme: 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:
```powershell
$vmr.strip[0].device.wdm = "Mic|Line|Instrument 1 (Audient EVO4)"
$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.
asio only defined for Bus[0].Device
@@ -404,14 +416,21 @@ $vmr.strip[0].eq.on = $true
$vmr.bus[0].eq.ab = $false
```
##### channel.cell
##### channel
The following bus.eq.channel.cell properties are available:
- trim: float, from -24.00 to 24.00
- delay: float, from 0.00 to 500.00
###### cell
The following eq.channel.cell properties are available:
- on: bool
- type: int, from 0 to 6
- f: float, from 20.00 to 20000.00
- gain: float, from -12.00 to 12.00
- gain: float, from -36.00 to 18.00
- q: float, from 0.30 to 100.00
for example:
@@ -458,22 +477,66 @@ The following Vban.instream | Vban.outstream properties are available:
- on: bool
- name: string
- ip: string
- sr: in, (11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000)
- channel: int from 1 to 8
- bit: int, 16 or 24
- quality: int, from 0 to 4
- route: int, from 0 to 8
for example:
```powershell
$vmr.vban.instream[0].on = $true
$vmr.vban.outstream[3].bit = 16
$vmr.vban.outstream[9].ip = '192.168.1.154'
```
##### audio
The following audio Vban.instream | Vban.outstream properties are available:
- sr: int, (11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000)
- channel: int, from 1 to 8
- bit: int, 16 or 24
- quality: int, from 0 to 4
- route: int, from 0 to 8
SR, channel and bit are defined as readonly for instreams. Attempting to write
to those parameters will throw an error. They are read and write for outstreams.
for example:
```powershell
$vmr.vban.instream[0].route = 4
$vmr.vban.outstream[3].bit = 16
```
##### midi
The following midi Vban.outstream properties are available:
- route: string, ('none', 'midi_in', 'aux_in', 'vban_in', 'all_in', 'midi_out')
for example:
```powershell
$vmr.vban.outstream[8].route = 'aux_in'
```
##### video
The following video Vban.outstream properties are available:
- vfps: int, from 1 to 30
- vformat: string, ('png', 'jpg')
- vquality: int, from 1 to 100
- vcursor: bool
- route: int, from 0 to 4
Route is currently write-only. This is a VMR bug.
for example:
```powershell
$vmr.vban.outstream[9].vformat = 'jpg'
$vmr.vban.outstream[9].vquality = 85
$vmr.vban.outstream[9].vcursor = $true
```
### Command
Certain 'special' commands are defined by the API as performing actions rather than setting values.
@@ -742,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.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
- `VMRemoteError`: Base custom error class.

View File

@@ -9,3 +9,16 @@ tasks:
cmds:
- echo "Running tests..."
- 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}}

View File

@@ -1,64 +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()]
param(
[switch]$help,
[switch]$interactive,
[ValidateSet('basic', 'banana', 'potato')]
[String]$kind = 'banana',
[String[]]$script = @()
)
Import-Module ..\..\lib\Voicemeeter.psm1
function get-value {
param([object]$vmr, [string]$line)
if ($help -or ($script.Count -eq 0 -and -not $interactive)) {
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 {
$retval = $vmr.Getter($line)
$retval = $vmr.Getter($Parameter)
}
catch {
$retval = $vmr.Getter_String($line)
$retval = $vmr.Getter_String($Parameter)
}
$retval
}
function msgHandler {
param([object]$vmr, [string]$line)
$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)
function Invoke-VoicemeeterCLICommand {
param(
[object]$vmr,
[string]$Command
)
# 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('=')) {
"Setting $line" | Write-Debug
$vmr.SendText($line)
# Set command
elseif ($Command.Contains('=')) {
$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 {
"Getting $line" | Write-Debug
$retval = get-value -vmr $vmr -line $line
$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
}
$script | ForEach-Object {
msgHandler -vmr $vmr -line $_
foreach ($command in $script) {
Invoke-VoicemeeterCLICommand -vmr $vmr -Command $command
}
}
finally { Disconnect-Voicemeeter }
}
main
finally { Disconnect-Voicemeeter }

View File

@@ -1,32 +1,39 @@
## About
A simple voicemeeter-cli script. Offers ability to toggle, get and set parameters.
A basic command-line interface
## 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.
- -i: (-interactive) to toggle interactive mode.
- -k: (-kind) to set the kind of Voicemeeter. Defaults to banana.
- -s: (script) a string array, one string for each command.
Options:
-help Display this help message.
-interactive Start in interactive mode.
-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:
`powershell.exe .\CLI.ps1 -o -k "banana" -s "strip[0].mute", "!strip[0].mute", "strip[0].mute", "bus[2].eq.on=1", "command.lock=1"`
Expected output:
```powershell
.\CLI.ps1 -script strip[0].mute, !strip[0].mute, strip[0].mute, bus[2].eq.on=1, command.lock=1
```
Getting strip[0].mute
strip[0].mute = 0
Toggling strip[0].mute
Getting strip[0].mute
should produce the output:
```console
strip[0].mute = 1
Setting bus[2].eq.on=1
Setting command.lock=1
```
If running in interactive mode press `<Enter>` to exit.
Toggled strip[0].mute to 0
strip[0].mute = 0
Set bus[2].eq.on=1
Set command.lock=1
```

View File

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

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

View 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

View File

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

View File

@@ -4,8 +4,7 @@
. $PSScriptRoot\kinds.ps1
. $PSScriptRoot\iremote.ps1
. $PSScriptRoot\arraymember.ps1
. $PSScriptRoot\device.ps1
. $PSScriptRoot\eq.ps1
. $PSScriptRoot\io.ps1
. $PSScriptRoot\strip.ps1
. $PSScriptRoot\bus.ps1
. $PSScriptRoot\macrobuttons.ps1
@@ -76,6 +75,22 @@ class Remote {
[void] PDirty() { P_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 {

View File

@@ -225,4 +225,59 @@ function Get_Level {
throw [CAPIError]::new($retval, 'VBVMR_GetLevel')
}
[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
}
}

View File

@@ -43,6 +43,15 @@ function Setup_DLL {
[DllImport(@"$dll")]
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

View File

@@ -1,13 +1,12 @@
class Bus : IRemote {
class Bus : IOControl {
[Object]$mode
[Object]$eq
[Object]$levels
Bus ([int]$index, [Object]$remote) : base ($index, $remote) {
AddBoolMembers -PARAMS @('mute', 'sel', 'monitor')
AddBoolMembers -PARAMS @('sel', 'monitor')
AddIntMembers -PARAMS @('mono')
AddStringMembers -PARAMS @('label')
AddFloatMembers -PARAMS @('gain', 'returnreverb', 'returndelay', 'returnfx1', 'returnfx2')
AddFloatMembers -PARAMS @('returnreverb', 'returndelay', 'returnfx1', 'returnfx2')
$this.mode = [BusMode]::new($index, $remote)
$this.eq = [BusEq]::new($index, $remote)
@@ -17,17 +16,9 @@ class Bus : IRemote {
[string] identifier () {
return 'Bus[' + $this.index + ']'
}
[void] FadeTo ([single]$target, [int]$time) {
$this.Setter('FadeTo', "($target, $time)")
}
[void] FadeBy ([single]$target, [int]$time) {
$this.Setter('FadeBy', "($target, $time)")
}
}
class BusLevels : IRemote {
class BusLevels : IOLevels {
[int]$init
[int]$offset
@@ -36,23 +27,6 @@ class BusLevels : IRemote {
$this.offset = 8
}
hidden [single] Convert([single]$val) {
if ($val -gt 0) {
return [math]::Round(20 * [math]::Log10($val), 1)
}
else {
return - 200.0
}
}
[System.Collections.ArrayList] Getter([int]$mode) {
[System.Collections.ArrayList]$vals = @()
$this.init..$($this.init + $this.offset - 1) | ForEach-Object {
$vals.Add($this.Convert($(Get_Level -MODE $mode -INDEX $_)))
}
return $vals
}
[System.Collections.ArrayList] All() {
return $this.Getter(3)
}
@@ -93,7 +67,7 @@ class BusMode : IRemote {
}
}
class BusEq : Eq {
class BusEq : IOEq {
BusEq ([int]$index, [Object]$remote) : base ($index, $remote, 'Bus') {
}
@@ -122,10 +96,10 @@ class VirtualBus : Bus {
}
}
class BusDevice : Device {
BusDevice ([int]$index, [Object]$remote) : base ($index, $remote) {
class BusDevice : IODevice {
BusDevice ([int]$index, [Object]$remote) : base ($index, $remote, 'Output') {
if ($this.index -eq 0) {
$this.AddASIO()
AddStringMembers -PARAMS @('asio') -WriteOnly
}
}
@@ -133,14 +107,12 @@ class BusDevice : Device {
return 'Bus[' + $this.index + '].Device'
}
hidden [void] AddASIO () {
Add-Member -InputObject $this -MemberType ScriptProperty -Name 'asio' `
-Value {
return Write-Warning ("ERROR: $($this.identifier()).asio is write only")
} -SecondValue {
param([string]$arg)
return $this.Setter('asio', $arg)
} -Force
[int] EnumCount () {
return $this.remote.GetOutputCount()
}
[PSObject] EnumDevice ([int]$eIndex) {
return $this.remote.GetOutputDevice($eIndex)
}
}

View File

@@ -1,52 +0,0 @@
class Device : IRemote {
Device ([int]$index, [Object]$remote) : base ($index, $remote) {
}
hidden $_name = $($this | Add-Member ScriptProperty 'name' `
{
$this.Getter_String('name')
} `
{
return Write-Warning ("ERROR: $($this.identifier()).name is read only")
}
)
hidden $_sr = $($this | Add-Member ScriptProperty 'sr' `
{
[int]$this.Getter('sr')
} `
{
return Write-Warning ("ERROR: $($this.identifier()).sr is read only")
}
)
hidden $_wdm = $($this | Add-Member ScriptProperty 'wdm' `
{
return Write-Warning ("ERROR: $($this.identifier()).wdm is write only")
} `
{
param([string]$arg)
return $this.Setter('wdm', $arg)
}
)
hidden $_ks = $($this | Add-Member ScriptProperty 'ks' `
{
return Write-Warning ("ERROR: $($this.identifier()).ks is write only")
} `
{
param([string]$arg)
return $this.Setter('ks', $arg)
}
)
hidden $_mme = $($this | Add-Member ScriptProperty 'mme' `
{
return Write-Warning ("ERROR: $($this.identifier()).mme is write only")
} `
{
param([string]$arg)
return $this.Setter('mme', $arg)
}
)
}

View File

@@ -1,60 +0,0 @@
class Eq : IRemote {
[System.Collections.ArrayList]$channel
[string]$kindOfEq
Eq ([int]$index, [Object]$remote, [string]$kindOfEq) : base ($index, $remote) {
$this.kindOfEq = $kindOfEq
AddBoolMembers -PARAMS @('on', 'ab')
$this.channel = @()
for ($ch = 0; $ch -lt $remote.kind.eq_ch[$this.kindOfEq]; $ch++) {
$this.channel.Add([EqChannel]::new($ch, $remote, $this.identifier()))
}
}
[void] Load ([string]$filename) {
$param = 'Command.Load{0}Eq[{1}]' -f $this.kindOfEq, $this.index
$this.remote.Setter($param, $filename)
}
[void] Save ([string]$filename) {
$param = 'Command.Save{0}Eq[{1}]' -f $this.kindOfEq, $this.index
$this.remote.Setter($param, $filename)
}
}
class EqChannel : IRemote {
[System.Collections.ArrayList]$cell
[string]$eqId
EqChannel ([int]$index, [Object]$remote, [string]$eqId) : base ($index, $remote) {
$this.eqId = $eqId
$this.cell = @()
$cellCount = $this.remote.kind.cells
for ($c = 0; $c -lt $cellCount; $c++) {
$this.cell.Add([EqCell]::new($c, $remote, $this.identifier()))
}
}
[string] identifier () {
return '{0}.Channel[{1}]' -f $this.eqId, $this.index
}
}
class EqCell : IRemote {
[string]$channelId
EqCell ([int]$index, [Object]$remote, [string]$channelId) : base ($index, $remote) {
$this.channelId = $channelId
AddBoolMembers -PARAMS @('on')
AddIntMembers -PARAMS @('type')
AddFloatMembers -PARAMS @('f', 'gain', 'q')
}
[string] identifier () {
return '{0}.Cell[{1}]' -f $this.channelId, $this.index
}
}

222
lib/io.ps1 Normal file
View File

@@ -0,0 +1,222 @@
class IOControl : IRemote {
IOControl ([int]$index, [Object]$remote) : base ($index, $remote) {
AddBoolMembers -PARAMS @('mute')
AddFloatMembers -PARAMS @('gain')
AddStringMembers -PARAMS @('label')
}
[void] FadeTo ([single]$target, [int]$time) {
$this.Setter('FadeTo', "($target, $time)")
}
[void] FadeBy ([single]$target, [int]$time) {
$this.Setter('FadeBy', "($target, $time)")
}
}
class IOLevels : IRemote {
IOLevels ([int]$index, [Object]$remote) : base ($index, $remote) {
}
hidden [single] Convert([single]$val) {
if ($val -gt 0) {
return [math]::Round(20 * [math]::Log10($val), 1)
}
else {
return -200.0
}
}
[System.Collections.ArrayList] Getter([int]$mode) {
[System.Collections.ArrayList]$vals = @()
$this.init..$($this.init + $this.offset - 1) | ForEach-Object {
$vals.Add($this.Convert($(Get_Level -MODE $mode -INDEX $_)))
}
return $vals
}
}
class IOEq : IRemote {
[System.Collections.ArrayList]$channel
[string]$kindOfEq
IOEq ([int]$index, [Object]$remote, [string]$kindOfEq) : base ($index, $remote) {
$this.kindOfEq = $kindOfEq
AddBoolMembers -PARAMS @('on', 'ab')
$this.channel = @()
for ($ch = 0; $ch -lt $remote.kind.eq_ch[$this.kindOfEq]; $ch++) {
$this.channel.Add([EqChannel]::new($ch, $this))
}
}
[void] Load ([string]$filename) {
$param = 'Command.Load{0}Eq[{1}]' -f $this.kindOfEq, $this.index
$this.remote.Setter($param, $filename)
}
[void] Save ([string]$filename) {
$param = 'Command.Save{0}Eq[{1}]' -f $this.kindOfEq, $this.index
$this.remote.Setter($param, $filename)
}
}
class EqChannel : IRemote {
[System.Collections.ArrayList]$cell
[Object]$eq
EqChannel ([int]$index, [Object]$eq) : base ($index, $eq.remote) {
$this.eq = $eq
if ($eq.kindOfEq -eq 'Bus') { AddFloatMembers -PARAMS @('trim', 'delay') }
$this.cell = @()
$cellCount = $this.remote.kind.cells
for ($c = 0; $c -lt $cellCount; $c++) {
$this.cell.Add([EqCell]::new($c, $this))
}
}
[string] identifier () {
return '{0}.Channel[{1}]' -f $this.eq.identifier(), $this.index
}
}
class EqCell : IRemote {
[Object]$channel
EqCell ([int]$index, [Object]$channel) : base ($index, $channel.remote) {
$this.channel = $channel
AddBoolMembers -PARAMS @('on')
AddIntMembers -PARAMS @('type')
AddFloatMembers -PARAMS @('f', 'gain', 'q')
}
[string] identifier () {
return '{0}.Cell[{1}]' -f $this.channel.identifier(), $this.index
}
}
class IODevice : IRemote {
[string]$kindOfDevice
[Hashtable]$drivers
IODevice ([int]$index, [Object]$remote, [string]$kindOfDevice) : base ($index, $remote) {
$this.kindOfDevice = $kindOfDevice
AddStringMembers -WriteOnly -PARAMS @('wdm', 'ks', 'mme')
AddStringMembers -ReadOnly -PARAMS @('name')
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")
}
)
}

View File

@@ -9,7 +9,7 @@ $KindMap = @{
'asio_out' = 8
'composite' = 0
'insert' = 0
'vban' = @{ 'in' = 4; 'out' = 4; 'midi' = 1; 'text' = 1 }
'vban' = @{ 'in' = 4; 'out' = 4; 'midi' = 1; 'text' = 1; 'video' = 1 }
'eq_ch' = @{ 'strip' = 0; 'bus' = 0 }
'cells' = 0
'gainlayer' = 0
@@ -24,7 +24,7 @@ $KindMap = @{
'asio_out' = 8
'composite' = 8
'insert' = 22
'vban' = @{ 'in' = 8; 'out' = 8; 'midi' = 1; 'text' = 1 }
'vban' = @{ 'in' = 8; 'out' = 8; 'midi' = 1; 'text' = 1; 'video' = 1 }
'eq_ch' = @{ 'strip' = 0; 'bus' = 8 }
'cells' = 6
'gainlayer' = 0
@@ -39,7 +39,7 @@ $KindMap = @{
'asio_out' = 8
'composite' = 8
'insert' = 34
'vban' = @{ 'in' = 8; 'out' = 8; 'midi' = 1; 'text' = 1 }
'vban' = @{ 'in' = 8; 'out' = 8; 'midi' = 1; 'text' = 1; 'video' = 1 }
'eq_ch' = @{ 'strip' = 2; 'bus' = 8 }
'cells' = 6
'gainlayer' = 8

View File

@@ -1,65 +1,57 @@
function AddBoolMembers () {
param(
[String[]]$PARAMS
[String[]]$PARAMS, [Switch]$readOnly, [Switch]$writeOnly
)
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures['Getter'] = "[bool]`$this.Getter('{0}')" -f $param
# Define setter
$Signatures['Setter'] = "param ( [bool]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
Addmember -ReadOnly:$readOnly -WriteOnly:$writeOnly
}
}
function AddFloatMembers () {
param(
[String[]]$PARAMS,
[String[]]$PARAMS, [Switch]$readOnly, [Switch]$writeOnly,
[int]$decimals = 2
)
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures['Getter'] = "[math]::Round(`$this.Getter('{0}'), {1})" -f $param, $decimals
# Define setter
$Signatures['Setter'] = "param ( [Single]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
Addmember -ReadOnly:$readOnly -WriteOnly:$writeOnly
}
}
function AddIntMembers () {
param(
[String[]]$PARAMS
[String[]]$PARAMS, [Switch]$readOnly, [Switch]$writeOnly
)
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures['Getter'] = "[Int]`$this.Getter('{0}')" -f $param
# Define setter
$Signatures['Setter'] = "param ( [Int]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
Addmember -ReadOnly:$readOnly -WriteOnly:$writeOnly
}
}
function AddStringMembers () {
param(
[String[]]$PARAMS
[String[]]$PARAMS, [Switch]$readOnly, [Switch]$writeOnly
)
[hashtable]$Signatures = @{}
foreach ($param in $PARAMS) {
# Define getter
$Signatures['Getter'] = "[String]`$this.Getter_String('{0}')" -f $param
# Define setter
$Signatures['Setter'] = "param ( [String]`$arg )`n`$this.Setter('{0}', `$arg)" `
-f $param
Addmember
Addmember -ReadOnly:$readOnly -WriteOnly:$writeOnly
}
}
@@ -97,6 +89,24 @@ function AddChannelMembers () {
}
function Addmember {
param(
[Switch]$readOnly, [Switch]$writeOnly
)
if ($readOnly -and $writeOnly) {
throw "AddMember: cannot be both readOnly and writeOnly for '$param'"
}
# Override signatures based on mode
if ($readOnly) {
$Signatures['Setter'] = "return Write-Warning (`"ERROR: `$(`$this.identifier()).{0} is read only`")" `
-f $param
}
elseif ($writeOnly) {
$Signatures['Getter'] = "return Write-Warning (`"ERROR: `$(`$this.identifier()).{0} is write only`")" `
-f $param
}
$AddMemberParams = @{
Name = $param
MemberType = 'ScriptProperty'

View File

@@ -25,6 +25,7 @@ class Recorder : IRemote {
AddActionMembers -PARAMS @('replay', 'ff', 'rew')
AddFloatMembers -PARAMS @('gain')
AddIntMembers -PARAMS @('prerectime')
AddStringMembers -PARAMS @('prefix') -WriteOnly
AddChannelMembers
}
@@ -117,16 +118,6 @@ class Recorder : IRemote {
}
)
hidden $_prefix = $($this | Add-Member ScriptProperty 'prefix' `
{
return Write-Warning ("ERROR: $($this.identifier()).prefix is write only")
} `
{
param([string]$arg)
$this._prefix = $this.Setter('prefix', $arg)
}
)
hidden $_filetype = $($this | Add-Member ScriptProperty 'filetype' `
{
return Write-Warning ("ERROR: $($this.identifier()).filetype is write only")

View File

@@ -1,11 +1,10 @@
class Strip : IRemote {
class Strip : IOControl {
[System.Collections.ArrayList]$gainlayer
[Object]$levels
Strip ([int]$index, [Object]$remote) : base ($index, $remote) {
AddBoolMembers -PARAMS @('solo', 'mute')
AddFloatMembers -PARAMS @('gain', 'limit', 'pan_x', 'pan_y')
AddStringMembers -PARAMS @('label')
AddBoolMembers -PARAMS @('solo')
AddFloatMembers -PARAMS @('limit', 'pan_x', 'pan_y')
AddChannelMembers
@@ -20,17 +19,9 @@ class Strip : IRemote {
[string] identifier () {
return 'Strip[' + $this.index + ']'
}
[void] FadeTo ([single]$target, [int]$time) {
$this.Setter('FadeTo', "($target, $time)")
}
[void] FadeBy ([single]$target, [int]$time) {
$this.Setter('FadeBy', "($target, $time)")
}
}
class StripLevels : IRemote {
class StripLevels : IOLevels {
[int]$init
[int]$offset
@@ -46,23 +37,6 @@ class StripLevels : IRemote {
}
}
hidden [single] Convert([single]$val) {
if ($val -gt 0) {
return [math]::Round(20 * [math]::Log10($val), 1)
}
else {
return -200.0
}
}
[System.Collections.ArrayList] Getter([int]$mode) {
[System.Collections.ArrayList]$vals = @()
$this.init..$($this.init + $this.offset - 1) | ForEach-Object {
$vals.Add($this.Convert($(Get_Level -MODE $mode -INDEX $_)))
}
return $vals
}
[System.Collections.ArrayList] PreFader() {
return $this.Getter(0)
}
@@ -101,7 +75,22 @@ class PhysicalStrip : Strip {
}
}
class StripComp : IRemote {
class StripKnob : IRemote {
StripKnob ([int]$index, [Object]$remote) : base ($index, $remote) {
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
{
[math]::Round($this.Getter(''), 2)
} `
{
param([single]$arg)
return $this.Setter('', $arg)
}
)
}
class StripComp : StripKnob {
StripComp ([int]$index, [Object]$remote) : base ($index, $remote) {
AddFloatMembers -PARAMS @('gainin', 'ratio', 'threshold', 'attack', 'release', 'knee', 'gainout')
AddBoolMembers -PARAMS @('makeup')
@@ -110,19 +99,9 @@ class StripComp : IRemote {
[string] identifier () {
return 'Strip[' + $this.index + '].Comp'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
{
[math]::Round($this.Getter(''), 2)
} `
{
param([single]$arg)
return $this.Setter('', $arg)
}
)
}
class StripGate : IRemote {
class StripGate : StripKnob {
StripGate ([int]$index, [Object]$remote) : base ($index, $remote) {
AddFloatMembers -PARAMS @('threshold', 'damping', 'bpsidechain', 'attack', 'hold', 'release')
}
@@ -130,19 +109,9 @@ class StripGate : IRemote {
[string] identifier () {
return 'Strip[' + $this.index + '].Gate'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
{
[math]::Round($this.Getter(''), 2)
} `
{
param([single]$arg)
return $this.Setter('', $arg)
}
)
}
class StripDenoiser : IRemote {
class StripDenoiser : StripKnob {
StripDenoiser ([int]$index, [Object]$remote) : base ($index, $remote) {
AddFloatMembers -PARAMS @('threshold')
}
@@ -150,16 +119,6 @@ class StripDenoiser : IRemote {
[string] identifier () {
return 'Strip[' + $this.index + '].Denoiser'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
{
[math]::Round($this.Getter(''), 2)
} `
{
param([single]$arg)
return $this.Setter('', $arg)
}
)
}
class StripPitch : IRemote {
@@ -177,26 +136,16 @@ class StripPitch : IRemote {
}
}
class StripAudibility : IRemote {
class StripAudibility : StripKnob {
StripAudibility ([int]$index, [Object]$remote) : base ($index, $remote) {
}
[string] identifier () {
return 'Strip[' + $this.index + '].Audibility'
}
hidden $_knob = $($this | Add-Member ScriptProperty 'knob' `
{
[math]::Round($this.Getter(''), 2)
} `
{
param([single]$arg)
return $this.Setter('', $arg)
}
)
}
class StripEq : Eq {
class StripEq : IOEq {
StripEq ([int]$index, [Object]$remote) : base ($index, $remote, 'Strip') {
}
@@ -205,13 +154,21 @@ class StripEq : Eq {
}
}
class StripDevice : Device {
StripDevice ([int]$index, [Object]$remote) : base ($index, $remote) {
class StripDevice : IODevice {
StripDevice ([int]$index, [Object]$remote) : base ($index, $remote, 'Input') {
}
[string] identifier () {
return 'Strip[' + $this.index + '].Device'
}
[int] EnumCount () {
return $this.remote.GetInputCount()
}
[PSObject] EnumDevice ([int]$eIndex) {
return $this.remote.GetInputDevice($eIndex)
}
}
class VirtualStrip : Strip {

View File

@@ -3,18 +3,14 @@ class Vban : IRemote {
Vban ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote) {
$this.direction = $direction
AddBoolMembers -PARAMS @('on')
AddStringMembers -PARAMS @('name', 'ip')
}
[string] identifier () {
return 'vban.' + $this.direction + 'stream[' + $this.index + ']'
}
}
class VbanAudio : Vban {
VbanAudio ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
AddBoolMembers -PARAMS @('on')
AddStringMembers -PARAMS @('name', 'ip')
}
hidden $_port = $($this | Add-Member ScriptProperty 'port' `
{
@@ -30,43 +26,33 @@ class VbanAudio : Vban {
}
}
)
}
hidden $_sr = $($this | Add-Member ScriptProperty 'sr' `
{
[int]$this.Getter('sr')
} `
{
param([int]$arg)
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)) {
$this._sr = $this.Setter('sr', $arg)
}
else {
Write-Warning ('Expected one of', $opts)
}
}
}
)
class VbanAudio : Vban {
VbanAudio ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
AddIntMembers -PARAMS @('quality', 'route')
}
}
hidden $_channel = $($this | Add-Member ScriptProperty 'channel' `
{
[int]$this.Getter('channel')
} `
{
param([int]$arg)
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)
}
else {
Write-Warning ('Expected value from 1 to 8')
}
}
}
)
class VbanMidi : Vban {
VbanMidi ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
}
}
class VbanText : Vban {
VbanText ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
}
}
class VbanVideo : Vban {
VbanVideo ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
}
}
class VbanInAudio : VbanAudio {
VbanInAudio ([int]$index, [Object]$remote) : base ($index, $remote, 'in') {
AddIntMembers -ReadOnly -PARAMS @('sr', 'channel')
}
hidden $_bit = $($this | Add-Member ScriptProperty 'bit' `
{
@@ -74,125 +60,9 @@ class VbanAudio : Vban {
return $val
} `
{
param([int]$arg)
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 }
$this._bit = $this.Setter('bit', $val)
}
else {
Write-Warning ('Expected value 16 or 24')
}
}
Write-Warning ("ERROR: $($this.identifier()).bit is read only")
}
)
hidden $_quality = $($this | Add-Member ScriptProperty 'quality' `
{
[int]$this.Getter('quality')
} `
{
param([int]$arg)
if ($arg -ge 0 -and $arg -le 4) {
$this._quality = $this.Setter('quality', $arg)
}
else {
Write-Warning ('Expected value from 0 to 4')
}
}
)
hidden $_route = $($this | Add-Member ScriptProperty 'route' `
{
[int]$this.Getter('route')
} `
{
param([int]$arg)
$rt = $this.remote.kind['p_' + $this.direction] + $this.remote.kind['v_' + $this.direction] - 1
if ($arg -ge 0 -and $arg -le $rt) {
$this._route = $this.Setter('route', $arg)
}
else {
Write-Warning ("Expected value from 0 to $rt")
}
}
)
}
class VbanMidi : Vban {
VbanMidi ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
}
hidden $_on = $($this | Add-Member ScriptProperty 'on' `
{
return Write-Warning ("ERROR: $($this.identifier()).on is write only")
} `
{
param([bool]$arg)
$this._on = $this.Setter('on', $arg)
}
)
hidden $_name = $($this | Add-Member ScriptProperty 'name' `
{
return Write-Warning ("ERROR: $($this.identifier()).name is write only")
} `
{
param([string]$arg)
$this._name = $this.Setter('name', $arg)
}
)
hidden $_ip = $($this | Add-Member ScriptProperty 'ip' `
{
return Write-Warning ("ERROR: $($this.identifier()).ip is write only")
} `
{
param([string]$arg)
$this._ip = $this.Setter('ip', $arg)
}
)
}
class VbanText : Vban {
VbanText ([int]$index, [Object]$remote, [string]$direction) : base ($index, $remote, $direction) {
}
hidden $_on = $($this | Add-Member ScriptProperty 'on' `
{
return Write-Warning ("ERROR: $($this.identifier()).on is write only")
} `
{
param([bool]$arg)
$this._on = $this.Setter('on', $arg)
}
)
hidden $_name = $($this | Add-Member ScriptProperty 'name' `
{
return Write-Warning ("ERROR: $($this.identifier()).name is write only")
} `
{
param([string]$arg)
$this._name = $this.Setter('name', $arg)
}
)
hidden $_ip = $($this | Add-Member ScriptProperty 'ip' `
{
return Write-Warning ("ERROR: $($this.identifier()).ip is write only")
} `
{
param([string]$arg)
$this._ip = $this.Setter('ip', $arg)
}
)
}
class VbanInAudio : VbanAudio {
VbanInAudio ([int]$index, [Object]$remote) : base ($index, $remote, 'in') {
}
}
class VbanInMidi : VbanMidi {
@@ -207,12 +77,104 @@ class VbanInText : VbanText {
class VbanOutAudio : VbanAudio {
VbanOutAudio ([int]$index, [Object]$remote) : base ($index, $remote, 'out') {
AddIntMembers -PARAMS @('channel')
}
hidden $_sr = $($this | Add-Member ScriptProperty 'sr' `
{
[int]$this.Getter('sr')
} `
{
param([int]$arg)
$opts = @(11025, 16000, 22050, 24000, 32000, 44100, 48000, 64000, 88200, 96000)
if ($opts.Contains($arg)) {
$this._sr = $this.Setter('sr', $arg)
}
else {
Write-Warning ('Expected one of', $opts)
}
}
)
hidden $_bit = $($this | Add-Member ScriptProperty 'bit' `
{
$val = if ($this.Getter('bit') -eq 1) { 16 } else { 24 }
return $val
} `
{
param([int]$arg)
if (@(16, 24).Contains($arg)) {
$val = if ($arg -eq 16) { 1 } else { 2 }
$this._bit = $this.Setter('bit', $val)
}
else {
Write-Warning ('Expected value 16 or 24')
}
}
)
}
class VbanOutMidi : VbanMidi {
VbanOutMidi ([int]$index, [Object]$remote) : base ($index, $remote, 'out') {
}
hidden $_route = $($this | Add-Member ScriptProperty 'route' `
{
[string]$val = ''
switch ($this.Getter('route')) {
0 { $val = 'none' }
1 { $val = 'midi_in' }
2 { $val = 'aux_in' }
4 { $val = 'vban_in' }
7 { $val = 'all_in' }
8 { $val = 'midi_out' }
}
return $val
} `
{
param([string]$arg)
[int]$val = 0
switch ($arg) {
'none' { $val = 0 }
'midi_in' { $val = 1 }
'aux_in' { $val = 2 }
'vban_in' { $val = 4 }
'all_in' { $val = 7 }
'midi_out' { $val = 8 }
default { Write-Warning ("route got: $arg, expected one of 'none', 'midi_in', 'aux_in', 'vban_in', 'all_in', 'midi_out'") }
}
$this._route = $this.Setter('route', $val)
}
)
}
class VbanOutVideo : VbanVideo {
VbanOutVideo ([int]$index, [Object]$remote) : base ($index, $remote, 'out') {
AddIntMembers -PARAMS @('vfps', 'vquality')
AddIntMembers -WriteOnly -PARAMS @('route')
AddBoolMembers -PARAMS @('vcursor')
}
hidden $_vformat = $($this | Add-Member ScriptProperty 'vformat' `
{
[string]$val = ''
switch ($this.Getter('vformat')) {
1 { $val = 'png' }
2 { $val = 'jpg' }
}
return $val
} `
{
param([string]$arg)
[int]$val = 0
switch ($arg) {
'png' { $val = 1 }
'jpg' { $val = 2 }
default { Write-Warning ("vformat got: $arg, expected one of 'png', 'jpg'") }
}
$this._vformat = $this.Setter('vformat', $val)
}
)
}
function Make_Vban ([Object]$remote) {
@@ -220,7 +182,7 @@ function Make_Vban ([Object]$remote) {
[System.Collections.ArrayList]$outstream = @()
$totalInstreams = $remote.kind.vban.in + $remote.kind.vban.midi + $remote.kind.vban.text
$totalOutstreams = $remote.kind.vban.out + $remote.kind.vban.midi
$totalOutstreams = $remote.kind.vban.out + $remote.kind.vban.midi + $remote.kind.vban.video
for ($i = 0; $i -lt $totalInstreams; $i++) {
if ($i -lt $remote.kind.vban.in) {
@@ -237,9 +199,12 @@ function Make_Vban ([Object]$remote) {
if ($i -lt $remote.kind.vban.out) {
[void]$outstream.Add([VbanOutAudio]::new($i, $remote))
}
else {
elseif ($i -lt ($remote.kind.vban.out + $remote.kind.vban.midi)) {
[void]$outstream.Add([VbanOutMidi]::new($i, $remote))
}
else {
[void]$outstream.Add([VbanOutVideo]::new($i, $remote))
}
}
$CustomObject = [pscustomobject]@{

View File

@@ -150,10 +150,10 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
$vmr.vban.enable | Should -Be $expected
}
Context 'Instream' -ForEach @(
Context 'Instream, audio, midi, text' -ForEach @(
@{ Index = $vban_inA }
# @{ Index = $vban_inM }
# @{ Index = $vban_inT }
@{ Index = $vban_inM }
@{ Index = $vban_inT }
) {
It "Should set vban.instream[$index].on" {
$vmr.vban.instream[$index].on = $value
@@ -161,15 +161,25 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
}
}
Context 'Outstream' -ForEach @(
Context 'Outstream, audio, midi, video' -ForEach @(
@{ Index = $vban_outA }
# @{ Index = $vban_outM }
@{ Index = $vban_outM }
@{ Index = $vban_outV }
) {
It "Should set vban.outstream[$index].on" {
$vmr.vban.outstream[$index].on = $value
$vmr.vban.outstream[$index].on | Should -Be $expected
}
}
Context 'Outstream, video only' -ForEach @(
@{ Index = $vban_outV }
) {
It "Should set vban.outstream[$index].vcursor" {
$vmr.vban.outstream[$index].vcursor = $value
$vmr.vban.outstream[$index].vcursor | Should -Be $expected
}
}
}
Context 'Recorder' -Skip:$ifBasic {
@@ -465,19 +475,33 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
Context 'EQ' -Skip:$ifBasic -ForEach @(
@{ Eq = $vmr.bus[$index].eq }
) {
It "Should set Bus[$index].EQ.Channel[$bus_ch].Cell[$cells].F" {
$eq.channel[$bus_ch].cell[$cells].f = $msHz
$eq.channel[$bus_ch].cell[$cells].f | Should -Be $msHz
}
Context "Channel[$bus_ch]" {
It "Should set Bus[$index].EQ.Channel[$bus_ch].Trim" {
$eq.channel[$bus_ch].trim = $slide
$eq.channel[$bus_ch].trim | Should -Be $slide
}
It "Should set Bus[$index].EQ.Channel[$bus_ch].Cell[$cells].Gain" {
$eq.channel[$bus_ch].cell[$cells].gain = $slide
$eq.channel[$bus_ch].cell[$cells].gain | Should -Be $slide
}
It "Should set Bus[$index].EQ.Channel[$bus_ch].Delay" {
$eq.channel[$bus_ch].delay = $msHz
$eq.channel[$bus_ch].delay | Should -Be $msHz
}
Context "Cell[$cells]" {
It "Should set Bus[$index].EQ.Channel[$bus_ch].Cell[$cells].F" {
$eq.channel[$bus_ch].cell[$cells].f = $msHz
$eq.channel[$bus_ch].cell[$cells].f | Should -Be $msHz
}
It "Should set Bus[$index].EQ.Channel[$bus_ch].Cell[$cells].Q" {
$eq.channel[$bus_ch].cell[$cells].q = $knob
$eq.channel[$bus_ch].cell[$cells].q | Should -Be $knob
It "Should set Bus[$index].EQ.Channel[$bus_ch].Cell[$cells].Gain" {
$eq.channel[$bus_ch].cell[$cells].gain = $slide
$eq.channel[$bus_ch].cell[$cells].gain | Should -Be $slide
}
It "Should set Bus[$index].EQ.Channel[$bus_ch].Cell[$cells].Q" {
$eq.channel[$bus_ch].cell[$cells].q = $knob
$eq.channel[$bus_ch].cell[$cells].q | Should -Be $knob
}
}
}
}
}
@@ -582,8 +606,10 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
$vmr.vban.port | Should -Be $expected
}
Context 'Instream' -ForEach @(
Context 'Instream, audio, midi, text' -ForEach @(
@{ Index = $vban_inA }
@{ Index = $vban_inM }
@{ Index = $vban_inT }
) {
It "Should set vban.instream[$index].port" -ForEach @(
@{ Value = 1024; Expected = 1024 }
@@ -594,16 +620,21 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
Start-Sleep -Milliseconds 2000
$vmr.vban.instream[$index].port | Should -Be $expected
}
}
It "Should set vban.instream[$index].sr" {
Context 'Instream, audio only' -ForEach @(
@{ Index = $vban_inA }
) {
It "Should get vban.instream[$index].sr" {
$vmr.vban.instream[$index].sr | Should -BeOfType [int]
}
It "Should set vban.instream[$index].channel" {
It "Should get vban.instream[$index].channel" {
$vmr.vban.instream[$index].channel | Should -BeOfType [int]
}
It "Should set vban.instream[$index].bit" {
It "Should get vban.instream[$index].bit" {
$vmr.vban.instream[$index].bit | Should -BeOfType [int]
}
@@ -625,8 +656,10 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
}
}
Context 'Outstream' -ForEach @(
Context 'Outstream, audio, midi, video' -ForEach @(
@{ Index = $vban_outA }
@{ Index = $vban_outM }
@{ Index = $vban_outV }
) {
It "Should set vban.outstream[$index].port" -ForEach @(
@{ Value = 1024; Expected = 1024 }
@@ -637,6 +670,11 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
Start-Sleep -Milliseconds 2000
$vmr.vban.outstream[$index].port | Should -Be $expected
}
}
Context 'Outstream, audio only' -ForEach @(
@{ Index = $vban_outA }
) {
It "Should set vban.outstream[$index].sr" -ForEach @(
@{ Value = 44100; Expected = 44100 }
@@ -679,6 +717,34 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
$vmr.vban.outstream[$index].route | Should -Be $expected
}
}
Context 'Outstream, video only' -ForEach @(
@{ Index = $vban_outV }
) {
It "Should set vban.outstream[$index].vfps" -ForEach @(
@{ Value = 6; Expected = 6 }
@{ Value = 24; Expected = 24 }
) {
$vmr.vban.outstream[$index].vfps = $value
$vmr.vban.outstream[$index].vfps | Should -Be $expected
}
It "Should set vban.outstream[$index].vquality" -ForEach @(
@{ Value = 80; Expected = 80 }
@{ Value = 100; Expected = 100 }
) {
$vmr.vban.outstream[$index].vquality = $value
$vmr.vban.outstream[$index].vquality | Should -Be $expected
}
<# It "Should set vban.outstream[$index].route" -ForEach @(
@{ Value = 1; Expected = 1 }
@{ Value = 4; Expected = 4 }
) {
$vmr.vban.outstream[$index].route = $value
$vmr.vban.outstream[$index].route | Should -Be $expected
} #>
}
}
Context 'Patch' {
@@ -784,24 +850,47 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
@{ Index = $phys_in }
) {
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" {
$vmr.strip[$index].device.wdm = $value
BeforeEach {
$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
$vmr.strip[$index].device.name | Should -Be $value
$vmr.strip[$index].device.driver | Should -Be $expected
}
It "Should set Strip[$index].Device.ks" {
$vmr.strip[$index].device.ks = $value
It "Should set Strip[$index].Device" -ForEach @(
@{
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
$vmr.strip[$index].device.name | Should -Be $value
}
It "Should set Strip[$index].Device.mme" {
$vmr.strip[$index].device.mme = $value
Start-Sleep -Milliseconds 800
$vmr.strip[$index].device.name | Should -Be $value
$result = $vmr.strip[$index].device.Get()
$result.Driver | Should -Be $device.Driver
$result.Name | Should -Be $device.Name
$result.HardwareId | Should -Be $device.HardwareId
$result.IsOutput | Should -Be $device.IsOutput
}
}
@@ -917,24 +1006,47 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
@{ Index = $phys_out }
) {
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" {
$vmr.bus[$index].device.wdm = $value
BeforeEach {
$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
$vmr.bus[$index].device.name | Should -Be $value
$vmr.bus[$index].device.driver | Should -Be $expected
}
It "Should set Bus[$index].Device.ks" {
$vmr.bus[$index].device.ks = $value
It "Should set Bus[$index].Device" -ForEach @(
@{
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
$vmr.bus[$index].device.name | Should -Be $value
}
It "Should set Bus[$index].Device.mme" {
$vmr.bus[$index].device.mme = $value
Start-Sleep -Milliseconds 800
$vmr.bus[$index].device.name | Should -Be $value
$result = $vmr.bus[$index].device.Get()
$result.Driver | Should -Be $device.Driver
$result.Name | Should -Be $device.Name
$result.HardwareId | Should -Be $device.HardwareId
$result.IsOutput | Should -Be $device.IsOutput
}
}
}
@@ -943,33 +1055,56 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
@{ Index = $virt_out }
) {
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" {
$vmr.bus[$index].device.wdm = $value
BeforeEach {
$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
$vmr.bus[$index].device.name | Should -Be $value
$vmr.bus[$index].device.driver | Should -Be $expected
}
It "Should set Bus[$index].Device.ks" {
$vmr.bus[$index].device.ks = $value
It "Should set Bus[$index].Device" -ForEach @(
@{
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
$vmr.bus[$index].device.name | Should -Be $value
}
It "Should set Bus[$index].Device.mme" {
$vmr.bus[$index].device.mme = $value
Start-Sleep -Milliseconds 800
$vmr.bus[$index].device.name | Should -Be $value
$result = $vmr.bus[$index].device.Get()
$result.Driver | Should -Be $device.Driver
$result.Name | Should -Be $device.Name
$result.HardwareId | Should -Be $device.HardwareId
$result.IsOutput | Should -Be $device.IsOutput
}
}
}
Describe 'Vban' {
Context 'Instream' -ForEach @(
Context 'Instream, audio, midi, text' -ForEach @(
@{ Index = $vban_inA }
# @{ Index = $vban_inM }
# @{ Index = $vban_inT }
@{ Index = $vban_inM }
@{ Index = $vban_inT }
) {
It "Should set vban.instream[$index].name" -ForEach @(
@{ Value = 'TestIn0'; Expected = 'TestIn0' }
@@ -987,9 +1122,10 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
}
}
Context 'Outstream' -ForEach @(
Context 'Outstream, audio, midi, video' -ForEach @(
@{ Index = $vban_outA }
# @{ Index = $vban_outM }
@{ Index = $vban_outM }
@{ Index = $vban_outV }
) {
It "Should set vban.outstream[$index].name" -ForEach @(
@{ Value = 'TestOut0'; Expected = 'TestOut0' }
@@ -1006,6 +1142,31 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
$vmr.vban.outstream[$index].ip | Should -Be $expected
}
}
Context 'Outstream, midi only' -ForEach @(
@{ Index = $vban_outM }
) {
It "Should set vban.outstream[$index].route" -ForEach @(
@{ Value = 'aux_in'; Expected = 'aux_in' }
@{ Value = 'all_in'; Expected = 'all_in' }
@{ Value = 'midi_out'; Expected = 'midi_out' }
) {
$vmr.vban.outstream[$index].route = $value
$vmr.vban.outstream[$index].route | Should -Be $expected
}
}
Context 'Outstream, video only' -ForEach @(
@{ Index = $vban_outV }
) {
It "Should set vban.outstream[$index].vformat" -ForEach @(
@{ Value = 'png'; Expected = 'png' }
@{ Value = 'jpg'; Expected = 'jpg' }
) {
$vmr.vban.outstream[$index].vformat = $value
$vmr.vban.outstream[$index].vformat | Should -Be $expected
}
}
}
Context 'Recorder' -Skip:$ifBasic {
@@ -1016,13 +1177,19 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
$vmr.recorder.prefix = $prefix
$vmr.recorder.filetype = $filetype
$start = Get-Date
$vmr.recorder.state = 'record'
Start-Sleep -Milliseconds 10
$stamp = '{0:yyyy-MM-dd} at {0:HH}h{0:mm}m{0:ss}s' -f (Get-Date)
$vmr.recorder.state | Should -Be 'record'
Start-Sleep -Milliseconds 2000
$tmp = [System.IO.Path]::Combine($recDir, ("{0} {1}.{2}" -f $prefix, $stamp, $filetype))
$tmp = Get-ChildItem -Path $recDir -Filter ("{0}*.{1}" -f $prefix, $filetype) -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt $start } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $tmp) {
throw "'$filetype' file with prefix '$prefix' was not found in '$recDir'."
}
$vmr.recorder.state = 'stop'
$vmr.recorder.eject()
@@ -1110,12 +1277,18 @@ Describe -Tag 'higher', -TestName 'All Higher Tests' {
}
BeforeEach {
$start = Get-Date
$vmr.recorder.record()
Start-Sleep -Milliseconds 10
$stamp = '{0:yyyy-MM-dd} at {0:HH}h{0:mm}m{0:ss}s' -f (Get-Date)
Start-Sleep -Milliseconds 2000
$tmp = [System.IO.Path]::Combine($recDir, ("{0} {1}.{2}" -f $prefix, $stamp, $filetype))
$tmp = Get-ChildItem -Path $recDir -Filter ("{0}*.{1}" -f $prefix, $filetype) -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt $start } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $tmp) {
throw "'$filetype' file with prefix '$prefix' was not found in '$recDir'."
}
$vmr.recorder.pause()
Start-Sleep -Milliseconds 500

View File

@@ -11,12 +11,18 @@ function Test-RecDir ([object]$vmr, [string]$recDir) {
try {
$start = Get-Date
$vmr.recorder.record()
Start-Sleep -Milliseconds 10
$stamp = '{0:yyyy-MM-dd} at {0:HH}h{0:mm}m{0:ss}s' -f (Get-Date)
Start-Sleep -Milliseconds 2000
$tmp = Join-Path $recDir ("{0} {1}.{2}" -f $prefix, $stamp, $filetype)
$tmp = Get-ChildItem -Path $recDir -Filter ("{0}*.{1}" -f $prefix, $filetype) -ErrorAction SilentlyContinue |
Where-Object { $_.LastWriteTime -gt $start } |
Sort-Object LastWriteTime -Descending |
Select-Object -First 1
if (-not $tmp) {
throw "'$filetype' file with prefix '$prefix' was not found in '$recDir'."
}
$vmr.recorder.stop()
$vmr.recorder.eject()
@@ -53,6 +59,7 @@ function main() {
$vban_inT = $vmr.kind.vban.in + $vmr.kind.vban.midi + $vmr.kind.vban.text - 1
$vban_outA = $vmr.kind.vban.out - 1
$vban_outM = $vmr.kind.vban.out + $vmr.kind.vban.midi - 1
$vban_outV = $vmr.kind.vban.out + $vmr.kind.vban.midi + $vmr.kind.vban.video - 1
$insert = $vmr.kind.insert - 1
$composite = $vmr.kind.composite - 1
$strip_ch = $vmr.kind.eq_ch['strip'] - 1