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:
@@ -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,
|
||||
|
||||
118
scripts/generate_from_template.py
Normal file
118
scripts/generate_from_template.py
Normal 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()
|
||||
70
scripts/generate_registry_code.py
Normal file
70
scripts/generate_registry_code.py
Normal 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"]}")')
|
||||
68
scripts/parse_ground_truth.py
Normal file
68
scripts/parse_ground_truth.py
Normal 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'])}")
|
||||
Reference in New Issue
Block a user