2022-03-04 14:22:29 +00:00
|
|
|
from dataclasses import dataclass
|
|
|
|
|
2023-06-25 16:15:32 +01:00
|
|
|
from .kinds import KindMapClass
|
2022-10-04 15:39:56 +01:00
|
|
|
from .util import comp
|
|
|
|
|
2023-08-04 23:06:51 +01:00
|
|
|
VBAN_PROTOCOL_TXT = 0x40
|
|
|
|
VBAN_PROTOCOL_SERVICE = 0x60
|
|
|
|
|
2022-03-26 23:00:09 +00:00
|
|
|
VBAN_SERVICE_RTPACKETREGISTER = 32
|
|
|
|
VBAN_SERVICE_RTPACKET = 33
|
2023-08-04 23:06:51 +01:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
MAX_PACKET_SIZE = 1436
|
2023-08-04 23:06:51 +01:00
|
|
|
HEADER_SIZE = 4 + 1 + 1 + 1 + 1 + 16
|
2022-03-26 23:00:09 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
|
|
|
|
@dataclass
|
2022-08-10 17:49:21 +01:00
|
|
|
class VbanRtPacket:
|
|
|
|
"""Represents the body of a VBAN RT data packet"""
|
|
|
|
|
2023-06-25 16:15:32 +01:00
|
|
|
_kind: KindMapClass
|
2023-08-04 23:06:51 +01:00
|
|
|
_voicemeeterType: bytes # data[28:29]
|
|
|
|
_reserved: bytes # data[29:30]
|
|
|
|
_buffersize: bytes # data[30:32]
|
|
|
|
_voicemeeterVersion: bytes # data[32:36]
|
|
|
|
_optionBits: bytes # data[36:40]
|
|
|
|
_samplerate: bytes # data[40:44]
|
|
|
|
_inputLeveldB100: bytes # data[44:112]
|
|
|
|
_outputLeveldB100: bytes # data[112:240]
|
|
|
|
_TransportBit: bytes # data[240:244]
|
|
|
|
_stripState: bytes # data[244:276]
|
|
|
|
_busState: bytes # data[276:308]
|
|
|
|
_stripGaindB100Layer1: bytes # data[308:324]
|
|
|
|
_stripGaindB100Layer2: bytes # data[324:340]
|
|
|
|
_stripGaindB100Layer3: bytes # data[340:356]
|
|
|
|
_stripGaindB100Layer4: bytes # data[356:372]
|
|
|
|
_stripGaindB100Layer5: bytes # data[372:388]
|
|
|
|
_stripGaindB100Layer6: bytes # data[388:404]
|
|
|
|
_stripGaindB100Layer7: bytes # data[404:420]
|
|
|
|
_stripGaindB100Layer8: bytes # data[420:436]
|
|
|
|
_busGaindB100: bytes # data[436:452]
|
|
|
|
_stripLabelUTF8c60: bytes # data[452:932]
|
|
|
|
_busLabelUTF8c60: bytes # data[932:1412]
|
2022-10-04 15:39:56 +01:00
|
|
|
|
|
|
|
def _generate_levels(self, levelarray) -> tuple:
|
|
|
|
return tuple(
|
|
|
|
int.from_bytes(levelarray[i : i + 2], "little")
|
|
|
|
for i in range(0, len(levelarray), 2)
|
|
|
|
)
|
|
|
|
|
2023-06-25 16:15:32 +01:00
|
|
|
@property
|
|
|
|
def strip_levels(self):
|
|
|
|
return self._generate_levels(self._inputLeveldB100)
|
|
|
|
|
|
|
|
@property
|
|
|
|
def bus_levels(self):
|
|
|
|
return self._generate_levels(self._outputLeveldB100)
|
|
|
|
|
2022-10-04 15:39:56 +01:00
|
|
|
def pdirty(self, other) -> bool:
|
2022-05-14 14:00:08 +01:00
|
|
|
"""True iff any defined parameter has changed"""
|
|
|
|
|
2022-04-26 23:38:56 +01:00
|
|
|
return not (
|
2022-03-26 23:00:09 +00:00
|
|
|
self._stripState == other._stripState
|
|
|
|
and self._busState == other._busState
|
|
|
|
and self._stripGaindB100Layer1 == other._stripGaindB100Layer1
|
2022-03-29 23:40:35 +01:00
|
|
|
and self._stripGaindB100Layer2 == other._stripGaindB100Layer2
|
|
|
|
and self._stripGaindB100Layer3 == other._stripGaindB100Layer3
|
|
|
|
and self._stripGaindB100Layer4 == other._stripGaindB100Layer4
|
|
|
|
and self._stripGaindB100Layer5 == other._stripGaindB100Layer5
|
|
|
|
and self._stripGaindB100Layer6 == other._stripGaindB100Layer6
|
|
|
|
and self._stripGaindB100Layer7 == other._stripGaindB100Layer7
|
|
|
|
and self._stripGaindB100Layer8 == other._stripGaindB100Layer8
|
2022-03-26 23:00:09 +00:00
|
|
|
and self._busGaindB100 == other._busGaindB100
|
2022-10-04 15:39:56 +01:00
|
|
|
and self._stripLabelUTF8c60 == other._stripLabelUTF8c60
|
|
|
|
and self._busLabelUTF8c60 == other._busLabelUTF8c60
|
|
|
|
)
|
|
|
|
|
|
|
|
def ldirty(self, strip_cache, bus_cache) -> bool:
|
|
|
|
self._strip_comp, self._bus_comp = (
|
2023-06-25 16:15:32 +01:00
|
|
|
tuple(not val for val in comp(strip_cache, self.strip_levels)),
|
|
|
|
tuple(not val for val in comp(bus_cache, self.bus_levels)),
|
2022-04-26 23:38:56 +01:00
|
|
|
)
|
2022-10-04 15:39:56 +01:00
|
|
|
return any(any(l) for l in (self._strip_comp, self._bus_comp))
|
2022-03-20 12:25:50 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def voicemeetertype(self) -> str:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns voicemeeter type as a string"""
|
|
|
|
type_ = ("basic", "banana", "potato")
|
|
|
|
return type_[int.from_bytes(self._voicemeeterType, "little") - 1]
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def voicemeeterversion(self) -> tuple:
|
2022-03-27 16:12:07 +01:00
|
|
|
"""returns voicemeeter version as a tuple"""
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
|
|
|
reversed(
|
|
|
|
tuple(
|
|
|
|
int.from_bytes(self._voicemeeterVersion[i : i + 1], "little")
|
|
|
|
for i in range(4)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def samplerate(self) -> int:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns samplerate as an int"""
|
|
|
|
return int.from_bytes(self._samplerate, "little")
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
2022-10-04 15:39:56 +01:00
|
|
|
def inputlevels(self) -> tuple:
|
|
|
|
"""returns the entire level array across all inputs for a kind"""
|
2023-07-25 16:22:47 +01:00
|
|
|
return self.strip_levels[0 : self._kind.num_strip_levels]
|
2022-03-26 23:00:09 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
2022-10-04 15:39:56 +01:00
|
|
|
def outputlevels(self) -> tuple:
|
|
|
|
"""returns the entire level array across all outputs for a kind"""
|
2023-07-25 16:22:47 +01:00
|
|
|
return self.bus_levels[0 : self._kind.num_bus_levels]
|
2022-03-26 23:00:09 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripstate(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns tuple of strip states accessable through bit modes"""
|
|
|
|
return tuple(self._stripState[i : i + 4] for i in range(0, 32, 4))
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def busstate(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns tuple of bus states accessable through bit modes"""
|
|
|
|
return tuple(self._busState[i : i + 4] for i in range(0, 32, 4))
|
2022-03-04 14:22:29 +00:00
|
|
|
|
|
|
|
"""
|
|
|
|
these functions return an array of gainlayers[i] across all strips
|
|
|
|
ie stripgainlayer1 = [strip[0].gainlayer[0], strip[1].gainlayer[0], strip[2].gainlayer[0]...]
|
2022-03-26 23:00:09 +00:00
|
|
|
"""
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer1(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer1[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer2(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer2[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer3(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer3[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer4(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer4[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer5(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer5[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer6(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer6[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer7(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer7[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def stripgainlayer8(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._stripGaindB100Layer8[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
2022-03-04 14:22:29 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def busgain(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns tuple of bus gains"""
|
|
|
|
return tuple(
|
2022-09-28 18:07:10 +01:00
|
|
|
int.from_bytes(self._busGaindB100[i : i + 2], "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 16, 2)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def striplabels(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns tuple of strip labels"""
|
|
|
|
return tuple(
|
2022-07-16 21:52:41 +01:00
|
|
|
self._stripLabelUTF8c60[i : i + 60].decode().split("\x00")[0]
|
2022-03-26 23:00:09 +00:00
|
|
|
for i in range(0, 480, 60)
|
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def buslabels(self) -> tuple:
|
2022-03-26 23:00:09 +00:00
|
|
|
"""returns tuple of bus labels"""
|
|
|
|
return tuple(
|
2022-07-16 21:52:41 +01:00
|
|
|
self._busLabelUTF8c60[i : i + 60].decode().split("\x00")[0]
|
2022-03-28 18:23:46 +01:00
|
|
|
for i in range(0, 480, 60)
|
2022-03-26 23:00:09 +00:00
|
|
|
)
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
|
2023-08-04 23:06:51 +01:00
|
|
|
@dataclass
|
|
|
|
class SubscribeHeader:
|
|
|
|
"""Represents the header an RT Packet Service subscription packet"""
|
|
|
|
|
|
|
|
name = "Register RTP"
|
|
|
|
timeout = 15
|
|
|
|
vban: bytes = "VBAN".encode()
|
|
|
|
format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, "little")
|
|
|
|
format_nbs: bytes = (0).to_bytes(1, "little")
|
|
|
|
format_nbc: bytes = (VBAN_SERVICE_RTPACKETREGISTER).to_bytes(1, "little")
|
|
|
|
format_bit: bytes = (timeout & 0x000000FF).to_bytes(1, "little") # timeout
|
|
|
|
streamname: bytes = name.encode("ascii") + bytes(16 - len(name))
|
|
|
|
framecounter: bytes = (0).to_bytes(4, "little")
|
|
|
|
|
|
|
|
@property
|
|
|
|
def header(self):
|
|
|
|
header = self.vban
|
|
|
|
header += self.format_sr
|
|
|
|
header += self.format_nbs
|
|
|
|
header += self.format_nbc
|
|
|
|
header += self.format_bit
|
|
|
|
header += self.streamname
|
|
|
|
header += self.framecounter
|
|
|
|
assert (
|
|
|
|
len(header) == HEADER_SIZE + 4
|
|
|
|
), f"expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE +4} bytes total)"
|
|
|
|
return header
|
|
|
|
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@dataclass
|
2022-08-10 17:49:21 +01:00
|
|
|
class VbanRtPacketHeader:
|
2023-08-04 23:06:51 +01:00
|
|
|
"""Represents the header of a VBAN RT response packet"""
|
2022-03-26 23:00:09 +00:00
|
|
|
|
|
|
|
name = "Voicemeeter-RTP"
|
|
|
|
vban: bytes = "VBAN".encode()
|
2023-08-04 23:06:51 +01:00
|
|
|
format_sr: bytes = (VBAN_PROTOCOL_SERVICE).to_bytes(1, "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
format_nbs: bytes = (0).to_bytes(1, "little")
|
|
|
|
format_nbc: bytes = (VBAN_SERVICE_RTPACKET).to_bytes(1, "little")
|
|
|
|
format_bit: bytes = (0).to_bytes(1, "little")
|
|
|
|
streamname: bytes = name.encode("ascii") + bytes(16 - len(name))
|
2022-03-04 14:22:29 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def header(self):
|
2022-03-26 23:00:09 +00:00
|
|
|
header = self.vban
|
2022-03-04 14:22:29 +00:00
|
|
|
header += self.format_sr
|
|
|
|
header += self.format_nbs
|
|
|
|
header += self.format_nbc
|
|
|
|
header += self.format_bit
|
|
|
|
header += self.streamname
|
2023-08-04 23:06:51 +01:00
|
|
|
assert len(header) == HEADER_SIZE, f"expected header size {HEADER_SIZE} bytes"
|
2022-03-04 14:22:29 +00:00
|
|
|
return header
|
|
|
|
|
2022-03-26 23:00:09 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@dataclass
|
2022-08-10 17:49:21 +01:00
|
|
|
class RequestHeader:
|
2023-08-05 13:40:32 +01:00
|
|
|
"""Represents the header of a REQUEST RT PACKET"""
|
2022-03-26 23:00:09 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
name: str
|
|
|
|
bps_index: int
|
|
|
|
channel: int
|
2022-03-26 23:00:09 +00:00
|
|
|
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")
|
2022-03-04 14:22:29 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def sr(self):
|
2023-08-04 23:06:51 +01:00
|
|
|
return (VBAN_PROTOCOL_TXT + self.bps_index).to_bytes(1, "little")
|
2022-03-26 23:00:09 +00:00
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def nbc(self):
|
2022-03-26 23:00:09 +00:00
|
|
|
return (self.channel).to_bytes(1, "little")
|
|
|
|
|
2022-03-04 14:22:29 +00:00
|
|
|
@property
|
|
|
|
def streamname(self):
|
2022-03-26 23:00:09 +00:00
|
|
|
return self.name.encode() + bytes(16 - len(self.name))
|
2022-03-04 14:22:29 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def header(self):
|
2022-03-26 23:00:09 +00:00
|
|
|
header = self.vban
|
2022-03-04 14:22:29 +00:00
|
|
|
header += self.sr
|
|
|
|
header += self.nbs
|
|
|
|
header += self.nbc
|
|
|
|
header += self.bit
|
|
|
|
header += self.streamname
|
|
|
|
header += self.framecounter
|
2023-08-04 23:06:51 +01:00
|
|
|
assert (
|
|
|
|
len(header) == HEADER_SIZE + 4
|
|
|
|
), f"expected header size {HEADER_SIZE} bytes + 4 bytes framecounter ({HEADER_SIZE +4} bytes total)"
|
2022-06-16 16:10:06 +01:00
|
|
|
return header
|