From d439da725c644d1f5b8a4fba9e38223b372e99a6 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 18 Jan 2026 14:42:07 +0000 Subject: [PATCH] implement parametric eq --- vban_cmd/kinds.py | 20 ++++-- vban_cmd/packet.py | 24 +++++-- vban_cmd/strip.py | 155 ++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 189 insertions(+), 10 deletions(-) diff --git a/vban_cmd/kinds.py b/vban_cmd/kinds.py index 9f79950..c3942e3 100644 --- a/vban_cmd/kinds.py +++ b/vban_cmd/kinds.py @@ -15,12 +15,15 @@ class SingletonType(type): return cls._instances[cls] -@dataclass +@dataclass(frozen=True) class KindMapClass(metaclass=SingletonType): name: str ins: tuple outs: tuple vban: tuple + strip_channels: int + bus_channels: int + cells: int @property def phys_in(self): @@ -58,28 +61,37 @@ class KindMapClass(metaclass=SingletonType): return self.name.capitalize() -@dataclass +@dataclass(frozen=True) class BasicMap(KindMapClass): name: str ins: tuple = (2, 1) outs: tuple = (1, 1) vban: tuple = (4, 4, 1, 1) + strip_channels: int = 0 + bus_channels: int = 0 + cells: int = 0 -@dataclass +@dataclass(frozen=True) class BananaMap(KindMapClass): name: str ins: tuple = (3, 2) outs: tuple = (3, 2) vban: tuple = (8, 8, 1, 1) + strip_channels: int = 0 + bus_channels: int = 8 + cells: int = 6 -@dataclass +@dataclass(frozen=True) class PotatoMap(KindMapClass): name: str ins: tuple = (5, 3) outs: tuple = (5, 3) vban: tuple = (8, 8, 1, 1) + strip_channels: int = 2 + bus_channels: int = 8 + cells: int = 6 def kind_factory(kind_id): diff --git a/vban_cmd/packet.py b/vban_cmd/packet.py index d93fd59..62dbaff 100644 --- a/vban_cmd/packet.py +++ b/vban_cmd/packet.py @@ -1,3 +1,4 @@ +import struct from dataclasses import dataclass from typing import NamedTuple @@ -240,11 +241,11 @@ class EqGains(NamedTuple): class ParametricEQSettings(NamedTuple): - eq_on: bool - eq_type: int - eq_gain: float - eq_freq: float - eq_q: float + on: bool + type: int + gain: float + freq: float + q: float class Sends(NamedTuple): @@ -437,6 +438,19 @@ class VbanVMParamStrip: ] ) + @property + def parametric_eq_settings(self) -> tuple[ParametricEQSettings, ...]: + return tuple( + ParametricEQSettings( + on=bool(int.from_bytes(self._PEQ_eqOn[i : i + 1], 'little')), + type=int.from_bytes(self._PEQ_eqtype[i : i + 1], 'little'), + freq=struct.unpack(' Sends: return Sends( diff --git a/vban_cmd/strip.py b/vban_cmd/strip.py index 1820ebe..d52f127 100644 --- a/vban_cmd/strip.py +++ b/vban_cmd/strip.py @@ -68,7 +68,7 @@ class PhysicalStrip(Strip): 'comp': StripComp(remote, index), 'gate': StripGate(remote, index), 'denoiser': StripDenoiser(remote, index), - 'eq': StripEQ(remote, index), + 'eq': StripEQ.make(remote, index), }, ) @@ -247,6 +247,25 @@ class StripDenoiser(IRemote): class StripEQ(IRemote): + @classmethod + def make(cls, remote, i): + """ + Factory method for Strip EQ. + + Returns a StripEQ class. + """ + STRIPEQ_cls = type( + 'StripEQ', + (cls,), + { + 'channel': tuple( + StripEQCh.make(remote, i, j) + for j in range(remote.kind.strip_channels) + ) + }, + ) + return STRIPEQ_cls(remote, i) + @property def identifier(self) -> str: return f'strip[{self.index}].eq' @@ -268,6 +287,140 @@ class StripEQ(IRemote): self.setter('ab', 1 if val else 0) +class StripEQCh(IRemote): + @classmethod + def make(cls, remote, i, j): + """ + Factory method for Strip EQ channel. + + Returns a StripEQCh class. + """ + StripEQCh_cls = type( + 'StripEQCh', + (cls,), + { + 'cell': tuple( + StripEQChCell(remote, i, j, k) for k in range(remote.kind.cells) + ) + }, + ) + return StripEQCh_cls(remote, i, j) + + def __init__(self, remote, i, j): + super().__init__(remote, i) + self.channel_index = j + + @property + def identifier(self) -> str: + return f'Strip[{self.index}].eq.channel[{self.channel_index}]' + + +class StripEQChCell(IRemote): + def __init__(self, remote, i, j, k): + super().__init__(remote, i) + self.channel_index = j + self.cell_index = k + + @property + def identifier(self) -> str: + return f'Strip[{self.index}].eq.channel[{self.channel_index}].cell[{self.cell_index}]' + + @property + def on(self) -> bool: + if self.channel_index > 0: + self.logger.warning( + 'Only channel 0 is supported over VBAN for Strip EQ cells' + ) + if self.public_packets[NBS.one] is None: + return False + return ( + self.public_packets[NBS.one] + .strips[self.index] + .parametric_eq_settings[self.cell_index] + .on + ) + + @on.setter + def on(self, val: bool): + self.setter('on', 1 if val else 0) + + @property + def type(self) -> int: + if self.channel_index > 0: + self.logger.warning( + 'Only channel 0 is supported over VBAN for Strip EQ cells' + ) + if self.public_packets[NBS.one] is None: + return 0 + return ( + self.public_packets[NBS.one] + .strips[self.index] + .parametric_eq_settings[self.cell_index] + .type + ) + + @type.setter + def type(self, val: int): + self.setter('type', val) + + @property + def f(self) -> float: + if self.channel_index > 0: + self.logger.warning( + 'Only channel 0 is supported over VBAN for Strip EQ cells' + ) + if self.public_packets[NBS.one] is None: + return 0.0 + return ( + self.public_packets[NBS.one] + .strips[self.index] + .parametric_eq_settings[self.cell_index] + .freq + ) + + @f.setter + def f(self, val: float): + self.setter('f', val) + + @property + def gain(self) -> float: + if self.channel_index > 0: + self.logger.warning( + 'Only channel 0 is supported over VBAN for Strip EQ cells' + ) + if self.public_packets[NBS.one] is None: + return 0.0 + return ( + self.public_packets[NBS.one] + .strips[self.index] + .parametric_eq_settings[self.cell_index] + .gain + ) + + @gain.setter + def gain(self, val: float): + self.setter('gain', val) + + @property + def q(self) -> float: + if self.channel_index > 0: + self.logger.warning( + 'Only channel 0 is supported over VBAN for Strip EQ cells' + ) + if self.public_packets[NBS.one] is None: + return 0.0 + return ( + self.public_packets[NBS.one] + .strips[self.index] + .parametric_eq_settings[self.cell_index] + .q + ) + + @q.setter + def q(self, val: float): + self.setter('q', val) + + class VirtualStrip(Strip): @classmethod def make(cls, remote, i, is_phys):