upd the interface to read/write multiple private/public packets.

{VirtualStrip}.bass/mid/treble implemented reading from public packet NBS=1
This commit is contained in:
onyx-and-iris 2026-01-17 09:37:31 +00:00
parent 51394c0076
commit 96e9d6f4fd
7 changed files with 117 additions and 40 deletions

View File

@ -1,17 +1,11 @@
import time import time
from abc import abstractmethod from abc import abstractmethod
from enum import IntEnum
from typing import Union from typing import Union
from .enums import NBS, BusModes
from .iremote import IRemote from .iremote import IRemote
from .meta import bus_mode_prop, channel_bool_prop, channel_label_prop from .meta import bus_mode_prop, channel_bool_prop, channel_label_prop
BusModes = IntEnum(
'BusModes',
'normal amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly',
start=0,
)
class Bus(IRemote): class Bus(IRemote):
""" """
@ -31,7 +25,7 @@ class Bus(IRemote):
@property @property
def gain(self) -> float: def gain(self) -> float:
def fget(): def fget():
val = self.public_packet.busgain[self.index] val = self.public_packets[NBS.zero].busgain[self.index]
if 0 <= val <= 1200: if 0 <= val <= 1200:
return val * 0.01 return val * 0.01
return (((1 << 16) - 1) - val) * -0.01 return (((1 << 16) - 1) - val) * -0.01
@ -109,7 +103,7 @@ class BusLevel(IRemote):
) )
return tuple( return tuple(
fget(i) fget(i)
for i in self._remote._get_levels(self.public_packet)[1][ for i in self._remote._get_levels(self.public_packets[NBS.zero])[1][
self.range[0] : self.range[-1] self.range[0] : self.range[-1]
] ]
) )
@ -157,7 +151,12 @@ def _make_bus_mode_mixin():
def get(self): def get(self):
states = [ states = [
(int.from_bytes(self.public_packet.busstate[self.index], 'little') & val) (
int.from_bytes(
self.public_packets[NBS.zero].busstate[self.index], 'little'
)
& val
)
>> 4 >> 4
for val in self._modes.modevals for val in self._modes.modevals
] ]

15
vban_cmd/enums.py Normal file
View File

@ -0,0 +1,15 @@
from enum import IntEnum
class NBS(IntEnum):
zero = 0
one = 1
BusModes = IntEnum(
'BusModes',
'normal amix bmix repeat composite tvmix upmix21 upmix41 upmix61 centeronly lfeonly rearonly',
start=0,
)
EQGains = IntEnum('EQGains', 'bass mid treble', start=0)

View File

@ -116,9 +116,9 @@ class IRemote(metaclass=ABCMeta):
pass pass
@property @property
def public_packet(self): def public_packets(self):
"""Returns an RT data packet.""" """Returns an RT data packet."""
return self._remote.public_packet return self._remote.public_packets
def apply(self, data): def apply(self, data):
"""Sets all parameters of a dict for the channel.""" """Sets all parameters of a dict for the channel."""

View File

