mirror of
https://github.com/onyx-and-iris/voicemeeter-api-python.git
synced 2024-11-24 20:00:47 +00:00
212 lines
5.7 KiB
Python
212 lines
5.7 KiB
Python
import itertools
|
|
import logging
|
|
from pathlib import Path
|
|
|
|
from .error import VMError
|
|
|
|
try:
|
|
import tomllib
|
|
except ModuleNotFoundError:
|
|
import tomli as tomllib
|
|
|
|
from .kinds import request_kind_map as kindmap
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class TOMLStrBuilder:
|
|
"""builds a config profile, as a string, for the toml parser"""
|
|
|
|
def __init__(self, kind):
|
|
self.kind = kind
|
|
self.higher = itertools.chain(
|
|
[f"strip-{i}" for i in range(kind.num_strip)],
|
|
[f"bus-{i}" for i in range(kind.num_bus)],
|
|
)
|
|
|
|
def init_config(self, profile=None):
|
|
self.virt_strip_params = (
|
|
[
|
|
"mute = false",
|
|
"mono = false",
|
|
"solo = false",
|
|
"gain = 0.0",
|
|
]
|
|
+ [f"A{i} = false" for i in range(1, self.kind.phys_out + 1)]
|
|
+ [f"B{i} = false" for i in range(1, self.kind.virt_out + 1)]
|
|
)
|
|
self.phys_strip_params = self.virt_strip_params + [
|
|
"comp.knob = 0.0",
|
|
"gate.knob = 0.0",
|
|
"denoiser.knob = 0.0",
|
|
"eq.on = false",
|
|
]
|
|
self.bus_params = [
|
|
"mono = false",
|
|
"eq.on = false",
|
|
"mute = false",
|
|
"gain = 0.0",
|
|
]
|
|
|
|
if profile == "reset":
|
|
self.reset_config()
|
|
|
|
def reset_config(self):
|
|
self.phys_strip_params = list(
|
|
map(lambda x: x.replace("B1 = false", "B1 = true"), self.phys_strip_params)
|
|
)
|
|
self.virt_strip_params = list(
|
|
map(lambda x: x.replace("A1 = false", "A1 = true"), self.virt_strip_params)
|
|
)
|
|
|
|
def build(self, profile="reset"):
|
|
self.init_config(profile)
|
|
toml_str = str()
|
|
for eachclass in self.higher:
|
|
toml_str += f"[{eachclass}]\n"
|
|
toml_str = self.join(eachclass, toml_str)
|
|
return toml_str
|
|
|
|
def join(self, eachclass, toml_str):
|
|
kls, index = eachclass.split("-")
|
|
match kls:
|
|
case "strip":
|
|
toml_str += ("\n").join(
|
|
self.phys_strip_params
|
|
if int(index) < self.kind.phys_in
|
|
else self.virt_strip_params
|
|
)
|
|
case "bus":
|
|
toml_str += ("\n").join(self.bus_params)
|
|
case _:
|
|
pass
|
|
return toml_str + "\n"
|
|
|
|
|
|
class TOMLDataExtractor:
|
|
def __init__(self, file):
|
|
with open(file, "rb") as f:
|
|
self._data = tomllib.load(f)
|
|
|
|
@property
|
|
def data(self):
|
|
return self._data
|
|
|
|
@data.setter
|
|
def data(self, value):
|
|
self._data = value
|
|
|
|
|
|
def dataextraction_factory(file):
|
|
"""
|
|
factory function for parser
|
|
|
|
this opens the possibility for other parsers to be added
|
|
"""
|
|
if file.suffix == ".toml":
|
|
extractor = TOMLDataExtractor
|
|
else:
|
|
raise ValueError("Cannot extract data from {}".format(file))
|
|
return extractor(file)
|
|
|
|
|
|
class SingletonType(type):
|
|
"""ensure only a single instance of Loader object"""
|
|
|
|
_instances = {}
|
|
|
|
def __call__(cls, *args, **kwargs):
|
|
if cls not in cls._instances:
|
|
cls._instances[cls] = super(SingletonType, cls).__call__(*args, **kwargs)
|
|
return cls._instances[cls]
|
|
|
|
|
|
class Loader(metaclass=SingletonType):
|
|
"""
|
|
invokes the parser
|
|
|
|
checks if config already in memory
|
|
|
|
loads data into memory if not found
|
|
"""
|
|
|
|
def __init__(self, kind):
|
|
self._kind = kind
|
|
self.logger = logger.getChild(self.__class__.__name__)
|
|
self._configs = dict()
|
|
self.defaults(kind)
|
|
self.parser = None
|
|
|
|
def defaults(self, kind):
|
|
self.builder = TOMLStrBuilder(kind)
|
|
toml_str = self.builder.build()
|
|
self.register("reset", tomllib.loads(toml_str))
|
|
|
|
def parse(self, identifier, data):
|
|
if identifier in self._configs:
|
|
self.logger.info(
|
|
f"config file with name {identifier} already in memory, skipping.."
|
|
)
|
|
return
|
|
try:
|
|
self.parser = dataextraction_factory(data)
|
|
except tomllib.TOMLDecodeError as e:
|
|
ERR_MSG = (str(e), f"When attempting to load {identifier}.toml")
|
|
self.logger.error(f"{type(e).__name__}: {' '.join(ERR_MSG)}")
|
|
return
|
|
return True
|
|
|
|
def register(self, identifier, data=None):
|
|
self._configs[identifier] = data if data else self.parser.data
|
|
self.logger.info(f"config {self.name}/{identifier} loaded into memory")
|
|
|
|
def deregister(self):
|
|
self._configs.clear()
|
|
self.defaults(self._kind)
|
|
|
|
@property
|
|
def configs(self):
|
|
return self._configs
|
|
|
|
@property
|
|
def name(self):
|
|
return self._kind.name
|
|
|
|
|
|
def loader(kind):
|
|
"""
|
|
traverses defined paths for config files
|
|
|
|
directs the loader
|
|
|
|
returns configs loaded into memory
|
|
"""
|
|
logger_loader = logger.getChild("loader")
|
|
loader = Loader(kind)
|
|
|
|
for path in (
|
|
Path.cwd() / "configs" / kind.name,
|
|
Path.home() / ".config" / "voicemeeter" / kind.name,
|
|
Path.home() / "Documents" / "Voicemeeter" / "configs" / kind.name,
|
|
):
|
|
if path.is_dir():
|
|
logger_loader.info(f"Checking [{path}] for TOML config files:")
|
|
for file in path.glob("*.toml"):
|
|
identifier = file.with_suffix("").stem
|
|
if loader.parse(identifier, file):
|
|
loader.register(identifier)
|
|
return loader.configs
|
|
|
|
|
|
def request_config(kind_id: str):
|
|
"""
|
|
config entry point.
|
|
|
|
Returns all configs loaded into memory for a kind
|
|
"""
|
|
try:
|
|
configs = loader(kindmap(kind_id))
|
|
except KeyError as e:
|
|
raise VMError(f"Unknown Voicemeeter kind {kind_id}") from e
|
|
return configs
|