diff --git a/_profiles/banana/config.toml b/_profiles/banana/config.toml new file mode 100644 index 0000000..cb9f040 --- /dev/null +++ b/_profiles/banana/config.toml @@ -0,0 +1,36 @@ +extends = 'blank' +[strip-0] +limit = 12 +label = "" + +[strip-1] +A1 = true +A2 = true +A3 = true +B1 = true +B2 = true +mono = true +solo = true +mute = true +limit = 12 +label = "" + +[strip-2] +limit = 12 +label = "" + +[strip-3] +limit = 12 +label = "" + +[strip-4] +A1 = false +A2 = false +A3 = false +B1 = false +B2 = false +k = false +solo = false +mute = false +limit = 12 +label = "" diff --git a/_profiles/basic/config.toml b/_profiles/basic/config.toml new file mode 100644 index 0000000..aa7fd08 --- /dev/null +++ b/_profiles/basic/config.toml @@ -0,0 +1,18 @@ +extends = 'blank' +[strip-0] +A1 = true +B1 = true +mono = true +solo = true +mute = true +limit = 12 +label = "" + +[strip-1] +A1 = false +B1 = false +mono = false +solo = false +mute = false +limit = 12 +label = "" diff --git a/_profiles/potato/config.toml b/_profiles/potato/config.toml new file mode 100644 index 0000000..ea18167 --- /dev/null +++ b/_profiles/potato/config.toml @@ -0,0 +1,43 @@ +extends = 'blank' +[strip-0] +limit = 12 +label = "" + +[strip-1] +limit = 12 +label = "" + +[strip-2] +A1 = true +A2 = true +A3 = true +A4 = true +A5 = true +B1 = true +B2 = true +B3 = true +mono = true +solo = true +mute = true +limit = 12 +label = "" + +[strip-3] +limit = 12 +label = "" + +[strip-4] +limit = 12 +label = "" + +[strip-5] +limit = 12 +label = "" + +[strip-6] +limit = 12 +label = "" + +[strip-7] +limit = 12 +label = "" diff --git a/setup.py b/setup.py index abd957b..facca01 100644 --- a/setup.py +++ b/setup.py @@ -6,6 +6,7 @@ setup( description='VBAN CMD Python API', packages=['vbancmd'], install_requires=[ + 'toml' ], extras_require={ 'development': [ diff --git a/vbancmd/profiles.py b/vbancmd/profiles.py new file mode 100644 index 0000000..b09f250 --- /dev/null +++ b/vbancmd/profiles.py @@ -0,0 +1,77 @@ +import toml +from . import kinds +from .util import project_path +from pathlib import Path + +profiles = {} + +def _make_blank_profile(kind): + phys_in, virt_in = kind.ins + phys_out, virt_out = kind.outs + all_input_strip_config = { + 'gain': 0.0, + 'solo': False, + 'mute': False, + 'mono': False, + **{f'A{i}': False for i in range(1, phys_out+1)}, + **{f'B{i}': False for i in range(1, virt_out+1)}, + } + phys_input_strip_config={ + 'comp': 0.0, + 'gate': 0.0, + } + output_bus_config = { + 'gain': 0.0, + 'eq': False, + 'mute': False, + 'mono': False, + } + all_ = {f'strip-{i}': all_input_strip_config for i in range(phys_in+virt_in)} + phys = {f'strip-{i}': phys_input_strip_config for i in range(phys_in)} + abc = all_ + for i in phys.keys(): + abc[i] = all_[i] | phys[i] + return { + **abc, + **{f'bus-{i}': output_bus_config for i in range(phys_out+virt_out)} + } + +def _make_base_profile(kind): + phys_in, virt_in = kind.ins + blank = _make_blank_profile(kind) + overrides = { + **{f'strip-{i}': dict(B1=True) for i in range(phys_in)}, + **{f'strip-{i}': dict(A1=True) for i in range(phys_in, phys_in+virt_in)} + } + base = blank + for i in overrides.keys(): + base[i] = blank[i] | overrides[i] + return base + +for kind in kinds.all: + profiles[kind.id] = { + 'blank': _make_blank_profile(kind), + 'base': _make_base_profile(kind) + } + +# Load profiles from config files in profiles//.toml +for kind in kinds.all: + profiles_paths = [ + Path(project_path()) / 'profiles' / kind.id, + Path.cwd() / 'profiles' / kind.id, + Path.home() / 'Documents/Voicemeeter' / 'profiles' / kind.id, + ] + for path in profiles_paths: + if path.is_dir(): + filenames = list(path.glob('*.toml')) + configs = {} + for filename in filenames: + name = filename.with_suffix('').stem + try: + configs[name] = toml.load(filename) + except toml.TomlDecodeError: + print(f'Invalid TOML profile: {kind.id}/{filename.stem}') + + for name, cfg in configs.items(): + print(f'Loaded profile {kind.id}/{name}') + profiles[kind.id][name] = cfg diff --git a/vbancmd/util.py b/vbancmd/util.py index 5344f50..202f8c4 100644 --- a/vbancmd/util.py +++ b/vbancmd/util.py @@ -1,3 +1,10 @@ +from pathlib import Path + +PROJECT_DIR = str(Path(__file__).parents[1]) + +def project_path(): + return PROJECT_DIR + def cache(func): """ check if recently cached was an updated value """ def wrapper(*args, **kwargs): diff --git a/vbancmd/vbancmd.py b/vbancmd/vbancmd.py index 18e73f8..704653d 100644 --- a/vbancmd/vbancmd.py +++ b/vbancmd/vbancmd.py @@ -7,6 +7,7 @@ from typing import NamedTuple, NoReturn, Optional, Union from .errors import VMCMDErrors from . import kinds +from . import profiles from .dataclass import ( HEADER_SIZE, VBAN_VMRT_Packet_Data, @@ -59,6 +60,22 @@ class VbanCmd(abc.ABC): self.login() return self + def login(self): + """ + Start listening for RT Packets + + Start background threads: + + Register to RT service + Keep public packet updated. + """ + self._rt_packet_socket.bind((socket.gethostbyname(socket.gethostname()), self._port)) + worker = Thread(target=self._send_register_rt, daemon=True) + worker.start() + self._public_packet = self._get_rt() + worker2 = Thread(target=self._keepupdated, daemon=True) + worker2.start() + def _send_register_rt(self): """ Continuously register to the RT Packet Service @@ -154,7 +171,7 @@ class VbanCmd(abc.ABC): count = int.from_bytes(self._text_header.framecounter, 'little') + 1 self._text_header.framecounter = count.to_bytes(4, 'little') self.cache[f'{id_}.{param}'] = [val, True] - sleep(0.018) + sleep(self._ratelimiter) def sendtext(self, cmd): """ Sends a multiple parameter string over a network. """ @@ -197,21 +214,24 @@ class VbanCmd(abc.ABC): raise ValueError(obj) target.apply(submapping) - def login(self): - """ - Start listening for RT Packets + def apply_profile(self, name: str): + try: + profile = self.profiles[name] + if 'extends' in profile: + base = self.profiles[profile['extends']] + del profile['extends'] + for key in profile.keys(): + if key in base: + base[key] |= profile[key] + else: + base[key] = profile[key] + profile = base + self.apply(profile) + except KeyError: + raise VMCMDErrors(f'Unknown profile: {self.kind.id}/{name}') - Start background threads: - - Register to RT service - Keep public packet updated. - """ - self._rt_packet_socket.bind((socket.gethostbyname(socket.gethostname()), self._port)) - worker = Thread(target=self._send_register_rt, daemon=True) - worker.start() - self._public_packet = self._get_rt() - worker2 = Thread(target=self._keepupdated, daemon=True) - worker2.start() + def reset(self) -> NoReturn: + self.apply_profile('base') def logout(self): """ sets thread flag, closes sockets """ @@ -250,8 +270,12 @@ def _make_remote(kind: NamedTuple) -> VbanCmd: for i in range(self.phys_out + self.virt_out)) self.command = Command.make(self) + def get_profiles(self): + return profiles.profiles[kind.id] + return type(f'VbanCmd{kind.name}', (VbanCmd,), { '__init__': init, + 'profiles': property(get_profiles) }) _remotes = {kind.id: _make_remote(kind) for kind in kinds.all}