[![PyPI version](https://badge.fury.io/py/vban-cmd.svg)](https://badge.fury.io/py/vban-cmd) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/onyx-and-iris/vban-cmd-python/blob/dev/LICENSE) [![Poetry](https://img.shields.io/endpoint?url=https://python-poetry.org/badge/v0.json)](https://python-poetry.org/) [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff) ![Tests Status](./tests/basic.svg?dummy=8484744) ![Tests Status](./tests/banana.svg?dummy=8484744) ![Tests Status](./tests/potato.svg?dummy=8484744) # VBAN CMD This python interface allows you to send Voicemeeter/Matrix commands over a network. It offers the same public API as [Voicemeeter Remote Python API](https://github.com/onyx-and-iris/voicemeeter-api-python). Only the VBAN SERVICE/TEXT subprotocols are supported, there is no support for AUDIO or MIDI in this package. For an outline of past/future changes refer to: [CHANGELOG](CHANGELOG.md) ## Tested against - Basic 1.1.2.2 - Banana 2.1.2.2 - Potato 3.1.2.2 ## Requirements - [Voicemeeter](https://voicemeeter.com/) - Python 3.10 or greater ## Installation ```console pip install vban-cmd ``` ## `Use` #### Connection Load VBAN connection info from toml config. A valid `vban.toml` might look like this: ```toml [connection] host = "localhost" port = 6980 streamname = "Command1" ``` It should be placed in \ / "Documents" / "Voicemeeter" / "configs" Alternatively you may pass `host`, `port`, `streamname` as keyword arguments. #### `__main__.py` Simplest use case, use a context manager to request a VbanCmd class of a kind. Login and logout are handled for you in this scenario. ```python import vban_cmd class ManyThings: def __init__(self, vban): self.vban = vban def things(self): self.vban.strip[0].label = 'podmic' self.vban.strip[0].mute = True print( f'strip 0 ({self.vban.strip[0].label}) mute has been set to {self.vban.strip[0].mute}' ) def other_things(self): self.vban.bus[3].gain = -6.3 self.vban.bus[4].eq = True info = ( f'bus 3 gain has been set to {self.vban.bus[3].gain}', f'bus 4 eq has been set to {self.vban.bus[4].eq}', ) print('\n'.join(info)) def main(): KIND_ID = 'banana' with vban_cmd.api( KIND_ID, host='localhost', port=6980, streamname='Command1' ) as vban: do = ManyThings(vban) do.things() do.other_things() # set many parameters at once vban.apply( { 'strip-2': {'A1': True, 'B1': True, 'gain': -6.0}, 'bus-2': {'mute': True}, 'vban-in-0': {'on': True}, } ) if __name__ == '__main__': main() ``` Otherwise you must remember to call `vban.login()`, `vban.logout()` at the start/end of your code. ## `KIND_ID` Pass the kind of Voicemeeter as an argument. KIND_ID may be: - `basic` - `banana` - `potato` A fourth kind `matrix` has been added, if you pass it as a KIND_ID you are expected to use the [{VbanCmd}.sendtext()](https://github.com/onyx-and-iris/vban-cmd-python?tab=readme-ov-file#vbansendtextscript) method for sending text requests. ## `Available commands` ### Strip The following properties are available. - `mono`: boolean - `solo`: boolean - `mute`: boolean - `label`: string - `gain`: float, -60 to 12 - `A1 - A5`, `B1 - B3`: boolean - `limit`: int, from -40 to 12 example: ```python vban.strip[3].gain = 3.7 print(vban.strip[0].label) ``` The following methods are available. - `appgain(name, value)`: string, float, from 0.0 to 1.0 Set the gain in db by value for the app matching name. - `appmute(name, value)`: string, bool Set mute state as value for the app matching name. example: ```python vban.strip[5].appmute('Spotify', True) vban.strip[5].appgain('Spotify', 0.5) ``` ##### Strip.Comp The following properties are available. - `knob`: float, from 0.0 to 10.0 - `gainin`: float, from -24.0 to 24.0 - `ratio`: float, from 1.0 to 8.0 - `threshold`: float, from -40.0 to -3.0 - `attack`: float, from 0.0 to 200.0 - `release`: float, from 0.0 to 5000.0 - `knee`: float, from 0.0 to 1.0 - `gainout`: float, from -24.0 to 24.0 - `makeup`: boolean example: ```python print(vban.strip[4].comp.knob) ``` Strip Comp `knob` is defined for all versions, all other parameters potato only. ##### Strip.Gate The following properties are available. - `knob`: float, from 0.0 to 10.0 - `threshold`: float, from -60.0 to -10.0 - `damping`: float, from -60.0 to -10.0 - `bpsidechain`: int, from 100 to 4000 - `attack`: float, from 0.0 to 1000.0 - `hold`: float, from 0.0 to 5000.0 - `release`: float, from 0.0 to 5000.0 example: ```python vban.strip[2].gate.attack = 300.8 ``` Strip Gate `knob` is defined for all versions, all other parameters potato only. ##### Strip.Denoiser The following properties are available. - `knob`: float, from 0.0 to 10.0 strip.denoiser properties are defined as write only, potato version only. ##### Strip.EQ The following properties are available. - `on`: boolean - `ab`: boolean example: ```python vban.strip[0].eq.ab = True ``` ##### Strip.EQ.Channel.Cell The following properties are available. - `on`: boolean - `type`: int, from 0 up to 6 - `f`: float, from 20.0 up to 20_000.0 - `gain`: float, from -36.0 up to 18.0 - `q`: float, from 0.3 up to 100 example: ```python vban.strip[0].eq.channel[0].cell[2].on = True vban.strip[1].eq.channel[0].cell[2].f = 5000 ``` Strip EQ parameters are defined for PhysicalStrips, potato version only. Only channel[0] properties are readable over VBAN. ##### Gainlayers - `gain`: float, from -60.0 to 12.0 example: ```python vban.strip[3].gainlayer[3].gain = 3.7 ``` Gainlayers are defined for potato version only. ##### Levels The following properties are available. - `prefader` example: ```python print(vban.strip[3].levels.prefader) ``` Level properties will return -200.0 if no audio detected. ### Bus The following properties are available. - `mono`: boolean - `mute`: boolean - `label`: string - `gain`: float, -60 to 12 example: ```python print(vban.bus[0].label) ``` ##### Bus.EQ The following properties are available. - `on`: boolean - `ab`: boolean ```python vban.bus[4].eq.on = true ``` ##### Modes The following properties are available. - `normal`: boolean - `amix`: boolean - `bmix`: boolean - `composite`: boolean - `tvmix`: boolean - `upmix21`: boolean - `upmix41`: boolean - `upmix61`: boolean - `centeronly`: boolean - `lfeonly`: boolean - `rearonly`: boolean The following methods are available. - `get()`: Returns the current bus mode example: ```python vban.bus[4].mode.amix = True print(vban.bus[2].mode.get()) ``` ##### Levels The following properties are available. - `all` example: ```python print(vban.bus[0].levels.all) ``` `levels.all` will return -200.0 if no audio detected. ### Strip | Bus The following methods are available. - `fadeto(amount, time)`: float, int - `fadeby(amount, time)`: float, int Modify gain to or by the selected amount in db over a time interval in ms. example: ```python vban.strip[0].fadeto(-10.3, 1000) vban.bus[3].fadeby(-5.6, 500) ``` ### Recorder The following methods are available - `play()` - `stop()` - `pause()` - `record()` - `ff()` - `rew()` - `load(filepath)`: raw string - `goto(time_string)`: time string in format `hh:mm:ss` The following properties are available - `samplerate`: int, (22050, 24000, 32000, 44100, 48000, 88200, 96000, 176400, 192000) - `bitresolution`: int, (8, 16, 24, 32) - `channel`: int, from 1 to 8 - `kbps`: int, (32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320) - `gain`: float, from -60.0 to 12.0 example: ```python vban.recorder.play() vban.recorder.stop() # filepath as raw string vban.recorder.load(r'C:\music\mytune.mp3') # set the goto time to 1m 30s vban.recorder.goto('00:01:30') ``` ### Command Certain 'special' commands are defined by the API as performing actions rather than setting values. The following methods are available: - `show()` : Bring Voiceemeter GUI to the front - `shutdown()` : Shuts down the GUI - `restart()` : Restart the audio engine - `reset()`: Applies the `reset` config. (phys strip B1, virt strip A1, gains, comp, gate 0.0, mute, mono, solo, eq false) The following properties are write only and accept boolean values. - `showvbanchat`: boolean - `lock`: boolean example: ```python vban.command.restart() vban.command.showvbanchat = true ``` ### Multiple parameters - `apply` Set many strip/bus parameters at once, for example: ```python vban.apply( { 'strip-0': {'A1': True, 'B1': True, 'gain': -6.0}, 'bus-1': {'mute': True, 'mode': 'composite'}, 'bus-2': {'eq': {'on': True}}, 'vban-in-0': {'on': True}, } ) ``` Or for each class you may do: ```python vban.strip[0].apply({'mute': True, 'gain': 3.2, 'A1': True}) vban.vban.outstream[0].apply({'on': True, 'name': 'streamname', 'bit': 24}) ``` ## Config Files `vban.apply_config()` You may load config files in TOML format. Three example configs have been included with the package. Remember to save current settings before loading a user config. To load one you may do: ```python import vban_cmd with vban_cmd.api('banana') as vban: vban.apply_config('example') ``` will load a config file at configs/banana/example.toml for Voicemeeter Banana. Your configs may be located in one of the following paths: - \ / "configs" / kind_id - \ / ".config" / "vban-cmd" / kind_id - \ / "Documents" / "Voicemeeter" / "configs" / kind_id If a config with the same name is located in multiple locations, only the first one found is loaded into memory, in the above order. #### `config extends` You may also load a config that extends another config with overrides or additional parameters. You just need to define a key `extends` in the config TOML, that names the config to be extended. Three example 'extender' configs are included with the repo. You may load them with: ```python import vban_cmd with vban_cmd.api('banana') as vm: vm.apply_config('extender') ``` ## Events Level updates are considered high volume, by default they are NOT listened for. Use `subs` keyword arg to initialize event updates. example: ```python import vban_cmd opts = { 'host': '', 'streamname': 'Command1', 'port': 6980, } with vban_cmd.api('banana', ldirty=True, **opts) as vban: ... ``` #### `vban.subject` Use the Subject class to register an app as event observer. The following methods are available: - `add`: registers an app as an event observer - `remove`: deregisters an app as an event observer example: ```python # register an app to receive updates class App(): def __init__(self, vban): vban.subject.add(self) ... ``` #### `vban.event` Use the event class to toggle updates as necessary. The following properties are available: - `pdirty`: boolean - `ldirty`: boolean example: ```python vban.event.ldirty = True vban.event.pdirty = False ``` Or add, remove a list of events. The following methods are available: - `add()` - `remove()` - `get()` example: ```python vban.event.remove(['pdirty', 'ldirty']) # get a list of currently subscribed print(vban.event.get()) ``` ## VbanCmd class `vban_cmd.api(kind_id: str, **opts)` You may pass the following optional keyword arguments: - `host`: str='localhost', ip or hostname of remote machine - `port`: int=6980, vban udp port of remote machine. - `streamname`: str='Command1', name of the stream to connect to. - `bps`: int=256000, bps rate of the stream. - `channel`: int=0, channel on which to send the UDP requests. - `pdirty`: boolean=False, parameter updates - `ldirty`: boolean=False, level updates - `script_ratelimit`: float=0.05, default to 20 script requests per second. This affects vban.sendtext() specifically. - `timeout`: int=5, timeout for socket operations. - `disable_rt_listeners`: boolean=False, set `True` if you don't wish to receive RT packets. - You can still send Matrix string requests ending with `?` and receive a response. #### `vban.pdirty` True iff a parameter has been changed. #### `vban.ldirty` True iff a level value has been changed. #### `vban.sendtext(script)` Sends a script block as a string request, for example: ```python vban.sendtext('Strip[0].Mute=1;Bus[0].Mono=1') ``` You can even use it to send matrix commands: ```python vban.sendtext('Point(ASIO128.IN[1..4],ASIO128.OUT[1]).dBGain = -3.0') vban.sendtext('Command.Version = ?') ``` ## Errors - `errors.VBANCMDError`: Base VBANCMD Exception class. - `errors.VBANCMDConnectionError`: Exception raised when connection/timeout errors occur. ## Logging It's possible to see the messages sent by the interface's setters and getters, may be useful for debugging. example: ```python import vban_cmd logging.basicConfig(level=logging.DEBUG) opts = {'host': 'localhost', 'port': 6980, 'streamname': 'Command1'} with vban_cmd.api('banana', **opts) as vban: ... ``` ### Run tests Install [poetry](https://python-poetry.org/docs/#installation) and then: ```powershell poetry poe test-basic poetry poe test-banana poetry poe test-potato ``` ## Resources - [Voicemeeter VBAN TEXT](https://vb-audio.com/Voicemeeter/VBANProtocol_Specifications.pdf#page=19) - [Voicemeeter RT Packet Service](https://vb-audio.com/Voicemeeter/VBANProtocol_Specifications.pdf#page=27)