125 lines
4.6 KiB
Python

from dataclasses import dataclass
from enum import Enum
from .headers import VbanPingHeader
# VBAN PING bitType constants
VBANPING_TYPE_RECEPTOR = 0x00000001 # Simple receptor
VBANPING_TYPE_TRANSMITTER = 0x00000002 # Simple Transmitter
VBANPING_TYPE_RECEPTORSPOT = 0x00000004 # SPOT receptor
VBANPING_TYPE_TRANSMITTERSPOT = 0x00000008 # SPOT transmitter
VBANPING_TYPE_VIRTUALDEVICE = 0x00000010 # Virtual Device
VBANPING_TYPE_VIRTUALMIXER = 0x00000020 # Virtual Mixer
VBANPING_TYPE_MATRIX = 0x00000040 # MATRIX
VBANPING_TYPE_DAW = 0x00000080 # Workstation
VBANPING_TYPE_SERVER = 0x01000000 # VBAN SERVER
# VBAN PING bitfeature constants
VBANPING_FEATURE_AUDIO = 0x00000001
VBANPING_FEATURE_AOIP = 0x00000002
VBANPING_FEATURE_VOIP = 0x00000004
VBANPING_FEATURE_SERIAL = 0x00000100
VBANPING_FEATURE_MIDI = 0x00000300
VBANPING_FEATURE_FRAME = 0x00001000
VBANPING_FEATURE_TXT = 0x00010000
class VbanServerType(Enum):
"""VBAN server types detected from PONG responses"""
UNKNOWN = 0
VOICEMEETER = VBANPING_TYPE_VIRTUALMIXER
MATRIX = VBANPING_TYPE_MATRIX
@dataclass
class VbanPing0Payload:
"""Represents the VBAN PING0 payload structure as defined in the VBAN protocol documentation."""
def __init__(self):
self.bit_type = VBANPING_TYPE_RECEPTOR
self.bit_feature = VBANPING_FEATURE_TXT
self.bit_feature_ex = 0x00000000
self.preferred_rate = 48000
self.min_rate = 8000
self.max_rate = 192000
self.color_rgb = 0x00FF0000
self.version = b'\x01\x02\x03\x04'
self.gps_position = b'\x00' * 8
self.user_position = b'\x00' * 8
self.lang_code = b'EN\x00\x00\x00\x00\x00\x00'
self.reserved = b'\x00' * 8
self.reserved_ex = b'\x00' * 64
self.distant_ip = b'\x00' * 32
self.distant_port = 0
self.distant_reserved = 0
self.device_name = b'VBAN-CMD-Python\x00'.ljust(64, b'\x00')
self.manufacturer_name = b'Python-VBAN\x00'.ljust(64, b'\x00')
self.application_name = b'vban-cmd\x00'.ljust(64, b'\x00')
self.host_name = b'localhost\x00'.ljust(64, b'\x00')
self.user_name = b'Python User\x00'.ljust(128, b'\x00')
self.user_comment = b'VBAN CMD Python Client\x00'.ljust(128, b'\x00')
@classmethod
def to_bytes(cls) -> bytes:
"""Convert payload to bytes"""
payload = cls()
data = bytearray()
data.extend(payload.bit_type.to_bytes(4, 'little'))
data.extend(payload.bit_feature.to_bytes(4, 'little'))
data.extend(payload.bit_feature_ex.to_bytes(4, 'little'))
data.extend(payload.preferred_rate.to_bytes(4, 'little'))
data.extend(payload.min_rate.to_bytes(4, 'little'))
data.extend(payload.max_rate.to_bytes(4, 'little'))
data.extend(payload.color_rgb.to_bytes(4, 'little'))
data.extend(payload.version)
data.extend(payload.gps_position)
data.extend(payload.user_position)
data.extend(payload.lang_code)
data.extend(payload.reserved)
data.extend(payload.reserved_ex)
data.extend(payload.distant_ip)
data.extend(payload.distant_port.to_bytes(2, 'little'))
data.extend(payload.distant_reserved.to_bytes(2, 'little'))
data.extend(payload.device_name)
data.extend(payload.manufacturer_name)
data.extend(payload.application_name)
data.extend(payload.host_name)
data.extend(payload.user_name)
data.extend(payload.user_comment)
return bytes(data)
@classmethod
def create_packet(cls, framecounter: int) -> bytes:
"""Creates a complete PING packet with header and payload."""
data = bytearray()
data.extend(VbanPingHeader.to_bytes(framecounter))
data.extend(cls.to_bytes())
return bytes(data)
@staticmethod
def detect_server_type(pong_data: bytes) -> VbanServerType:
"""Detect server type from PONG response packet.
Args:
pong_data: Raw bytes from PONG response packet
Returns:
VbanServerType enum indicating the detected server type
"""
try:
if len(pong_data) >= 32:
frame_counter_bytes = pong_data[28:32]
frame_counter = int.from_bytes(frame_counter_bytes, 'little')
if frame_counter == VbanServerType.MATRIX.value:
return VbanServerType.MATRIX
elif frame_counter == VbanServerType.VOICEMEETER.value:
return VbanServerType.VOICEMEETER
return VbanServerType.UNKNOWN
except Exception:
return VbanServerType.UNKNOWN