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

@@ -0,0 +1,213 @@
# Design: audio-clip-playlist
## Context
The current `FLPBuilder` emits pattern-based playlist items (single 32-byte `ArrangementItem`) for all tracks. Melodic tracks with `source_type="audio"` need a different encoding: 3 consecutive 32-byte items per clip, with fixed indices `0x3FF0` / `0x8080` / `0x6440`.
---
## Architecture Decisions
| Decision | Choice | Rationale |
|----------|--------|-----------|
| Polymorphism | `to_items()` returns `list[bytes]` (3 items) vs `to_bytes()` returns `bytes` (1 item) | Avoids union-type juggling; builder collects all items uniformly |
| `source_type` default | `"pattern"` | Existing callers unchanged; audio is opt-in |
| Audio clip item indices | Treat `0x8080`/`0x6440`/`0x3FF0` as constants | Per-proposal risk mitigation; verified against `audio_clip_reference.flp` |
| ChSamplePath correlation | Audio clip item precedes its `ChSamplePath` event in channel data | Builder writes items first, then channel events with sample paths already handled by `ChannelSkeletonLoader` |
---
## File Changes
| File | Action |
|------|--------|
| `src/flp_builder/arrangement.py` | Add `AudioClipItem`, constants `AUDIO_CLIP_HEADER_INDEX`, `AUDIO_CLIP_DATA_INDEX` |
| `src/flp_builder/schema.py` | Add `source_type: str = "pattern"` field to `MelodicTrack` |
| `src/flp_builder/builder.py` | Route on `track.source_type`; append `AudioClipItem` objects to arrangement items list |
---
## Binary Format
Each audio clip placement = **96 bytes** (3 × 32):
### Item 0 — PATTERN positioning
```
struct.pack("<IHHIHH HH 4B ff",
position, # bar * ppq * 4
0x5000, # pattern_base (distinct from drum pattern_base 0x5000)
0x5000 + pattern_id,
length, # num_bars * ppq * 4
track_rvidx, # (max_tracks - 1) - track_index
0, # group
0x0078,
0x0040, # flags (no mute for audio)
64, 100, 128, 128,
-1.0, -1.0,
)
```
### Item 1 — AUDIO_CLIP_HEADER
```
00 00 00 00 00 00 F0 3F 00 00 00 00 00 00 00 00
00 03 00 00 00 50 05 00 03 00 00 F3 01 00 00
```
- `item_index = 0x3FF0`
- All other bytes constant (observed from reference FLP)
### Item 2 — AUDIO_CLIP_DATA
```
78 00 40 00 40 64 80 80 00 00 80 BF 00 00 80 BF
02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
```
- `pattern_base = 0x6440`
- `item_index = 0x8080`
- Remaining bytes constant
---
## Data Flow
```
SongDefinition.melodic_tracks[i]
├── source_type == "pattern" → ArrangementItem (existing path)
└── source_type == "audio" → AudioClipItem.to_items() [3 × 32 bytes]
build_arrangement_section()
(all items concatenated into Playlist ID233 data)
```
---
## AudioClipItem Interface
```python
# src/flp_builder/arrangement.py
AUDIO_CLIP_HEADER_INDEX = 0x3FF0 # item_index for header item
AUDIO_CLIP_DATA_INDEX = 0x8080 # item_index for data item
AUDIO_CLIP_DATA_BASE = 0x6440 # pattern_base for data item
@dataclass
class AudioClipItem:
"""Playlist item referencing an audio clip via 3×32-byte structure."""
pattern_id: int # references the pattern containing audio notes
bar: float # start bar (0-based)
num_bars: float # length in bars
track_index: int # 0-based track index
muted: bool = False
def to_items(self, ppq: int = PPQ_DEFAULT, max_tracks: int = MAX_TRACKS_DEFAULT) -> list[bytes]:
"""Return 3 consecutive 32-byte items: [PATTERN, HEADER, DATA]."""
...
# Convenience: to_bytes() returns concatenation for polymorphic use
def to_bytes(self, ppq: int = PPQ_DEFAULT, max_tracks: int = MAX_TRACKS_DEFAULT) -> bytes:
return b"".join(self.to_items(ppq, max_tracks))
```
---
## Schema Change
```python
# src/flp_builder/schema.py — MelodicTrack
@dataclass
class MelodicTrack:
role: str
sample_path: str
notes: list[MelodicNote]
channel_index: int
volume: float = 0.85
pan: float = 0.0
source_type: str = "pattern" # NEW: "pattern" | "audio"
```
`SongDefinition.validate()` also validates:
- `source_type in ("pattern", "audio")`
- If `source_type == "audio"`: `channel_index >= 17`
---
## Builder Routing
```python
# src/flp_builder/builder.py — _build_arrangement
def _build_arrangement(self, song, track_data_template):
items: list[ArrangementItem | AudioClipItem] = []
# Pattern tracks → ArrangementItem (existing)
for item in song.items:
items.append(ArrangementItem(
pattern_id=item.pattern,
bar=item.bar,
num_bars=item.bars,
track_index=item.track - 1,
muted=item.muted,
))
# Melodic tracks → route on source_type
drum_pattern_count = len(song.patterns)
max_drum_track = max((it.track for it in song.items), default=1)
for i, mt in enumerate(song.melodic_tracks):
pattern_id = drum_pattern_count + i + 1
track_index = max_drum_track + i # 0-based
if mt.source_type == "audio":
items.append(AudioClipItem(
pattern_id=pattern_id,
bar=mt.bar, # from MelodicTrack.bar (default 0.0)
num_bars=mt.num_bars, # from MelodicTrack.num_bars (default 4.0)
track_index=track_index,
muted=False,
))
else: # "pattern"
items.append(ArrangementItem(
pattern_id=pattern_id,
bar=mt.bar or 0.0,
num_bars=mt.num_bars or 4.0,
track_index=track_index,
muted=False,
))
return build_arrangement_section(items, track_data_template, ppq=song.meta.ppq)
```
**Note**: `MelodicTrack` currently has no `bar`/`num_bars` fields. The proposal scope does not include adding them; for now audio clip items use `bar=0.0, num_bars=4.0` as placeholders until a future change extends `MelodicTrack` with timeline placement fields.
---
## ChSamplePath Correlation
`AudioClipItem` places a reference on the playlist. The actual sample file path is carried by `ChSamplePath` events (ID 196) in the channel data, already handled by `ChannelSkeletonLoader.load(melodic_map=...)` which patches sampler channels with the correct `.wav` paths.
No additional correlation is needed: the channel index on the melodic track (e.g., ch17) maps to the same channel that carries the `ChSamplePath` event. The builder ensures channel events precede arrangement events in the FLP binary.
---
## Validation Strategy
Binary diff against `audio_clip_reference.flp`:
1. Generate a minimal FLP with one audio clip item
2. Extract playlist bytes (ID 233 data, after `encode_data_event` wrapper)
3. Compare first 96 bytes of playlist data against reference
4. Specifically verify:
- Bytes 031: pattern positioning (`PATTERN_BASE = 0x5000`)
- Bytes 3263: header (`item_index = 0x3FF0`)
- Bytes 6495: data (`pattern_base = 0x6440`, `item_index = 0x8080`)
If mismatch at 0x8080/0x6440: derive values from reference via `build_track_data_template`-style extraction, then update constants.
---
## Open Questions
| Question | Status |
|----------|--------|
| `MelodicTrack.bar`/`num_bars` fields needed? | Not in scope; placeholders used. Future change to add placement fields. |
| Index pool (0x8080/0x6440) per-project vs constant? | Treated as constants per proposal risk mitigation. If FLP fails to open, switch to derivation. |