@ -1,5 +1,6 @@
from functools import partial from functools import partial
from .enums import NBS
from .util import cache_bool, cache_string from .util import cache_bool, cache_string
@ -13,7 +14,7 @@ def channel_bool_prop(param):
return ( return (
not int.from_bytes( not int.from_bytes(
getattr( getattr(
self.public_packet, self.public_packets[NBS.zero],
f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}state', f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}state',
)[self.index], )[self.index],
'little', 'little',
@ -34,7 +35,7 @@ def channel_label_prop():
@partial(cache_string, param='label') @partial(cache_string, param='label')
def fget(self) -> str: def fget(self) -> str:
return getattr( return getattr(
self.public_packet, self.public_packets[NBS.zero],
f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}labels', f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}labels',
)[self.index] )[self.index]
@ -52,7 +53,9 @@ def strip_output_prop(param):
cmd = self._cmd(param) cmd = self._cmd(param)
self.logger.debug(f'getter: {cmd}') self.logger.debug(f'getter: {cmd}')
return ( return (
not int.from_bytes(self.public_packet.stripstate[self.index], 'little') not int.from_bytes(
self.public_packets[NBS.zero].stripstate[self.index], 'little'
)
& getattr(self._modes, f'_bus{param.lower()}') & getattr(self._modes, f'_bus{param.lower()}')
== 0 == 0
) )
@ -71,7 +74,12 @@ def bus_mode_prop(param):
cmd = self._cmd(param) cmd = self._cmd(param)
self.logger.debug(f'getter: {cmd}') self.logger.debug(f'getter: {cmd}')
return [ return [
(int.from_bytes(self.public_packet.busstate[self.index], 'little') & val) (
int.from_bytes(
self.public_packets[NBS.zero].busstate[self.index], 'little'
)
& val
)
>> 4 >> 4
for val in self._modes.modevals for val in self._modes.modevals
] == self.modestates[param] ] == self.modestates[param]

View File

@ -2,6 +2,7 @@ import time
from abc import abstractmethod from abc import abstractmethod
from typing import Union from typing import Union
from .enums import NBS, EQGains
from .iremote import IRemote from .iremote import IRemote
from .kinds import kinds_all from .kinds import kinds_all
from .meta import channel_bool_prop, channel_label_prop, strip_output_prop from .meta import channel_bool_prop, channel_label_prop, strip_output_prop
@ -34,7 +35,7 @@ class Strip(IRemote):
def gain(self) -> float: def gain(self) -> float:
val = self.getter('gain') val = self.getter('gain')
if val is None: if val is None:
val = self.gainlayer[0].gain val = max(layer.gain for layer in self.gainlayer)
return round(val, 1) return round(val, 1)
@gain.setter @gain.setter
@ -262,12 +263,48 @@ class VirtualStrip(Strip):
@property @property
def k(self) -> int: def k(self) -> int:
return if self.public_packets[NBS.one] is None:
return 0
return self.public_packets[NBS.one].strips[self.index].karaoke
@k.setter @k.setter
def k(self, val: int): def k(self, val: int):
self.setter('karaoke', val) self.setter('karaoke', val)
@property
def bass(self) -> float:
if self.public_packets[NBS.one] is None:
return 0.0
return self.public_packets[NBS.one].strips[self.index].eqgains[EQGains.bass]
@bass.setter
def bass(self, val: float):
self.setter('EQGain1', val)
@property
def mid(self) -> float:
if self.public_packets[NBS.one] is None:
return 0.0
return self.public_packets[NBS.one].strips[self.index].eqgains[EQGains.mid]
@mid.setter
def mid(self, val: float):
self.setter('EQGain2', val)
med = mid
@property
def treble(self) -> float:
if self.public_packets[NBS.one] is None:
return 0.0
return self.public_packets[NBS.one].strips[self.index].eqgains[EQGains.treble]
@treble.setter
def treble(self, val: float):
self.setter('EQGain3', val)
high = treble
def appgain(self, name: str, gain: float): def appgain(self, name: str, gain: float):
self.setter('AppGain', f'("{name}", {gain})') self.setter('AppGain', f'("{name}", {gain})')
@ -305,7 +342,7 @@ class StripLevel(IRemote):
) )
return tuple( return tuple(
fget(i) fget(i)
for i in self._remote._get_levels(self.public_packet)[0][ for i in self._remote._get_levels(self.public_packets[NBS.zero])[0][
self.range[0] : self.range[-1] self.range[0] : self.range[-1]
] ]
) )
@ -350,9 +387,9 @@ class GainLayer(IRemote):
@property @property
def gain(self) -> float: def gain(self) -> float:
def fget(): def fget():
val = getattr(self.public_packet, f'stripgainlayer{self._i + 1}')[ val = getattr(
self.index self.public_packets[NBS.zero], f'stripgainlayer{self._i + 1}'
] )[self.index]
if 0 <= val <= 1200: if 0 <= val <= 1200:
return val * 0.01 return val * 0.01
return (((1 << 16) - 1) - val) * -0.01 return (((1 << 16) - 1) - val) * -0.01

View File

@ -82,3 +82,11 @@ def deep_merge(dict1, dict2):
yield k, dict1[k] yield k, dict1[k]
else: else:
yield k, dict2[k] yield k, dict2[k]
def bump_framecounter(framecounter: int) -> int:
"""Increment framecounter with rollover at 0xFFFFFFFF."""
if framecounter > 0xFFFFFFFF:
return 0
else:
return framecounter + 1

