diff --git a/voicemeeterlib/factory.py b/voicemeeterlib/factory.py index 947cb26..403faba 100644 --- a/voicemeeterlib/factory.py +++ b/voicemeeterlib/factory.py @@ -26,7 +26,7 @@ class FactoryBuilder: BuilderProgress = IntEnum( "BuilderProgress", - "strip bus command macrobutton vban device recorder fx", + "strip bus command macrobutton vban device option recorder patch fx", start=0, ) @@ -40,7 +40,9 @@ class FactoryBuilder: f"Finished building macrobuttons for {self._factory}", f"Finished building vban in/out streams for {self._factory}", f"Finished building device for {self._factory}", + f"Finished building option for {self._factory}", f"Finished building recorder for {self._factory}", + f"Finished building patch for {self._factory}", f"Finished building fx for {self._factory}", ) @@ -79,10 +81,18 @@ class FactoryBuilder: self._factory.device = Device.make(self._factory) return self + def make_option(self) -> Self: + self._factory.option = misc.Option.make(self._factory) + return self + def make_recorder(self) -> Self: self._factory.recorder = Recorder.make(self._factory) return self + def make_patch(self) -> Self: + self._factory.patch = misc.Patch.make(self._factory) + return self + def make_fx(self) -> Self: self._factory.fx = misc.FX(self._factory) return self @@ -104,6 +114,7 @@ class FactoryBase(Remote): self.builder.make_macrobutton, self.builder.make_vban, self.builder.make_device, + self.builder.make_option, ) self._configs = None @@ -162,7 +173,7 @@ class BananaFactory(FactoryBase): @property def steps(self) -> Iterable: """steps required to build the interface for a kind""" - return self._steps + (self.builder.make_recorder,) + return self._steps + (self.builder.make_recorder, self.builder.make_patch) class PotatoFactory(FactoryBase): @@ -184,7 +195,11 @@ class PotatoFactory(FactoryBase): @property def steps(self) -> Iterable: """steps required to build the interface for a kind""" - return self._steps + (self.builder.make_recorder, self.builder.make_fx) + return self._steps + ( + self.builder.make_recorder, + self.builder.make_patch, + self.builder.make_fx, + ) def remote_factory(kind_id: str, **kwargs) -> Remote: diff --git a/voicemeeterlib/kinds.py b/voicemeeterlib/kinds.py index 0ca4003..e74b1c2 100644 --- a/voicemeeterlib/kinds.py +++ b/voicemeeterlib/kinds.py @@ -26,6 +26,8 @@ class KindMapClass(metaclass=SingletonType): ins: tuple outs: tuple vban: tuple + asio: tuple + insert: int @property def phys_in(self): @@ -61,6 +63,8 @@ class BasicMap(KindMapClass): ins: tuple = (2, 1) outs: tuple = (1, 1) vban: tuple = (4, 4) + asio: tuple = (0, 0) + insert: int = 0 @dataclass @@ -69,6 +73,8 @@ class BananaMap(KindMapClass): ins: tuple = (3, 2) outs: tuple = (3, 2) vban: tuple = (8, 8) + asio: tuple = (6, 8) + insert: int = 22 @dataclass @@ -77,6 +83,8 @@ class PotatoMap(KindMapClass): ins: tuple = (5, 3) outs: tuple = (5, 3) vban: tuple = (8, 8) + asio: tuple = (10, 8) + insert: int = 34 def kind_factory(kind_id): diff --git a/voicemeeterlib/misc.py b/voicemeeterlib/misc.py index 228aece..3a008c4 100644 --- a/voicemeeterlib/misc.py +++ b/voicemeeterlib/misc.py @@ -1,7 +1,12 @@ +from .error import VMError from .iremote import IRemote +from .kinds import kinds_all class FX(IRemote): + def __str__(self): + return f"{type(self).__name__}" + @property def identifier(self) -> str: return "FX" @@ -37,3 +42,188 @@ class FX(IRemote): @delay_ab.setter def delay_ab(self, val: bool): self.setter("delay.ab", 1 if val else 0) + + +class Patch(IRemote): + @classmethod + def make(cls, remote): + """ + Factory method for Patch. + + Mixes in required classes. + + Returns a Patch class of a kind. + """ + ASIO_cls = _make_asio_mixins(remote)[remote.kind.name] + return type( + f"Patch{remote.kind}", + (cls, ASIO_cls), + { + "composite": tuple(Composite(remote, i) for i in range(8)), + "insert": tuple(Insert(remote, i) for i in range(remote.kind.insert)), + }, + )(remote) + + def __str__(self): + return f"{type(self).__name__}" + + @property + def identifier(self) -> str: + return f"patch" + + @property + def postfadercomp(self) -> bool: + return self.getter("postfadercomposite") == 1 + + @postfadercomp.setter + def postfadercomp(self, val: bool): + self.setter("postfadercomposite", 1 if val else 0) + + @property + def postfxinsert(self) -> bool: + return self.getter("postfxinsert") == 1 + + @postfxinsert.setter + def postfxinsert(self, val: bool): + self.setter("postfxinsert", 1 if val else 0) + + +class Asio(IRemote): + @property + def identifier(self) -> str: + return f"patch" + + +class AsioIn(Asio): + def get(self) -> int: + return int(self.getter(f"asio{[self.index]}")) + + def set(self, val: int): + self.setter(f"asio{[self.index]}", val) + + +class AsioOut(Asio): + def __init__(self, remote, i, param): + IRemote.__init__(self, remote, i) + self._param = param + + def get(self) -> int: + return int(self.getter(f"out{self._param}{[self.index]}")) + + def set(self, val: int): + self.setter(f"out{self._param}{[self.index]}", val) + + +def _make_asio_mixin(remote, kind): + """Creates an ASIO mixin for a kind""" + asio_in, asio_out = kind.asio + + return type( + f"ASIO{kind}", + (IRemote,), + { + "asio": tuple(AsioIn(remote, i) for i in range(asio_in)), + **{ + param: tuple(AsioOut(remote, i, param) for i in range(asio_out)) + for param in ["A2", "A3", "A4", "A5"] + }, + }, + ) + + +def _make_asio_mixins(remote): + return {kind.name: _make_asio_mixin(remote, kind) for kind in kinds_all} + + +class Composite(IRemote): + @property + def identifier(self) -> str: + return f"patch" + + def get(self) -> int: + return int(self.getter(f"composite[{self.index}]")) + + def set(self, val: int): + self.setter(f"composite[{self.index}]", val) + + +class Insert(IRemote): + @property + def identifier(self) -> str: + return f"patch" + + @property + def on(self) -> bool: + return self.getter(f"insert[{self.index}]") == 1 + + @on.setter + def on(self, val: bool): + self.setter(f"insert[{self.index}]", 1 if val else 0) + + +class Option(IRemote): + @classmethod + def make(cls, remote): + """ + Factory method for Option. + + Mixes in required classes. + + Returns a Option class of a kind. + """ + return type( + f"Option{remote.kind}", + (cls,), + { + "delay": tuple(Delay(remote, i) for i in range(remote.kind.phys_out)), + }, + )(remote) + + def __str__(self): + return f"{type(self).__name__}" + + @property + def identifier(self) -> str: + return f"option" + + @property + def sr(self) -> int: + return int(self.getter("sr")) + + @sr.setter + def sr(self, val: int): + opts = (44100, 48000, 88200, 96000, 176400, 192000) + if val not in opts: + raise VMError(f"Expected one of: {opts}") + self.setter("sr", val) + + @property + def asiosr(self) -> bool: + return self.getter("asiosr") == 1 + + @asiosr.setter + def asiosr(self, val: bool): + self.setter("asiosr", 1 if val else 0) + + @property + def monitoronsel(self) -> bool: + return self.getter("monitoronsel") == 1 + + @monitoronsel.setter + def monitoronsel(self, val: bool): + self.setter("monitoronsel", 1 if val else 0) + + def buffer(self, driver, buffer): + self.setter(f"buffer.{driver}", buffer) + + +class Delay(IRemote): + @property + def identifier(self) -> str: + return f"option" + + def get(self) -> int: + return int(self.getter(f"delay[{self.index}]")) + + def set(self, val: int): + self.setter(f"delay[{self.index}]", val)