fix: real preset data for all VST2/VST3 plugins, template system with ground-truth registry

- Extracted preset data from all_plugins_v2.rpp for 14 previously broken plugins
- Fixed PLUGIN_REGISTRY entries: Kontakt 7, Gullfoss, ValhallaDelay, VC 160/76, The Glue
- Template parser falls back to PLUGIN_PRESETS when source RPP has fake data
- Substitute Transient Master (not installed) with FabFilter Pro-C 2
- All 25 plugins now load correctly in REAPER
- Added template generator scripts and ground truth references
- Cleaned up temp/debug files from output/
This commit is contained in:
renato97
2026-05-03 18:54:40 -03:00
parent 3444006411
commit 8562bfbed1
23 changed files with 99316 additions and 688 deletions

View File

@@ -97,16 +97,16 @@ ROLE_TO_SAMPLE_ROLE = {
# Mapping of effect names to VST3 plugin entries
# Format: (registry_key, filename) tuples
# registry_key must match a key in VST3_REGISTRY for _build_plugin() lookup
# registry_key must match a key in PLUGIN_REGISTRY for _build_plugin() lookup
_VST3_EFFECTS: dict[str, tuple[str, str]] = {
"Pro-Q 3": ("FabFilter Pro-Q 3", "FabFilter Pro-Q 3.vst3"),
"Pro-C 2": ("FabFilter Pro-C 2", "FabFilter Pro-C 2.vst3"),
"Pro-R 2": ("FabFilter Pro-R 2", "FabFilter Pro-R 2.vst3"),
"Timeless 3": ("FabFilter Timeless 3", "FabFilter Timeless 3.vst3"),
"Saturn 2": ("FabFilter Saturn 2", "FabFilter Saturn 2.vst3"),
"Pro-L 2": ("FabFilter Pro-L 2", "FabFilter Pro-L 2.vst3"),
"The Glue": ("The Glue", "The Glue.vst3"),
"Valhalla Delay": ("Valhalla Delay", "ValhallaDelay.vst3"),
"Pro-Q 3": ("Pro-Q_3", "FabFilter"),
"Pro-C 2": ("Pro-C_2", "FabFilter"),
"Pro-R 2": ("Pro-R_2", "FabFilter"),
"Timeless 3": ("Timeless_3", "FabFilter"),
"Saturn 2": ("Saturn_2", "FabFilter"),
"Pro-L 2": ("Pro-L_2", "FabFilter"),
"The Glue": ("The_Glue", "The"),
"Valhalla Delay": ("ValhallaDelay", "ValhallaDelay.dll"),
}
@@ -167,8 +167,8 @@ def create_return_tracks() -> list[TrackDef]:
pan=0.0,
clips=[],
plugins=[PluginDef(
name="FabFilter Pro-R 2",
path="FabFilter_Pro_R_2.vst3",
name="FabFilter_Pro-R_2",
path="FabFilter",
index=0,
)],
send_reverb=0.0,
@@ -180,8 +180,8 @@ def create_return_tracks() -> list[TrackDef]:
pan=0.0,
clips=[],
plugins=[PluginDef(
name="FabFilter Timeless 3",
path="FabFilter_Timeless_3.vst3",
name="FabFilter_Timeless_3",
path="FabFilter",
index=0,
)],
send_reverb=0.0,

View File

@@ -0,0 +1,118 @@
#!/usr/bin/env python
"""Generate fixed .rpp files from example RPP templates.
Extracts track/FX chain structures from the example .rpp files,
fixes GUIDs using real values from reaper-vstplugins64.ini,
strips invalid PARAM lines, and generates working .rpp files.
Usage:
python scripts/generate_from_template.py --all
python scripts/generate_from_template.py --template TU_DIABLO --output output/tu_diablo_fixed.rpp
python scripts/generate_from_template.py --template DEL_LUNE --output output/del_lune_fixed.rpp
"""
from __future__ import annotations
import argparse
import sys
from pathlib import Path
# Ensure project root on path
_ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(_ROOT))
from src.composer.templates import extract_template, generate_rpp
TEMPLATES: dict[str, dict] = {
"TU_DIABLO": {
"source": "ejemplos/TU_DIABLO_ITHAN_NY.rpp",
"output": "output/tu_diablo_fixed.rpp",
"description": "TU_DIABLO_ITHAN_NY.rpp — 99 BPM E minor, Drill chileno",
},
"DEL_LUNE": {
"source": "ejemplos/DEL_LUNE_AL_FINDE_ITHAN_NY_JULIANNO_SOSA.rpp",
"output": "output/del_lune_fixed.rpp",
"description": "DEL_LUNE_AL_FINDE_*.rpp — 100 BPM B major, Reggaeton moderno",
},
}
def main() -> None:
parser = argparse.ArgumentParser(
description="Generate fixed .rpp files from example templates."
)
parser.add_argument(
"--all",
action="store_true",
help="Generate all templates (TU_DIABLO and DEL_LUNE)",
)
parser.add_argument(
"--template",
choices=list(TEMPLATES.keys()),
help="Specific template to generate",
)
parser.add_argument(
"--output",
help="Override output path",
)
parser.add_argument(
"--list",
action="store_true",
help="List available templates",
)
args = parser.parse_args()
if args.list:
print("Available templates:")
for key, info in TEMPLATES.items():
print(f" {key}: {info['description']}")
print(f" Source: {info['source']}")
print(f" Output: {info['output']}")
return
templates_to_run = []
if args.all:
templates_to_run = list(TEMPLATES.keys())
elif args.template:
templates_to_run = [args.template]
else:
# Default: generate all
templates_to_run = list(TEMPLATES.keys())
for key in templates_to_run:
info = TEMPLATES[key]
source_path = _ROOT / info["source"]
output_path = Path(args.output) if args.output else _ROOT / info["output"]
print(f"\n[{key}]")
print(f" Source: {source_path}")
print(f" Output: {output_path}")
if not source_path.exists():
print(f" ERROR: Source file not found: {source_path}", file=sys.stderr)
continue
try:
# Extract template from source RPP
print(f" Extracting template...")
template = extract_template(source_path)
print(f" Tracks found: {len(template.tracks)}")
for track in template.tracks:
print(f" - {track.name}: {len(track.plugins)} plugins")
for plugin in track.plugins:
print(f" {plugin.name} ({plugin.path})")
# Generate fixed RPP
print(f" Generating RPP...")
generate_rpp(template, output_path)
print(f" SUCCESS: {output_path}")
except Exception as e:
print(f" ERROR: {e}", file=sys.stderr)
import traceback
traceback.print_exc()
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,70 @@
#!/usr/bin/env python
"""Generate registry code from parsed_plugins.json."""
import json
from pathlib import Path
plugins = json.loads(Path("output/parsed_plugins.json").read_text())
# We prefer VST3 over VST2 when both exist for the same plugin
# Build a map, VST3 overrides VST2
seen_keys = {}
for p in plugins:
key = p['key']
if key in seen_keys:
existing = seen_keys[key]
# Prefer VST3 over VST2
if p['is_vst3'] and not existing['is_vst3']:
seen_keys[key] = p
# Prefer VST3i/VSTi instruments
elif p['display_name'].startswith('VST3i') and not existing['display_name'].startswith('VST3i'):
seen_keys[key] = p
elif p['display_name'].startswith('VSTi') and not existing['display_name'].startswith('VSTi'):
seen_keys[key] = p
else:
seen_keys[key] = p
# Deduplicated
final = sorted(seen_keys.values(), key=lambda p: p['key'])
print(f"Unique plugins after dedup: {len(final)}")
# Generate registry code
lines = []
lines.append("# Auto-generated from output/all_plugins_v2.rpp (REAPER ground truth)")
lines.append("# Format: key → (display_name, filename, uid_guid)")
lines.append("PLUGIN_REGISTRY: dict[str, tuple[str, str, str]] = {")
for p in final:
dn = p['display_name']
fn = p['filename']
ug = p['uid_guid']
# Quote filename if it has spaces
if ' ' in fn or not p['is_vst3']:
fn_str = f'"{fn}"'
else:
fn_str = f'"{fn}"'
lines.append(f' "{p["key"]}": (')
lines.append(f' "{dn}",')
lines.append(f' {fn_str},')
lines.append(f' "{ug}",')
lines.append(f' ),')
lines.append("}")
# Generate presets code
lines.append("")
lines.append("# Auto-generated preset data from output/all_plugins_v2.rpp")
lines.append("PLUGIN_PRESETS: dict[str, list[str]] = {")
for p in final:
if p['preset_lines']:
lines.append(f' "{p["key"]}": [')
for pl in p['preset_lines']:
lines.append(f' "{pl}",')
lines.append(f' ],')
else:
lines.append(f' "{p["key"]}": [],')
lines.append("}")
Path("output/registry_code.py").write_text('\n'.join(lines), encoding='utf-8')
print(f"Generated output/registry_code.py ({len(lines)} lines)")
print(f"\nSample entries:")
for p in final[:5]:
print(f' "{p["key"]}": ("{p["display_name"]}", "{p["filename"]}", "{p["uid_guid"]}")')

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env python
"""Parse all_plugins_v2.rpp and generate registry code."""
import re
import json
from pathlib import Path
text = Path("output/all_plugins_v2.rpp").read_text(encoding="utf-8")
# Match VST elements: opening tag through closing >
# The closing > is on its own line with leading whitespace
pattern = r'<VST "([^"]+)" ([^\n]+)\n([\s\S]*?)\n\s*>'
matches = re.findall(pattern, text)
print(f"Total VST elements found: {len(matches)}")
plugins = []
for name, rest, preset_block in matches:
# Parse rest: filename 0 "" uid_guid ""
rest_parts = rest.strip().split()
# Find the uid_guid (contains { or <)
uid_guid = ""
for part in rest_parts:
if '{' in part or '<' in part:
uid_guid = part
break
# Find filename (second token, may be quoted)
filename = rest_parts[0].strip('"')
# Clean preset lines
preset_lines = [line.strip() for line in preset_block.strip().split('\n') if line.strip()]
# Remove PRESETNAME lines
preset_lines = [l for l in preset_lines if not l.startswith('PRESETNAME')]
is_vst3 = name.startswith('VST3') or name.startswith('VST3i')
# Generate a short key
# Remove prefix and vendor
clean = name.replace('VST3i: ', '').replace('VST3: ', '').replace('VSTi: ', '').replace('VST: ', '')
# Remove vendor in parens
clean = re.sub(r'\s*\([^)]+\)', '', clean).strip()
key = clean.replace(' ', '_')
plugins.append({
'key': key,
'display_name': name,
'filename': filename,
'uid_guid': uid_guid,
'is_vst3': is_vst3,
'preset_lines': preset_lines,
})
# Save as JSON for use by other scripts
Path("output/parsed_plugins.json").write_text(json.dumps(plugins, indent=2, ensure_ascii=False))
print(f"Saved to output/parsed_plugins.json")
# Print summary
vst3_count = sum(1 for p in plugins if p['is_vst3'])
vst2_count = sum(1 for p in plugins if not p['is_vst3'])
print(f"VST3: {vst3_count}, VST2: {vst2_count}")
# Print a few examples
for p in plugins[:5]:
print(f"\n {p['key']}: {p['display_name']}")
print(f" filename: {p['filename']}")
print(f" uid_guid: {p['uid_guid']}")
print(f" preset lines: {len(p['preset_lines'])}")