Last active
October 26, 2025 03:23
-
-
Save anytizer/18068e9fe0461767406353bb02c3cf58 to your computer and use it in GitHub Desktop.
LMMS ZynAddSubFx presets analysis
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # Author: @anytizer, 2025-10-25 | |
| # Advanced usages: | |
| # python lmms-zyn.py > lmms-zyn.csv | |
| # Helps to analyze zynaddsubfx presets (compressed files) | |
| # Compare with original release repo and LMMS Embedded Presets | |
| import os | |
| import hashlib | |
| import zlib, gzip | |
| from pathlib import Path | |
| import xml.etree.ElementTree as ET | |
| from bs4 import BeautifulSoup, XMLParsedAsHTMLWarning | |
| import warnings | |
| warnings.filterwarnings("ignore", category=XMLParsedAsHTMLWarning) | |
| # Obtain whole list of .xiz files | |
| def find_xiz_files(folder_path): | |
| folder = Path(folder_path) | |
| xiz_files = list(folder.rglob("*.xiz")) | |
| return xiz_files | |
| # A CRC signature of a file | |
| def crc32_file(filename, chunk_size=65536): | |
| checksum = 0 | |
| with open(filename, 'rb') as f: | |
| while chunk := f.read(chunk_size): | |
| checksum = zlib.crc32(chunk, checksum) | |
| return hex(checksum & 0xFFFFFFFF) | |
| # Pick a file signature | |
| def xiz_signature(filename="*.xiz"): | |
| return crc32_file(filename) | |
| # Convert bytes into human readable form | |
| def human_readable_size(size_bytes=0): | |
| units = ["B", "KB", "MB", "GB", "TB", "PB"] | |
| i = 0 | |
| while size_bytes >= 1024 and i < len(units) - 1: | |
| size_bytes /= 1024.0 | |
| i += 1 | |
| return f"{size_bytes:.2f} {units[i]}" | |
| # Decompressed XML Content from .xiz file | |
| def xiz_fc(filename="*.xiz"): | |
| content = "" | |
| try: | |
| with gzip.open(filename, 'rt', encoding='utf-8') as f: | |
| content = f.read() | |
| except Exception as e: | |
| content = "<ZynAddSubFX-data ZynAddSubFX-author='______'></ZynAddSubFX-data>" | |
| return content | |
| # Obtain file format author name | |
| # <ZynAddSubFX-data version-revision="1" version-minor="4" ZynAddSubFX-author="_______" version-major="2"> | |
| def xiz_author1(xml_content="*.xiz"): | |
| author = "Default Author Name" | |
| try: | |
| root = BeautifulSoup(xml_content, "lxml") | |
| ZynAddSubFX = root.find_all("ZynAddSubFX-data") | |
| author = ZynAddSubFX.get("ZynAddSubFX-author") | |
| except Exception as e: | |
| author = "Author Not Found" | |
| return author | |
| # Obtain instrument author | |
| def xiz_author2(xml_content="*.xiz"): | |
| author = "Instrument Author" | |
| try: | |
| root = ET.fromstring(xml_content) | |
| author_element = root.find(".//string[@name='author']") | |
| author = ""+author_element.text if author_element is not None else "" | |
| except Exception as e: | |
| author = "Author Not Found" | |
| return author | |
| # Convert file details into static hash | |
| def special_signature(author, instrument, basename=""): | |
| fullname = f"{author}/{instrument}/{basename}" | |
| md5_hash = hashlib.md5() | |
| md5_hash.update(fullname.encode("utf-8")) | |
| hex_digest = md5_hash.hexdigest().upper() | |
| return hex_digest | |
| # Write an uncompressed XML file | |
| def write_xml(filename="", xml_content=""): | |
| with open(filename, "w", encoding="latin-1") as fw: | |
| fw.write(xml_content) | |
| fw.close() | |
| # Calculate variables | |
| def analyze(filename="*.xiz"): | |
| size = human_readable_size(os.path.getsize(filename)) | |
| instrument = os.path.basename(os.path.dirname(filename)) | |
| basename = os.path.basename(filename) | |
| signature = xiz_signature(filename) | |
| return [instrument, basename, size, signature] | |
| # Some scripts to run early | |
| def setup(): | |
| # Empty the xmls files if any | |
| # Remove the xmls/ folder | |
| # Reduce disk i/o | |
| os.makedirs("xmls", exist_ok=True) | |
| return True | |
| def collect_xiz_files(): | |
| # Path to instrument presets from one of the folders when checked out | |
| # - https://github.com/LMMS/lmms/tree/master/data/presets/ZynAddSubFX | |
| # - https://github.com/zynaddsubfx/instruments/tree/master/banks | |
| #folder_to_scan = "lmms/data/presets/ZynAddSubFX" | |
| folder_to_scan = "instruments/banks" | |
| return find_xiz_files(folder_to_scan) | |
| # usage | |
| if __name__ == "__main__": | |
| operated = setup() | |
| xiz_files = collect_xiz_files() | |
| counter = 0 | |
| for filename in xiz_files: | |
| counter += 1 | |
| xml_content = xiz_fc(filename) | |
| instrument, basename, size, signature = analyze(filename) | |
| author1 = xiz_author1(xml_content) # file format author | |
| author2 = xiz_author2(xml_content) # instrument author | |
| author = author2 | |
| # Write uncompresed xml | |
| #new_filename = "xmls/"+special_signature(author, instrument, basename)+".xml" | |
| #write_xml(new_filename, xml_content) | |
| print( | |
| f'{counter:05}', | |
| f"{instrument:30}", | |
| f"{basename:50}", | |
| f"{size:15}", | |
| f"{signature:10}", | |
| f"{author:30}", | |
| sep=" | " | |
| ) | |
| # break | |
| # End of file |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment