Skip to content

Instantly share code, notes, and snippets.

@onyx-and-iris
Last active September 16, 2022 13:31
Show Gist options
  • Save onyx-and-iris/7da2591a3f4edc8db784c0da5e56e2b3 to your computer and use it in GitHub Desktop.
Save onyx-and-iris/7da2591a3f4edc8db784c0da5e56e2b3 to your computer and use it in GitHub Desktop.
Send a Voicemeeter text request using VBAN (sendtext)
import socket
import time
from dataclasses import dataclass
from pathlib import Path
try:
import tomllib
except ModuleNotFoundError:
import tomli as tomllib
HEADER_SIZE = 4 + 1 + 1 + 1 + 1 + 16 + 4
@dataclass
class TextRequestHeader:
"""Header for a string request packet"""
name: str
bps_index: int
channel: int
vban: bytes = "VBAN".encode()
nbs: bytes = (0).to_bytes(1, "little")
bit: bytes = (0x10).to_bytes(1, "little")
framecounter: bytes = (0).to_bytes(4, "little")
@property
def sr(self):
return (0x40 + self.bps_index).to_bytes(1, "little")
@property
def nbc(self):
return (self.channel).to_bytes(1, "little")
@property
def streamname(self):
return self.name.encode() + bytes(16 - len(self.name))
@property
def header(self):
header = self.vban
header += self.sr
header += self.nbs
header += self.nbc
header += self.bit
header += self.streamname
header += self.framecounter
assert len(header) == HEADER_SIZE, f"Header expected {HEADER_SIZE} bytes"
return header
class SendText:
# fmt: off
BPS_OPTS = [
0, 110, 150, 300, 600, 1200, 2400, 4800, 9600, 14400, 19200, 31250,
38400, 57600, 115200, 128000, 230400, 250000, 256000, 460800, 921600,
1000000, 1500000, 2000000, 3000000,
]
# fmt: on
def __init__(self, **kwargs):
defaultkwargs = {
"ip": None,
"port": 6980,
"streamname": "Command1",
"bps": 0,
"channel": 0,
"delay": 0.02,
}
kwargs = defaultkwargs | kwargs
for attr, val in kwargs.items():
setattr(self, attr, val)
# no ip? assume it's in a config.toml file
if not self.ip:
conn = self._conn_from_toml()
for attr, val in conn.items():
setattr(self, attr, val)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.header = TextRequestHeader(
name=self.streamname,
bps_index=self.BPS_OPTS.index(self.bps),
channel=self.channel,
)
def __enter__(self):
return self
def _conn_from_toml(self):
filepath = Path.cwd() / "config.toml"
with open(filepath, "rb") as f:
conn = tomllib.load(f)
return conn["connection"]
def send(self, cmd):
"""Sends a Voicemeeter string request over a network"""
self.sock.sendto(
self.header.header + cmd.encode(),
(socket.gethostbyname(self.ip), self.port),
)
count = int.from_bytes(self.header.framecounter, "little") + 1
self.header.framecounter = count.to_bytes(4, "little")
time.sleep(self.delay)
def __exit__(self, exc_t, exc_v, exc_tr):
self.sock.close()
if __name__ == "__main__":
cmd_off = "Strip[0].mute=0;Strip[1].mute=0;Strip[2].mute=0"
cmd_on = "Strip[0].mute=1;Strip[1].mute=1;Strip[2].mute=1"
with SendText() as vban:
for _ in range(30):
vban.send(cmd_off)
vban.send(cmd_on)
@onyx-and-iris
Copy link
Author

onyx-and-iris commented Aug 8, 2022

You can set ip, port, streamname etc as keyword arguments, or load them from a config.toml file. This example assumes existence of config file.

If you prefer to code more complex interactions or get and store parameter values check:

https://github.com/onyx-and-iris/vban-cmd-python

example config.toml:

[connection]
ip = "stream.local"
port = 7000
streamname = "mystreampc"

config.toml should be placed next to your __main__.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment