From f8f10e358f16e450741627a3a2c5a977a0d13a1c Mon Sep 17 00:00:00 2001 From: William Young Date: Sun, 15 Jun 2025 10:43:50 -0500 Subject: [PATCH 01/10] Initial setup adding classes for channels and cells --- examples/eq_edit/README.md | 9 ++++ examples/eq_edit/__main__.py | 21 ++++++++ voicemeeterlib/bus.py | 93 +++++++++++++++++++++++++++++++++++- voicemeeterlib/factory.py | 22 ++++++++- voicemeeterlib/iremote.py | 4 +- voicemeeterlib/kinds.py | 16 +++++++ 6 files changed, 162 insertions(+), 3 deletions(-) create mode 100644 examples/eq_edit/README.md create mode 100644 examples/eq_edit/__main__.py 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): From f702b4feb34ee1177ae8f4b1c2c9c458225141e4 Mon Sep 17 00:00:00 2001 From: William Young Date: Sun, 15 Jun 2025 11:48:17 -0500 Subject: [PATCH 02/10] Got rid of error with channels and cells not being subscriptable, but now getting -3 error trying to set eq.channel[0].cell[0].on --- voicemeeterlib/bus.py | 85 +++++++++++++++++++-------------------- voicemeeterlib/factory.py | 22 +--------- voicemeeterlib/iremote.py | 4 +- voicemeeterlib/kinds.py | 16 ++------ 4 files changed, 47 insertions(+), 80 deletions(-) diff --git a/voicemeeterlib/bus.py b/voicemeeterlib/bus.py index aa5b376..d56c098 100644 --- a/voicemeeterlib/bus.py +++ b/voicemeeterlib/bus.py @@ -88,6 +88,25 @@ class Bus(IRemote): class BusEQ(IRemote): + @classmethod + def make(cls, remote, i): + """ + Factory method for BusEQ. + + Returns a BusEQ class. + """ + kls = (cls,) + return type( + 'BusEQ', + kls, + { + 'channel': tuple( + BusEQCh.make(remote, j) + for j in range(remote.kind.channels) + ) + }, + ) + @property def identifier(self) -> str: return f'Bus[{self.index}].eq' @@ -109,15 +128,34 @@ class BusEQ(IRemote): self.setter('ab', 1 if val else 0) class BusEQCh(IRemote): + @classmethod + def make(cls, remote, i): + """ + Factory method for Bus EQ channel. + + Returns a BusEQCh class. + """ + kls = (cls,) + return type( + 'BusEQCh', + kls, + { + 'cell': tuple( + BusEQChCell(remote, k) + for k in range(remote.kind.cells) + ) + }, + ) + @property def identifier(self) -> str: - return f'Bus[{self.index}].eq.channel[{self.index}]' + return f'channel[{self.index}]' class BusEQChCell(IRemote): @property def identifier(self) -> str: - return f'Bus[{self.index}].eq.channel[{self.index}].cell[{self.kndex}]' + return f'cell[{self.index}]' @property def on(self) -> bool: @@ -300,20 +338,6 @@ 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.""" @@ -379,14 +403,13 @@ 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_cls, + 'eq': BusEQ.make(remote, i), }, )(remote, i) @@ -398,29 +421,3 @@ 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 e9c5fe1..21d9c2c 100644 --- a/voicemeeterlib/factory.py +++ b/voicemeeterlib/factory.py @@ -6,8 +6,6 @@ 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 @@ -32,7 +30,7 @@ class FactoryBuilder: BuilderProgress = IntEnum( 'BuilderProgress', - 'strip bus channels cells command macrobutton vban device option recorder patch fx', + 'strip bus command macrobutton vban device option recorder patch fx', start=0, ) @@ -42,8 +40,6 @@ 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}', @@ -74,20 +70,6 @@ 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 @@ -144,8 +126,6 @@ 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 da01333..7f67cf1 100644 --- a/voicemeeterlib/iremote.py +++ b/voicemeeterlib/iremote.py @@ -12,11 +12,9 @@ class IRemote(metaclass=ABCMeta): Provides some default implementation """ - def __init__(self, remote, index=None):#, jndex = None, kndex = None): + def __init__(self, remote, index=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 e4504cb..4e72d7e 100644 --- a/voicemeeterlib/kinds.py +++ b/voicemeeterlib/kinds.py @@ -65,14 +65,6 @@ 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() @@ -86,8 +78,8 @@ class BasicMap(KindMapClass): asio: tuple = (0, 0) insert: int = 0 composite: int = 0 - channels: int = 8 - cells: int = 7 + channels: int = 0 + cells: int = 0 @dataclass(frozen=True) @@ -110,8 +102,8 @@ class PotatoMap(KindMapClass): asio: tuple = (10, 8) insert: int = 34 composite: int = 8 - channels: int = 0 - cells: int = 0 + channels: int = 9 + cells: int = 7 def kind_factory(kind_id): From c7979124586044d4e5894cbdf73096b6fee6b99b Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 15 Jun 2025 20:02:08 +0100 Subject: [PATCH 03/10] set cell count to 6 (0 up to 5) --- voicemeeterlib/kinds.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/voicemeeterlib/kinds.py b/voicemeeterlib/kinds.py index 4e72d7e..475c689 100644 --- a/voicemeeterlib/kinds.py +++ b/voicemeeterlib/kinds.py @@ -91,7 +91,7 @@ class BananaMap(KindMapClass): insert: int = 22 composite: int = 8 channels: int = 9 - cells: int = 7 + cells: int = 6 @dataclass(frozen=True) @@ -103,7 +103,7 @@ class PotatoMap(KindMapClass): insert: int = 34 composite: int = 8 channels: int = 9 - cells: int = 7 + cells: int = 6 def kind_factory(kind_id): From 714d2fc972d902951ae228755a6608ae3cd01838 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 15 Jun 2025 20:03:11 +0100 Subject: [PATCH 04/10] pass channel + cell indices to each class update identifier properties to reflect changes. --- voicemeeterlib/bus.py | 49 +++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 20 deletions(-) diff --git a/voicemeeterlib/bus.py b/voicemeeterlib/bus.py index d56c098..2cb52b4 100644 --- a/voicemeeterlib/bus.py +++ b/voicemeeterlib/bus.py @@ -101,8 +101,7 @@ class BusEQ(IRemote): kls, { 'channel': tuple( - BusEQCh.make(remote, j) - for j in range(remote.kind.channels) + BusEQCh.make(remote, i, j) for j in range(remote.kind.channels) ) }, ) @@ -127,9 +126,10 @@ class BusEQ(IRemote): def ab(self, val: bool): self.setter('ab', 1 if val else 0) + class BusEQCh(IRemote): @classmethod - def make(cls, remote, i): + def make(cls, remote, i, j): """ Factory method for Bus EQ channel. @@ -141,62 +141,71 @@ class BusEQCh(IRemote): kls, { 'cell': tuple( - BusEQChCell(remote, k) - for k in range(remote.kind.cells) + BusEQChCell(remote, i, j, k) for k in range(remote.kind.cells) ) }, ) + def __init__(self, remote, i, j): + super().__init__(remote, i) + self.channel_index = j + @property def identifier(self) -> str: - return f'channel[{self.index}]' + return f'Bus[{self.index}].eq.channel[{self.channel_index}]' class BusEQChCell(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'cell[{self.index}]' - + return f'Bus[{self.index}].eq.channel[{self.channel_index}].cell[{self.cell_index}]' + @property def on(self) -> bool: return self.getter('on') == 1 - + @on.setter - def on(self,val: bool): + 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) - + return round(self.getter('f'), 1) + @f.setter - def f(self,val: float): + def f(self, val: float): self.setter('f', val) @property def gain(self) -> float: - return round(self.getter('gain'),1) - + return round(self.getter('gain'), 1) + @gain.setter - def gain(self,val: float): + def gain(self, val: float): self.setter('gain', val) @property def q(self) -> float: - return round(self.getter('q'),1) - + return round(self.getter('q'), 1) + @q.setter - def q(self,val: float): + def q(self, val: float): self.setter('q', val) + class PhysicalBus(Bus): @classmethod def make(cls, remote, i, kind): From abbbf57982f4754c9c29cd256b1fcb6cf4503457 Mon Sep 17 00:00:00 2001 From: William Young Date: Sun, 15 Jun 2025 15:43:41 -0500 Subject: [PATCH 05/10] Added some logic to test but changes seem to work now --- examples/eq_edit/__main__.py | 27 +++++++++++++++++++++------ 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/examples/eq_edit/__main__.py b/examples/eq_edit/__main__.py index 77df9f7..d85deb9 100644 --- a/examples/eq_edit/__main__.py +++ b/examples/eq_edit/__main__.py @@ -2,20 +2,35 @@ import time import voicemeeterlib +# Creates an array of channels you want to edit +# s is the channel to start on, n is how many channels to edit +def create_channel_array(s, n): + return list(range(s, s+n)) + def main(): KIND_ID = 'banana' with voicemeeterlib.api(KIND_ID) as vm: + print( 'Check getting values' ) + print( f'Bus[0].EQ.on: {vm.bus[0].eq.on}') + print( f'Bus[0].EQ.channel[0].cell[0].on: {vm.bus[0].eq.channel[0].cell[0].on}') + + print( 'Check sending commands (should affect your VM Banana window)') + channels_idx = create_channel_array(0, 2) 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 + vm.bus[0].eq.ab = 0 # corresponds to A EQ memory slot + vm.bus[0].mute = False + for i in channels_idx: + vm.bus[0].eq.channel[i].cell[0].on = True + vm.bus[0].eq.channel[i].cell[0].f = 500 + vm.bus[0].eq.channel[i].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 + for i in channels_idx: + vm.bus[0].eq.channel[i].cell[0].on = False + vm.bus[0].eq.channel[i].cell[0].f = 50 + vm.bus[0].eq.channel[i].cell[0].type = 0 if __name__ == '__main__': main() \ No newline at end of file From 4953751c022bf938c8c9a3f5b659d850648729c4 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 15 Jun 2025 22:32:46 +0100 Subject: [PATCH 06/10] instantiate types bump poethepoet --- pyproject.toml | 2 +- voicemeeterlib/bus.py | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9e716c3..5c37b67 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,7 +16,7 @@ dependencies = [ packages = [{ include = "voicemeeterlib" }] [tool.poetry.requires-plugins] -poethepoet = "^0.32.1" +poethepoet = "^0.35.0" [tool.poetry.group.dev.dependencies] pytest = "^8.3.4" diff --git a/voicemeeterlib/bus.py b/voicemeeterlib/bus.py index 2cb52b4..fb702c0 100644 --- a/voicemeeterlib/bus.py +++ b/voicemeeterlib/bus.py @@ -96,7 +96,7 @@ class BusEQ(IRemote): Returns a BusEQ class. """ kls = (cls,) - return type( + BusEQ_cls = type( 'BusEQ', kls, { @@ -105,6 +105,7 @@ class BusEQ(IRemote): ) }, ) + return BusEQ_cls(remote, i) @property def identifier(self) -> str: @@ -136,7 +137,7 @@ class BusEQCh(IRemote): Returns a BusEQCh class. """ kls = (cls,) - return type( + BusEQCh_cls = type( 'BusEQCh', kls, { @@ -145,6 +146,7 @@ class BusEQCh(IRemote): ) }, ) + return BusEQCh_cls(remote, i, j) def __init__(self, remote, i, j): super().__init__(remote, i) From fe1f4ee3243ee6180bc564cb9128022bbc69767c Mon Sep 17 00:00:00 2001 From: William Young Date: Sun, 15 Jun 2025 16:59:17 -0500 Subject: [PATCH 07/10] Updated example script to be sure other params work, updated readme and changed channel number from 9 to 8 --- README.md | 17 +++++++++++++++++ examples/eq_edit/__main__.py | 4 ++++ voicemeeterlib/kinds.py | 4 ++-- 3 files changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0c4f8da..fa01963 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,23 @@ example: vm.bus[3].eq.on = True ``` +##### Bus.EQ.Channel.Cell + +The following properties are available. + +- `on`: boolean +- `type`: int +- `f`: float +- `gain`: float +- `q`: quality + +example: + +```python +vm.bus[3].eq.channel[0].cell[2].on = True +vm.bus[3].eq.channel[0].cell[2].f = 5000 +``` + ##### Bus.Modes The following properties are available. diff --git a/examples/eq_edit/__main__.py b/examples/eq_edit/__main__.py index d85deb9..bedc13b 100644 --- a/examples/eq_edit/__main__.py +++ b/examples/eq_edit/__main__.py @@ -23,14 +23,18 @@ def main(): for i in channels_idx: vm.bus[0].eq.channel[i].cell[0].on = True vm.bus[0].eq.channel[i].cell[0].f = 500 + vm.bus[0].eq.channel[i].cell[0].gain = -10 vm.bus[0].eq.channel[i].cell[0].type = 3 # Should correspond to LPF + vm.bus[0].eq.channel[i].cell[0].q = 10 time.sleep(3) vm.bus[0].eq.on = False for i in channels_idx: vm.bus[0].eq.channel[i].cell[0].on = False vm.bus[0].eq.channel[i].cell[0].f = 50 + vm.bus[0].eq.channel[i].cell[0].gain = 0 vm.bus[0].eq.channel[i].cell[0].type = 0 + vm.bus[0].eq.channel[i].cell[0].q = 3 if __name__ == '__main__': main() \ No newline at end of file diff --git a/voicemeeterlib/kinds.py b/voicemeeterlib/kinds.py index 475c689..ab73065 100644 --- a/voicemeeterlib/kinds.py +++ b/voicemeeterlib/kinds.py @@ -90,7 +90,7 @@ class BananaMap(KindMapClass): asio: tuple = (6, 8) insert: int = 22 composite: int = 8 - channels: int = 9 + channels: int = 8 cells: int = 6 @@ -102,7 +102,7 @@ class PotatoMap(KindMapClass): asio: tuple = (10, 8) insert: int = 34 composite: int = 8 - channels: int = 9 + channels: int = 8 cells: int = 6 From 1ff2017d5149ff98e31acbd21b2a5e7aeeb088d9 Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 15 Jun 2025 23:32:47 +0100 Subject: [PATCH 08/10] iterate over cells. --- examples/eq_edit/__main__.py | 64 +++++++++++++++++++++--------------- 1 file changed, 37 insertions(+), 27 deletions(-) diff --git a/examples/eq_edit/__main__.py b/examples/eq_edit/__main__.py index bedc13b..51e5ce9 100644 --- a/examples/eq_edit/__main__.py +++ b/examples/eq_edit/__main__.py @@ -2,39 +2,49 @@ import time import voicemeeterlib -# Creates an array of channels you want to edit -# s is the channel to start on, n is how many channels to edit -def create_channel_array(s, n): - return list(range(s, s+n)) def main(): KIND_ID = 'banana' + BUS_INDEX = 0 # Index of the bus to edit, can be changed as needed + CHANNEL_INDEX = 0 # Index of the channel to edit, can be changed as needed with voicemeeterlib.api(KIND_ID) as vm: - print( 'Check getting values' ) - print( f'Bus[0].EQ.on: {vm.bus[0].eq.on}') - print( f'Bus[0].EQ.channel[0].cell[0].on: {vm.bus[0].eq.channel[0].cell[0].on}') + print(f'Bus[{BUS_INDEX}].EQ.on: {vm.bus[BUS_INDEX].eq.on}') + print( + f'Bus[{BUS_INDEX}].EQ.channel[{CHANNEL_INDEX}].cell[0].on: {vm.bus[BUS_INDEX].eq.channel[CHANNEL_INDEX].cell[0].on}' + ) - print( 'Check sending commands (should affect your VM Banana window)') - channels_idx = create_channel_array(0, 2) - vm.bus[0].eq.on = True - vm.bus[0].eq.ab = 0 # corresponds to A EQ memory slot - vm.bus[0].mute = False - for i in channels_idx: - vm.bus[0].eq.channel[i].cell[0].on = True - vm.bus[0].eq.channel[i].cell[0].f = 500 - vm.bus[0].eq.channel[i].cell[0].gain = -10 - vm.bus[0].eq.channel[i].cell[0].type = 3 # Should correspond to LPF - vm.bus[0].eq.channel[i].cell[0].q = 10 + print('Check sending commands (should affect your VM Banana window)') + + vm.bus[BUS_INDEX].eq.on = True + vm.bus[BUS_INDEX].eq.ab = 0 # corresponds to A EQ memory slot + vm.bus[BUS_INDEX].mute = False + + for j, cell in enumerate(vm.bus[BUS_INDEX].eq.channel[CHANNEL_INDEX].cell): + cell.on = True + cell.f = 500 + cell.gain = -10 + cell.type = 3 # Should correspond to LPF + cell.q = 10 + + print( + f'Channel {CHANNEL_INDEX}, Cell {j}: on={cell.on}, f={cell.f}, type={cell.type}, gain={cell.gain}, q={cell.q}' + ) + + time.sleep(1) # Sleep to simulate processing time + + cell.on = False + cell.f = 50 + cell.gain = 0 + cell.type = 0 + cell.q = 3 + + print( + f'Channel {CHANNEL_INDEX}, Cell {j}: on={cell.on}, f={cell.f}, type={cell.type} , gain={cell.gain}, q={cell.q}' + ) + + vm.bus[BUS_INDEX].eq.on = False - time.sleep(3) - vm.bus[0].eq.on = False - for i in channels_idx: - vm.bus[0].eq.channel[i].cell[0].on = False - vm.bus[0].eq.channel[i].cell[0].f = 50 - vm.bus[0].eq.channel[i].cell[0].gain = 0 - vm.bus[0].eq.channel[i].cell[0].type = 0 - vm.bus[0].eq.channel[i].cell[0].q = 3 if __name__ == '__main__': - main() \ No newline at end of file + main() From 7232ba62486aa08a5bc4a6ec303420e701b3bb9e Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 15 Jun 2025 23:38:11 +0100 Subject: [PATCH 09/10] add eqedit poe script minor bump --- pyproject.toml | 3 ++- scripts.py | 5 +++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 5c37b67..1ec628f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "voicemeeter-api" -version = "2.6.1" +version = "2.7.0" description = "A Python wrapper for the Voiceemeter API" authors = [ {name = "Onyx and Iris",email = "code@onyxandiris.online"} @@ -37,6 +37,7 @@ levels.script = "scripts:ex_levels" midi.script = "scripts:ex_midi" obs.script = "scripts:ex_obs" observer.script = "scripts:ex_observer" +eqedit.script = "scripts:ex_eqedit" test-basic.script = "scripts:test_basic" test-banana.script = "scripts:test_banana" test-potato.script = "scripts:test_potato" diff --git a/scripts.py b/scripts.py index 821bcff..76e9aa6 100644 --- a/scripts.py +++ b/scripts.py @@ -37,6 +37,11 @@ def ex_observer(): subprocess.run([sys.executable, str(scriptpath)]) +def ex_eqedit(): + scriptpath = Path.cwd() / 'examples' / 'eq_edit' / '.' + subprocess.run([sys.executable, str(scriptpath)]) + + def test_basic(): subprocess.run(['tox'], env=os.environ.copy() | {'KIND': 'basic'}) From 1509afd4f5c4dba8188067ea1a16d8c2eb70282c Mon Sep 17 00:00:00 2001 From: onyx-and-iris Date: Sun, 15 Jun 2025 23:42:23 +0100 Subject: [PATCH 10/10] add 2.7.0 to CHANGELOG --- CHANGELOG.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae3f5c6..0845b97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,13 @@ Before any major/minor/patch bump all unit tests will be run to verify they pass - [x] +## [2.7.0] - 2025-06-15 + +### Added + +- Bus.EQ Channel Cell commands added, see [Bus.EQ.Channel.Cell](). + - Added by [PR #16](https://github.com/onyx-and-iris/voicemeeter-api-python/pull/16) + ## [2.6.0] - 2024-06-29 ### Added