From 464830df370de71a6a82e360eadd9b6cded6e6c7 Mon Sep 17 00:00:00 2001 From: Jason Gillman Jr Date: Sun, 21 Feb 2021 23:49:51 -0500 Subject: [PATCH] v0.02 --- .gitignore | 3 +- CHANGELOG.md | 5 ++ bankify.py | 183 ++-------------------------------------------- vital/__init__.py | 160 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+), 176 deletions(-) create mode 100644 vital/__init__.py diff --git a/.gitignore b/.gitignore index 0cffcb3..85b121f 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ -config.json \ No newline at end of file +config.json +__pycache__ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b48667b..85d5f97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ Changelog ========= +## v0.02 +* Skins now supported +* Refactor + * Created module + * Abstract `VitalObject` class created ## v0.01 * The first version. diff --git a/bankify.py b/bankify.py index e73a226..583bec8 100644 --- a/bankify.py +++ b/bankify.py @@ -1,177 +1,10 @@ from __future__ import annotations from pathlib import Path -from typing import Dict, Iterable, Optional, Tuple, Union -from zipfile import ZipFile, ZIP_DEFLATED +from typing import Dict, Optional, Tuple +from vital import Bank, VitalObject import json -class Lfo: - """ - A Vital LFO - """ - EXTENSION = 'vitallfo' - DIR = 'LFOs' - - @classmethod - def from_file(cls, filepath: Path) -> Lfo: - """ - Create an LFO from a file - :param filepath: - :return: - """ - with filepath.open('r') as f: - return cls(data=json.load(f), filepath=filepath) - - def __str__(self): - return self.name - - def __repr__(self): - return str(self) - - def __init__(self, data: dict, filepath: Optional[Path]): - self._data = data - self.original_path = filepath - self.name = self._data['name'] - - def rename(self, new_name: str): - """ - Rename - - :param new_name: - :return: - """ - self._data['name'] = new_name - self.name = self._data['name'] - - -class Preset: - """ - A Vital Preset - """ - EXTENSION = 'vital' - DIR = 'Presets' - - @classmethod - def from_file(cls, filepath: Path) -> Preset: - """ - Create an Preset from a file - :param filepath: - :return: - """ - with filepath.open('r') as f: - return cls(data=json.load(f), filepath=filepath) - - def __str__(self): - return self.name - - def __repr__(self): - return str(self) - - def __init__(self, data: dict, filepath: Optional[Path], **kwargs): - self._data = data - self.original_path = filepath - - if filepath is not None: # Differs because it appears the name comes from the file - self.name = filepath.stem - else: - self.name = kwargs['name'] - - def rename(self, new_name: str): - """ - Rename - - :param new_name: - :return: - """ - self.name = new_name - - -class Wavetable: - """ - A Vital Wavetable - """ - EXTENSION = 'vitaltable' - DIR = 'Wavetables' - - @classmethod - def from_file(cls, filepath: Path) -> Wavetable: - """ - Create an LFO from a file - :param filepath: - :return: - """ - with filepath.open('r') as f: - return cls(data=json.load(f), filepath=filepath) - - def __str__(self): - return self.name - - def __repr__(self): - return str(self) - - def __init__(self, data: dict, filepath: Optional[Path]): - self._data = data - self.original_path = filepath - self.name = self._data['name'] - - def rename(self, new_name: str): - """ - Rename - - :param new_name: - :return: - """ - self._data['name'] = new_name - self.name = self._data['name'] - - -class Bank: - """ - Represents a bank comprised of LFOs, Presets, and Wavetables - """ - EXTENSION = 'vitalbank' - - def __str__(self): - return self.name - - def __repr__(self): - return str(self) - - def __init__(self, bank_name: str, lfos: Optional[Iterable[Lfo]], presets: Optional[Iterable[Preset]], - wavetables: Optional[Iterable[Wavetable]]): - self.name = bank_name - self.elements = { - Lfo: {lfo.name: lfo for lfo in lfos} if lfos is not None else list(), - Preset: {preset.name: preset for preset in presets} if presets is not None else list(), - Wavetable: {wavetable.name: wavetable for wavetable in wavetables} if wavetables is not None else list(), - } - - def write_file(self, filedir: Path): - """ - Write the bank file to disk - - :param filedir: The directory to write to - :return: - """ - filepath = filedir.joinpath(f'{self.name}.{Bank.EXTENSION}') - - bankfile = ZipFile(file=filepath, mode='w', compression=ZIP_DEFLATED, compresslevel=9) - - for ct, ctd in self.elements.items(): - zdir = f'{self.name}/{ct.DIR}' - for i in ctd: - zfile = f'{zdir}/{i.name}.{ct.EXTENSION}' - bankfile.writestr(zinfo_or_arcname=zfile, data=json.dumps(i._data)) - - bankfile.close() - - -TYPE_DATA = { - Lfo: ('LFOs', 'vitallfo'), - Preset: ('Presets', 'vital'), - Wavetable: ('Wavetables', 'vitaltable'), -} - CONFIG_FILE = Path('config.json') with CONFIG_FILE.open('r') as config_file: CONFIG = json.load(config_file) @@ -181,7 +14,7 @@ def write_file(self, filedir: Path): BANK_DELIM = CONFIG['delimiter'] -def is_bank_obj(delim: str, vobj: Union[Lfo, Preset, Wavetable]) -> Optional[Union[Lfo, Preset, Wavetable]]: +def is_bank_obj(delim: str, vobj: VitalObject) -> Optional[VitalObject]: """ If an object name contains the delmiter and matches the pattern for belonging in a bank, return it. None otherwise. First delimiter must be at the beginning @@ -200,7 +33,7 @@ def is_bank_obj(delim: str, vobj: Union[Lfo, Preset, Wavetable]) -> Optional[Uni return None -def bank_comps(delim: str, vobj: Union[Lfo, Preset, Wavetable]) -> Tuple[str, str]: +def bank_comps(delim: str, vobj: VitalObject) -> Tuple[str, str]: """ Return the bank name and the name of the object with the bank bits stripped out. @@ -223,16 +56,16 @@ def bank_comps(delim: str, vobj: Union[Lfo, Preset, Wavetable]) -> Tuple[str, st def main(): banks: Dict[str, Bank] = dict() - for clstype, typedata in TYPE_DATA.items(): - subdir = USER_DIR.joinpath(typedata[0]) + for clstype in VitalObject.__subclasses__(): + subdir = USER_DIR.joinpath(clstype.DIR) for f in subdir.glob('*'): if not f.is_dir() and f.suffix == f'.{clstype.EXTENSION}': vobj = clstype.from_file(filepath=f) if is_bank_obj(delim=BANK_DELIM, vobj=vobj) is not None: bank_name, obj_name = bank_comps(delim=BANK_DELIM, vobj=vobj) - banks.setdefault(bank_name, Bank(bank_name=bank_name, lfos=None, presets=None, wavetables=None)) + banks.setdefault(bank_name, Bank(bank_name=bank_name, vital_objects=None)) vobj.rename(new_name=obj_name) # Set the "clean" name - banks[bank_name].elements[clstype].append(vobj) + banks[bank_name].add_object(vital_object=vobj) for bank in banks.values(): print(bank.name) diff --git a/vital/__init__.py b/vital/__init__.py new file mode 100644 index 0000000..5f07cea --- /dev/null +++ b/vital/__init__.py @@ -0,0 +1,160 @@ +from __future__ import annotations +from pathlib import Path +from zipfile import ZipFile, ZIP_DEFLATED +from typing import Dict, Iterable, Optional, Type +import json + + +class VitalObject: + """ + A base class for Vital Objects + """ + EXTENSION: str = None + DIR: str = None + name: str = None + original_path: Optional[Path] = None + _data: dict = None + + @classmethod + def from_file(cls, filepath: Path) -> VitalObject: + """ + Create an LFO from a file + :param filepath: + :return: + """ + with filepath.open('r') as f: + return cls(data=json.load(f), filepath=filepath) + + def __str__(self): + return self.name + + def __repr__(self): + return str(self) + + def __init__(self, data: dict, filepath: Optional[Path], **kwargs): + self._modified = False + self._data = data + self.original_path = filepath + self.top_level_keys = list(self._data.keys()) + + if 'name' in self.top_level_keys: + self.name = self._data['name'] + elif filepath is not None: + self.name = filepath.stem + elif 'name' in kwargs: + self.name = kwargs['name'] + else: + raise Exception('Unable to define name for object.') + + def rename(self, new_name: str) -> None: + """ + Rename the object + + :param new_name: + :return: + """ + if 'name' in self.top_level_keys: + self._data['name'] = new_name + self.name = new_name + self._modified = True + + def return_data(self) -> dict: + """ + Return the raw underlying data + + :return: + """ + return self._data + + def is_modified(self) -> bool: + """ + Return a boolean indicating if the object has been modified from original creation + + :return: + """ + return self._modified + + +class Lfo(VitalObject): + """ + A Vital LFO + """ + EXTENSION = 'vitallfo' + DIR = 'LFOs' + + +class Preset(VitalObject): + """ + A Vital Preset + """ + EXTENSION = 'vital' + DIR = 'Presets' + + +class Skin(VitalObject): + """ + A Vital Skin + """ + EXTENSION = 'vitalskin' + DIR = 'Skins' + + +class Wavetable(VitalObject): + """ + A Vital Wavetable + """ + EXTENSION = 'vitaltable' + DIR = 'Wavetables' + + +class Bank: + """ + Represents a bank comprised of various VitalObjects + """ + EXTENSION = 'vitalbank' + + def __str__(self): + return self.name + + def __repr__(self): + return str(self) + + def __init__(self, bank_name: str, vital_objects: Optional[Iterable[VitalObject]]): + self.name = bank_name + self.elements: Dict[Type[VitalObject], Dict[str, VitalObject]] = dict() + + if vital_objects is not None: + for vobj in vital_objects: + self.add_object(vobj) + + def add_object(self, vital_object: VitalObject) -> None: + """ + Add a VitalObject to the Bank + + :param vital_object: + :return: + """ + if not isinstance(vital_object, VitalObject): + raise Exception(f'{type(vital_object).__name__} is not recognized as VitalObject.') + + self.elements.setdefault(type(vital_object), dict()) + self.elements[type(vital_object)][vital_object.name] = vital_object + + def write_file(self, filedir: Path): + """ + Write the bank file to disk + + :param filedir: The directory to write to + :return: + """ + filepath = filedir.joinpath(f'{self.name}.{Bank.EXTENSION}') + + bankfile = ZipFile(file=filepath, mode='w', compression=ZIP_DEFLATED, compresslevel=9) + + for ct, ctd in self.elements.items(): + zdir = f'{self.name}/{ct.DIR}' + for i in ctd.values(): + zfile = f'{zdir}/{i.name}.{ct.EXTENSION}' + bankfile.writestr(zinfo_or_arcname=zfile, data=json.dumps(i.return_data())) + + bankfile.close()