View File

@ -7,11 +7,12 @@ from pathlib import Path
from queue import Queue from queue import Queue
from typing import Iterable, Union from typing import Iterable, Union
from .enums import NBS
from .error import VBANCMDError from .error import VBANCMDError
from .event import Event from .event import Event
from .packet import RequestHeader from .packet import RequestHeader
from .subject import Subject from .subject import Subject
from .util import deep_merge, script from .util import bump_framecounter, deep_merge, script
from .worker import Producer, Subscriber, Updater from .worker import Producer, Subscriber, Updater
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -37,12 +38,9 @@ class VbanCmd(metaclass=ABCMeta):
for attr, val in kwargs.items(): for attr, val in kwargs.items():
setattr(self, attr, val) setattr(self, attr, val)
self.packet_request = RequestHeader( self._framecounter = 0
name=self.streamname,
bps_index=self.BPS_OPTS.index(self.bps),
channel=self.channel,
)
self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) self.sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.subject = self.observer = Subject() self.subject = self.observer = Subject()
self.cache = {} self.cache = {}
self._pdirty = False self._pdirty = False
@ -124,37 +122,49 @@ class VbanCmd(metaclass=ABCMeta):
def _set_rt(self, cmd: str, val: Union[str, float]): def _set_rt(self, cmd: str, val: Union[str, float]):
"""Sends a string request command over a network.""" """Sends a string request command over a network."""
req_packet = RequestHeader.to_bytes(
name=self.streamname,
bps_index=self.BPS_OPTS.index(self.bps),
channel=self.channel,
framecounter=self._framecounter,
)
self.sock.sendto( self.sock.sendto(
self.packet_request.header + f'{cmd}={val};'.encode(), req_packet + f'{cmd}={val};'.encode(),
(socket.gethostbyname(self.ip), self.port), (socket.gethostbyname(self.ip), self.port),
) )
self.packet_request.framecounter = ( self._framecounter = bump_framecounter(self._framecounter)
int.from_bytes(self.packet_request.framecounter, 'little') + 1
).to_bytes(4, 'little')
self.cache[cmd] = val self.cache[cmd] = val
@script @script
def sendtext(self, script): def sendtext(self, script):
"""Sends a multiple parameter string over a network.""" """Sends a multiple parameter string over a network."""
req_packet = RequestHeader.to_bytes(
name=self.streamname,
bps_index=self.BPS_OPTS.index(self.bps),
channel=self.channel,
framecounter=self._framecounter,
)
self.sock.sendto( self.sock.sendto(
self.packet_request.header + script.encode(), req_packet + script.encode(),
(socket.gethostbyname(self.ip), self.port), (socket.gethostbyname(self.ip), self.port),
) )
self.packet_request.framecounter = ( self._framecounter = bump_framecounter(self._framecounter)
int.from_bytes(self.packet_request.framecounter, 'little') + 1
).to_bytes(4, 'little')
self.logger.debug(f'sendtext: {script}') self.logger.debug(f'sendtext: {script}')
time.sleep(self.DELAY) time.sleep(self.DELAY)
@property @property
def type(self) -> str: def type(self) -> str:
"""Returns the type of Voicemeeter installation.""" """Returns the type of Voicemeeter installation."""
return self.public_packet.voicemeetertype return self.public_packets[NBS.zero].voicemeetertype
@property @property
def version(self) -> str: def version(self) -> str:
"""Returns Voicemeeter's version as a string""" """Returns Voicemeeter's version as a string"""
return '{0}.{1}.{2}.{3}'.format(*self.public_packet.voicemeeterversion) return '{0}.{1}.{2}.{3}'.format(
*self.public_packets[NBS.zero].voicemeeterversion
)
@property @property
def pdirty(self): def pdirty(self):
@ -167,8 +177,8 @@ class VbanCmd(metaclass=ABCMeta):
return self._ldirty return self._ldirty
@property @property
def public_packet(self): def public_packets(self):
return self._public_packet return self._public_packets
def clear_dirty(self) -> None: def clear_dirty(self) -> None:
while self.pdirty: while self.pdirty: