diff --git a/vban_cmd/bus.py b/vban_cmd/bus.py index 29b4a43..1b4d1bb 100644 --- a/vban_cmd/bus.py +++ b/vban_cmd/bus.py @@ -1,17 +1,11 @@ import time from abc import abstractmethod -from enum import IntEnum from typing import Union +from .enums import NBS, BusModes from .iremote import IRemote 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): """ @@ -31,7 +25,7 @@ class Bus(IRemote): @property def gain(self) -> float: def fget(): - val = self.public_packet.busgain[self.index] + val = self.public_packets[NBS.zero].busgain[self.index] if 0 <= val <= 1200: return val * 0.01 return (((1 << 16) - 1) - val) * -0.01 @@ -109,7 +103,7 @@ class BusLevel(IRemote): ) return tuple( 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] ] ) @@ -157,7 +151,12 @@ def _make_bus_mode_mixin(): def get(self): 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 for val in self._modes.modevals ] diff --git a/vban_cmd/enums.py b/vban_cmd/enums.py new file mode 100644 index 0000000..fd00350 --- /dev/null +++ b/vban_cmd/enums.py @@ -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) diff --git a/vban_cmd/iremote.py b/vban_cmd/iremote.py index 0bafa0f..bb8fc86 100644 --- a/vban_cmd/iremote.py +++ b/vban_cmd/iremote.py @@ -116,9 +116,9 @@ class IRemote(metaclass=ABCMeta): pass @property - def public_packet(self): + def public_packets(self): """Returns an RT data packet.""" - return self._remote.public_packet + return self._remote.public_packets def apply(self, data): """Sets all parameters of a dict for the channel.""" diff --git a/vban_cmd/meta.py b/vban_cmd/meta.py index 2faf454..776cd90 100644 --- a/vban_cmd/meta.py +++ b/vban_cmd/meta.py @@ -1,5 +1,6 @@ from functools import partial +from .enums import NBS from .util import cache_bool, cache_string @@ -13,7 +14,7 @@ def channel_bool_prop(param): return ( not int.from_bytes( getattr( - self.public_packet, + self.public_packets[NBS.zero], f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}state', )[self.index], 'little', @@ -34,7 +35,7 @@ def channel_label_prop(): @partial(cache_string, param='label') def fget(self) -> str: return getattr( - self.public_packet, + self.public_packets[NBS.zero], f'{"strip" if "strip" in type(self).__name__.lower() else "bus"}labels', )[self.index] @@ -52,7 +53,9 @@ def strip_output_prop(param): cmd = self._cmd(param) self.logger.debug(f'getter: {cmd}') 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()}') == 0 ) @@ -71,7 +74,12 @@ def bus_mode_prop(param): cmd = self._cmd(param) self.logger.debug(f'getter: {cmd}') 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 for val in self._modes.modevals ] == self.modestates[param] diff --git a/vban_cmd/strip.py b/vban_cmd/strip.py index c3716e6..eca112a 100644 --- a/vban_cmd/strip.py +++ b/vban_cmd/strip.py @@ -2,6 +2,7 @@ import time from abc import abstractmethod from typing import Union +from .enums import NBS, EQGains from .iremote import IRemote from .kinds import kinds_all from .meta import channel_bool_prop, channel_label_prop, strip_output_prop @@ -34,7 +35,7 @@ class Strip(IRemote): def gain(self) -> float: val = self.getter('gain') if val is None: - val = self.gainlayer[0].gain + val = max(layer.gain for layer in self.gainlayer) return round(val, 1) @gain.setter @@ -262,12 +263,48 @@ class VirtualStrip(Strip): @property 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 def k(self, val: int): 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): self.setter('AppGain', f'("{name}", {gain})') @@ -305,7 +342,7 @@ class StripLevel(IRemote): ) return tuple( 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] ] ) @@ -350,9 +387,9 @@ class GainLayer(IRemote): @property def gain(self) -> float: def fget(): - val = getattr(self.public_packet, f'stripgainlayer{self._i + 1}')[ - self.index - ] + val = getattr( + self.public_packets[NBS.zero], f'stripgainlayer{self._i + 1}' + )[self.index] if 0 <= val <= 1200: return val * 0.01 return (((1 << 16) - 1) - val) * -0.01 diff --git a/vban_cmd/util.py b/vban_cmd/util.py index 260e1d5..ae272db 100644 --- a/vban_cmd/util.py +++ b/vban_cmd/util.py @@ -82,3 +82,11 @@ def deep_merge(dict1, dict2): yield k, dict1[k] else: 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 diff --git a/vban_cmd/vbancmd.py b/vban_cmd/vbancmd.py index 28b2136..c473611 100644 --- a/vban_cmd/vbancmd.py +++ b/vban_cmd/vbancmd.py @@ -7,11 +7,12 @@ from pathlib import Path from queue import Queue from typing import Iterable, Union +from .enums import NBS from .error import VBANCMDError from .event import Event from .packet import RequestHeader from .subject import Subject -from .util import deep_merge, script +from .util import bump_framecounter, deep_merge, script from .worker import Producer, Subscriber, Updater logger = logging.getLogger(__name__) @@ -37,12 +38,9 @@ class VbanCmd(metaclass=ABCMeta): for attr, val in kwargs.items(): setattr(self, attr, val) - self.packet_request = RequestHeader( - name=self.streamname, - bps_index=self.BPS_OPTS.index(self.bps), - channel=self.channel, - ) + self._framecounter = 0 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.cache = {} self._pdirty = False @@ -124,37 +122,49 @@ class VbanCmd(metaclass=ABCMeta): def _set_rt(self, cmd: str, val: Union[str, float]): """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.packet_request.header + f'{cmd}={val};'.encode(), + req_packet + f'{cmd}={val};'.encode(), (socket.gethostbyname(self.ip), self.port), ) - self.packet_request.framecounter = ( - int.from_bytes(self.packet_request.framecounter, 'little') + 1 - ).to_bytes(4, 'little') + self._framecounter = bump_framecounter(self._framecounter) + self.cache[cmd] = val @script def sendtext(self, script): """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.packet_request.header + script.encode(), + req_packet + script.encode(), (socket.gethostbyname(self.ip), self.port), ) - self.packet_request.framecounter = ( - int.from_bytes(self.packet_request.framecounter, 'little') + 1 - ).to_bytes(4, 'little') + self._framecounter = bump_framecounter(self._framecounter) + self.logger.debug(f'sendtext: {script}') time.sleep(self.DELAY) @property def type(self) -> str: """Returns the type of Voicemeeter installation.""" - return self.public_packet.voicemeetertype + return self.public_packets[NBS.zero].voicemeetertype @property def version(self) -> str: """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 def pdirty(self): @@ -167,8 +177,8 @@ class VbanCmd(metaclass=ABCMeta): return self._ldirty @property - def public_packet(self): - return self._public_packet + def public_packets(self): + return self._public_packets def clear_dirty(self) -> None: while self.pdirty: