diff --git a/examples/eq_edit/README.md b/examples/eq_edit/README.md new file mode 100644 index 0000000..04af6a9 --- /dev/null +++ b/examples/eq_edit/README.md @@ -0,0 +1,9 @@ +## About + +The purpose of this script is to demonstratehow to utilize the channels and cells that are available as part of the EQ. It should take audio playing in the second virtual strip and then apply a LGF on the first physical at 500 Hz. + +## Use + +Configured for banana version. + +Make sure you are playing audio into the second virtual strip and out of the first physical bus, both channels are unmuted and that you aren't monitoring another mixbus. Then run the script. diff --git a/examples/eq_edit/__main__.py b/examples/eq_edit/__main__.py new file mode 100644 index 0000000..77df9f7 --- /dev/null +++ b/examples/eq_edit/__main__.py @@ -0,0 +1,21 @@ +import time + +import voicemeeterlib + +def main(): + KIND_ID = 'banana' + + with voicemeeterlib.api(KIND_ID) as vm: + vm.bus[0].eq.on = True + vm.bus[0].eq.channel[0].cell[0].on = True + vm.bus[0].eq.channel[0].cell[0].f = 500 + vm.bus[0].eq.channel[0].cell[0].type = 3 # Should correspond to LPF + + time.sleep(3) + vm.bus[0].eq.on = False + vm.bus[0].eq.channel[0].cell[0].on = False + vm.bus[0].eq.channel[0].cell[0].f = 50 + vm.bus[0].eq.channel[0].cell[0].type = 0 + +if __name__ == '__main__': + main() \ No newline at end of file diff --git a/voicemeeterlib/bus.py b/voicemeeterlib/bus.py index 17654ea..aa5b376 100644 --- a/voicemeeterlib/bus.py +++ b/voicemeeterlib/bus.py @@ -108,6 +108,56 @@ class BusEQ(IRemote): def ab(self, val: bool): self.setter('ab', 1 if val else 0) +class BusEQCh(IRemote): + @property + def identifier(self) -> str: + return f'Bus[{self.index}].eq.channel[{self.index}]' + + +class BusEQChCell(IRemote): + @property + def identifier(self) -> str: + return f'Bus[{self.index}].eq.channel[{self.index}].cell[{self.kndex}]' + + @property + def on(self) -> bool: + return self.getter('on') == 1 + + @on.setter + def on(self,val: bool): + self.setter('on', 1 if val else 0) + + @property + def type(self) -> int: + return int(self.getter('type')) + + @type.setter + def type(self, val: int): + self.setter('type', val) + + @property + def f(self) -> float: + return round(self.getter('f'),1) + + @f.setter + def f(self,val: float): + self.setter('f', val) + + @property + def gain(self) -> float: + return round(self.getter('gain'),1) + + @gain.setter + def gain(self,val: float): + self.setter('gain', val) + + @property + def q(self) -> float: + return round(self.getter('q'),1) + + @q.setter + def q(self,val: float): + self.setter('q', val) class PhysicalBus(Bus): @classmethod @@ -250,6 +300,20 @@ def make_bus_level_map(kind): _make_bus_level_maps = {kind.name: make_bus_level_map(kind) for kind in kinds.all} +def _make_bus_eq(remote, i): + """ + Factory function for bus.eq. + + Returns a BusEQ class of a kind. + """ + return type( + 'BusEQ', + (), + { + 'channel': BusEQCh(remote, i) + }, + ) + def _make_bus_mode_mixin(): """Creates a mixin of Bus Modes.""" @@ -315,13 +379,14 @@ def bus_factory(is_phys_bus, remote, i) -> Union[PhysicalBus, VirtualBus]: else VirtualBus.make(remote, i, remote.kind) ) BUSMODEMIXIN_cls = _make_bus_mode_mixin() + BUSEQ_cls = _make_bus_eq(remote, i) return type( f'{BUS_cls.__name__}{remote.kind}', (BUS_cls,), { 'levels': BusLevel(remote, i), 'mode': BUSMODEMIXIN_cls(remote, i), - 'eq': BusEQ(remote, i), + 'eq': BUSEQ_cls, }, )(remote, i) @@ -333,3 +398,29 @@ def request_bus_obj(phys_bus, remote, i) -> Bus: Returns a reference to a bus subclass of a kind """ return bus_factory(phys_bus, remote, i) + +def request_busCh_obj(channel, remote, i) -> BusEQCh: + """ + Bus EQ Channel entry point. Wraps factory method + + Returns a reference to a bus EQ channel subcless of a kind + """ + kls = () + BusEQChCls = type('channel',kls,{}) + return type( + f'{BusEQChCls.__name__}{remote.kind}', + (BusEQChCls,){}, + )() + +def request_busChCe_obj(cell, remote, i) -> BusEQChCell: + """ + Bus EQ Channel Cell entry point. Wraps factory method + + Returns a reference to a bus EQ channel cell subcless of a kind + """ + kls = () + BusEQCellCls = type('cell',kls,{}) + return type( + f'{BusEQCellCls.__name__}{remote.kind}', + (BusEQCellCls,){}, + )() diff --git a/voicemeeterlib/factory.py b/voicemeeterlib/factory.py index 21d9c2c..e9c5fe1 100644 --- a/voicemeeterlib/factory.py +++ b/voicemeeterlib/factory.py @@ -6,6 +6,8 @@ from typing import Iterable from . import misc from .bus import request_bus_obj as bus +from .bus import request_busCh_obj as channel +from .bus import request_busChCe_obj as cell from .command import Command from .config import request_config as configs from .device import Device @@ -30,7 +32,7 @@ class FactoryBuilder: BuilderProgress = IntEnum( 'BuilderProgress', - 'strip bus command macrobutton vban device option recorder patch fx', + 'strip bus channels cells command macrobutton vban device option recorder patch fx', start=0, ) @@ -40,6 +42,8 @@ class FactoryBuilder: self._info = ( f'Finished building strips for {self._factory}', f'Finished building buses for {self._factory}', + f'Finished building channels for {self._factory}', + f'Finished building cells for {self._factory}', f'Finished building commands for {self._factory}', f'Finished building macrobuttons for {self._factory}', f'Finished building vban in/out streams for {self._factory}', @@ -70,6 +74,20 @@ class FactoryBuilder: ) return self + def make_channels(self): + self._factory.channels = tuple( + channel(i < self.kind.channels, self._factory, i) + for i in range(self.kind.channels) + ) + return self + + def make_cells(self): + self._factory.cells = tuple( + cell(i < self.kind.cells, self._factory, i) + for i in range(self.kind.cells) + ) + return self + def make_command(self): self._factory.command = Command.make(self._factory) return self @@ -126,6 +144,8 @@ class FactoryBase(Remote): self._steps = ( self.builder.make_strip, self.builder.make_bus, + self.builder.make_channels, + self.builder.make_cells, self.builder.make_command, self.builder.make_macrobutton, self.builder.make_vban, diff --git a/voicemeeterlib/iremote.py b/voicemeeterlib/iremote.py index 7f67cf1..da01333 100644 --- a/voicemeeterlib/iremote.py +++ b/voicemeeterlib/iremote.py @@ -12,9 +12,11 @@ class IRemote(metaclass=ABCMeta): Provides some default implementation """ - def __init__(self, remote, index=None): + def __init__(self, remote, index=None):#, jndex = None, kndex = None): self._remote = remote self.index = index + #self.jndex = jndex + #self.kndex = kndex self.logger = logger.getChild(self.__class__.__name__) def getter(self, param, **kwargs): diff --git a/voicemeeterlib/kinds.py b/voicemeeterlib/kinds.py index 93f3cfe..e4504cb 100644 --- a/voicemeeterlib/kinds.py +++ b/voicemeeterlib/kinds.py @@ -31,6 +31,8 @@ class KindMapClass(metaclass=SingletonType): asio: tuple insert: int composite: int + channels: int + cells: int @property def phys_in(self) -> int: @@ -63,6 +65,14 @@ class KindMapClass(metaclass=SingletonType): @property def num_bus_levels(self) -> int: return 8 * (self.phys_out + self.virt_out) + + @property + def num_bus_channels(self) -> int: + return self.channels + + @property + def num_bus_cells(self) -> int: + return self.cells def __str__(self) -> str: return self.name.capitalize() @@ -76,6 +86,8 @@ class BasicMap(KindMapClass): asio: tuple = (0, 0) insert: int = 0 composite: int = 0 + channels: int = 8 + cells: int = 7 @dataclass(frozen=True) @@ -86,6 +98,8 @@ class BananaMap(KindMapClass): asio: tuple = (6, 8) insert: int = 22 composite: int = 8 + channels: int = 9 + cells: int = 7 @dataclass(frozen=True) @@ -96,6 +110,8 @@ class PotatoMap(KindMapClass): asio: tuple = (10, 8) insert: int = 34 composite: int = 8 + channels: int = 0 + cells: int = 0 def kind_factory(kind_id):