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. |

View File

@@ -0,0 +1,558 @@
<REAPER_PROJECT 0.1 "6.0" 1737000000
TEMPO 100 4 4
SAMPLERATE 44100
PLAYRATE 1 0 0.25 4
SELECTION 0 0
CURSOR 0
ZOOM 100 0 0
VZOOMEX 6 0
PANMODE 3
MIXERUIFLAGS 11 48
LOOPRECOVERY 0
TRACKNUMBER 0
MASTERTRACKHEIGHT 60
MASTERPEAKCOL 16576
MASTERMUTESOLO 0
MASTER_NCH 2
MASTER_VOLUME 1 0
MASTER_SEL 0
MASTER_MUTE 0
MASTERFXBYPASS 0
MASTERFX_BYPASS 0
MASTER_PANMODE 0
MASTER_WIDTH 1
; =======================================================
; PROYECTO: DEL LUNE AL FINDE - ITHAN NY ft. JULIANNO SOSA
; BPM: 100 | TONALIDAD: Si mayor (B major)
; AÑO: 2025 | DURACION: 2:50
; GENERO: Urbano latino / Reggaeton moderno con influencia drill
; MOOD: Festivo / nocturno / romantico-urbano
; =======================================================
; -------------------------------------------------------
; TRACK 1 - DRUM MACHINE PRINCIPAL
; Kick mas redondo que en Tu Diablo (estilo reggaeton moderno)
; Hi-hats en corcheas mas cerradas, snare electrico
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000001}
NAME "DRUMS PRINCIPAL"
PEAKCOL 16576
BEAT -1
AUTOMODE 0
VOLPAN 0.9 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Kontakt para el kit de bateria
<VST "VST3: Kontakt 7 (Native Instruments)" "Kontakt 7.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.85
>
; Transient designer para el punch del kick
<VST "VST: PrimalTap (SoundToys)" "PrimalTap.dll" 0 "" 0<> ""
0 0
PARAM 0 "Speed" 0.5
PARAM 1 "Feedback" 0.2
PARAM 2 "Mix" 0.15
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 2 - 808 / SUB BASS (B major, melodioso)
; 808 mas melodico que en Tu Diablo
; Con movimiento entre notas de B major scale
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000002}
NAME "808 SUB BASS - B major"
PEAKCOL 33023
BEAT -1
AUTOMODE 0
VOLPAN 0.95 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST3: Kontakt 7 (Native Instruments)" "Kontakt 7.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.88
>
; Saturacion harmonica para que se escuche en todo sistema
<VST "VST: Decapitator (SoundToys)" "Decapitator.dll" 0 "" 1145980753<56535444454341> ""
0 0
PARAM 0 "Drive" 0.25
PARAM 1 "Tone" 0.55
PARAM 2 "Mix" 0.3
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 3 - MELODIA PRINCIPAL (sintetizador / piano)
; Melodia alegre/festiva en B major
; Caracter nocturno pero mas luminoso que Tu Diablo
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000003}
NAME "MELODIA LEAD - Piano/Synth"
PEAKCOL 65280
BEAT -1
AUTOMODE 0
VOLPAN 0.8 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Serum2 para la melodia principal
<VST "VST3: Serum 2 (Xfer Records)" "Serum2.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.78
>
; Reverb de sala mediana para la melodia
<VST "VST: ValhallaDelay x64 (Valhalla DSP)" "ValhallaDelay_x64.dll" 0 "" 0<> ""
0 0
PARAM 0 "Mix" 0.2
PARAM 1 "Feedback" 0.35
PARAM 2 "Time" 0.45
>
; Chorus / width para engordar la melodia
<VST "VST: MicroShift (SoundToys)" "MicroShift.dll" 0 "" 0<> ""
0 0
PARAM 0 "Width" 0.6
PARAM 1 "Mix" 0.45
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 4 - ARPEGIO / SYNTH LEAD 2 (contrapunto)
; Segundo elemento melodico con arpegiador
; Muy tipico del urbano chileno 2025
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000004}
NAME "ARPEGIO SYNTH 2"
PEAKCOL 16711935
BEAT -1
AUTOMODE 0
VOLPAN 0.65 0.1
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Pigments para arpegio / lead 2
<VST "VST3: Pigments (Arturia)" "Pigments.exe" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.7
PARAM 1 "Pan" 0.0
>
; Tremolo ritmico en el arpegio
<VST "VST: Tremolator (SoundToys)" "Tremolator.dll" 0 "" 0<> ""
0 0
PARAM 0 "Depth" 0.5
PARAM 1 "Rate" 0.5
PARAM 2 "Mix" 0.45
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 5 - PAD DE RELLENO (sustain armonico)
; Pad ancho que llena el espacio estereo
; Armonia de B major sostenida
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000005}
NAME "PAD ARMONICO - Relleno"
PEAKCOL 8388607
BEAT -1
AUTOMODE 0
VOLPAN 0.55 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Omnisphere para el pad de fondo
<VST "VST3: Omnisphere (Spectrasonics)" "Omnisphere.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.65
>
; Modulacion sutil en el pad
<VST "VST: PhaseMistress (SoundToys)" "PhaseMistress.dll" 0 "" 1347371358<56535450484153> ""
0 0
PARAM 0 "Speed" 0.15
PARAM 1 "Depth" 0.35
PARAM 2 "Mix" 0.25
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 6 - VOZ LEAD ITHAN NY
; Estilo festivo, autotune medio (menos aggressive que Tu Diablo)
; Voz mas clara, presencia mas frontal
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000006}
NAME "VOZ LEAD - ITHAN NY"
PEAKCOL 16711680
BEAT -1
AUTOMODE 0
VOLPAN 0.9 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Compresor FET agresivo para voz urbana
<VST "VST3: VC 76 FX (Audified)" "VC 76.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Threshold" 0.6
PARAM 1 "Ratio" 0.7
PARAM 2 "Attack" 0.25
PARAM 3 "Release" 0.5
PARAM 4 "Gain" 0.55
>
; Saturacion para presencia en voz
<VST "VST: Radiator (SoundToys)" "Radiator.dll" 0 "" 1380012882<56535452414449> ""
0 0
PARAM 0 "Drive" 0.2
PARAM 1 "Mix" 0.35
>
; Delay con mucho swing/groove
<VST "VST: EchoBoy (SoundToys)" "EchoBoy.dll" 0 "" 1163022169<565354454348> ""
0 0
PARAM 0 "Mix" 0.22
PARAM 1 "Time" 0.52
PARAM 2 "Feedback" 0.28
>
; Reverb de habitacion / plate
<VST "VST: EffectRack (SoundToys)" "EffectRack.dll" 0 "" 0<> ""
0 0
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 7 - VOZ JULIANNO SOSA
; Voz mas profunda / tone diferente al de ITHAN
; Autotune sutil, mas melodico
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000007}
NAME "VOZ - JULIANNO SOSA"
PEAKCOL 255
BEAT -1
AUTOMODE 0
VOLPAN 0.85 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Compresor VCA mas suave para voz de Julianno
<VST "VST3: VC 160 FX (Audified)" "VC 160.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Threshold" 0.5
PARAM 1 "Ratio" 0.6
PARAM 2 "Attack" 0.35
PARAM 3 "Release" 0.5
>
; Saturacion leve
<VST "VST: Radiator (SoundToys)" "Radiator.dll" 0 "" 1380012882<56535452414449> ""
0 0
PARAM 0 "Drive" 0.15
PARAM 1 "Mix" 0.25
>
; Delay mas notable en Julianno (mas melodico)
<VST "VST: EchoBoy (SoundToys)" "EchoBoy.dll" 0 "" 1163022169<565354454348> ""
0 0
PARAM 0 "Mix" 0.25
PARAM 1 "Time" 0.55
PARAM 2 "Feedback" 0.32
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 8 - BACKGROUNDS / COROS
; Voces de fondo que apoyan en el coro
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000008}
NAME "COROS / BG VOCALS"
PEAKCOL 8421504
BEAT -1
AUTOMODE 0
VOLPAN 0.6 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Pitch shift para harmonias
<VST "VST: LittlePrimalTap (SoundToys)" "LittlePrimalTap.dll" 0 "" 0<> ""
0 0
PARAM 0 "Speed" 0.5
PARAM 1 "Mix" 0.45
>
; Width en las voces de fondo
<VST "VST: MicroShift (SoundToys)" "MicroShift.dll" 0 "" 0<> ""
0 0
PARAM 0 "Width" 0.7
PARAM 1 "Mix" 0.55
>
; Reverb grande para los coros
<VST "VST: ValhallaDelay x64 (Valhalla DSP)" "ValhallaDelay_x64.dll" 0 "" 0<> ""
0 0
PARAM 0 "Mix" 0.35
PARAM 1 "Feedback" 0.4
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 9 - PERCUSION ADICIONAL (congas / claps / shaker)
; Groove mas bailable comparado a Tu Diablo
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000009}
NAME "PERCUSION - Groove Layer"
PEAKCOL 16776960
BEAT -1
AUTOMODE 0
VOLPAN 0.7 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST3: Kontakt 7 (Native Instruments)" "Kontakt 7.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.75
>
; Filtro que da groove a la percusion
<VST "VST: FilterFreak2 (SoundToys)" "FilterFreak2.dll" 0 "" 0<> ""
0 0
PARAM 0 "Cutoff" 0.7
PARAM 1 "Resonance" 0.25
PARAM 2 "Mix" 0.35
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 10 - FX TRANSICIONES / RISERS / IMPACTS
; Elementos de transicion entre secciones
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000010}
NAME "FX TRANSITIONS"
PEAKCOL 16711808
BEAT -1
AUTOMODE 0
VOLPAN 0.65 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Crystallizer para efectos de pitch/reverb en transiciones
<VST "VST: Crystallizer (SoundToys)" "Crystallizer.dll" 0 "" 0<> ""
0 0
PARAM 0 "Pitch" 0.0
PARAM 1 "Size" 0.6
PARAM 2 "Mix" 0.5
PARAM 3 "Feedback" 0.3
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 11 - BUS PARALELO (parallel compression)
; Compresor en paralelo para glue general
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000011}
NAME "PARALLEL COMP BUS"
PEAKCOL 4210752
BEAT -1
AUTOMODE 0
VOLPAN 0.4 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 1 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST3: VC 2A FX (Audified)" "VC 2A.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Peak Reduction" 0.7
PARAM 1 "Gain" 0.55
PARAM 2 "Mix" 1.0
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 12 - MASTER BUS
; -------------------------------------------------------
<TRACK {22222222-0001-0001-0001-000000000012}
NAME "MASTER BUS"
PEAKCOL 16777215
BEAT -1
AUTOMODE 0
VOLPAN 1.0 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 1 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Gullfoss Live para balance espectral en master
<VST "VST: Gullfoss Live (Soundtheory)" "Gullfoss Live.dll" 0 "" 0<> ""
0 0
PARAM 0 "Recover" 0.25
PARAM 1 "Tame" 0.45
PARAM 2 "Bias" 0.05
>
; Limiter / maximizer final
<VST "VST: Gullfoss Master (Soundtheory)" "Gullfoss Master.dll" 0 "" 0<> ""
0 0
PARAM 0 "Recover" 0.3
PARAM 1 "Tame" 0.5
>
>
</TRACK>
</REAPER_PROJECT>

View File

@@ -0,0 +1,531 @@
<REAPER_PROJECT 0.1 "6.0" 1699000000
TEMPO 99 4 4
SAMPLERATE 44100
PLAYRATE 1 0 0.25 4
SELECTION 0 0
CURSOR 0
ZOOM 100 0 0
VZOOMEX 6 0
PANMODE 3
MIXERUIFLAGS 11 48
LOOPRECOVERY 0
TRACKNUMBER 0
MASTERTRACKHEIGHT 60
MASTERPEAKCOL 16576
MASTERMUTESOLO 0
MASTER_NCH 2
MASTER_VOLUME 1 0
MASTER_SEL 0
MASTER_MUTE 0
MASTERFXBYPASS 0
MASTERFX_BYPASS 0
MASTER_PANMODE 0
MASTER_WIDTH 1
POOLEDENVATTACHED 0
RENDER_FILE ""
RENDER_PATTERN ""
RENDER_FMT 0 2 0
RENDER_1X 0
RENDER_RANGE 1 0 0 18 1000
RENDER_RESAMPLE 3 0 1
RENDER_ADDTOPROJ 0
RENDER_STEMS 0
RENDER_DITHER 0
TIMELOCKMODE 1
TEMPOENVLOCKMODE 1
ITEMMIX 1
DEFPITCHMODE 589824 0
TAKELANE 1
SWTCHDELETE 0
<NOTES 0 2
>
RIPPLE 0
GROUPOVERRIDE 0 0 0
AUTOXFADE 129
ENVATTACH 1
POOLEDENVATTACH 0
OVERLAPS 1
LOOPRECFLAG 0
TUNER 0
STROBOSCOPE 0
REAPER_METADATA "" ""
; =======================================================
; PROYECTO: TU DIABLO - ITHAN NY
; BPM: 99 | TONALIDAD: Mi menor (E minor)
; PRODUCER: Donner | AÑO: 2022
; GENERO: Urbano latino / Drill chileno
; SAMPLE BASE: "Yo Sé Que Tú Quieres" - Sammy & Falsetto
; =======================================================
; -------------------------------------------------------
; TRACK 1 - BEAT / DRUM MACHINE (808 + Hi-Hats + Kick)
; Kick seco tipo drill, 808 sub con mucha presencia
; Hi-hats en corcheas triplets con rolls
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000001}
NAME "DRUMS - 808 BEAT"
PEAKCOL 16576
BEAT -1
AUTOMODE 0
VOLPAN 0.9 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
INQ 0 0 0 0.5 100 0 0 100
NCHAN 2
FX 1
FXCHAIN_REC 0
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; --- Transient Shaper: controla el ataque del kick ---
<VST "VST: Transient Master (Native Instruments)" "Transient Master.dll" 0 "" 1349676904<5653544E49544D00> ""
0 0
>
; --- Compresor con saturacion en el bus de bateria ---
<VST "VST: Decapitator (SoundToys)" "Decapitator.dll" 0 "" 1145980753<56535444454341> ""
0 0
PARAM 0 "Drive" 0.35
PARAM 1 "Tone" 0.5
PARAM 2 "Mix" 0.4
>
<VST "VST: Radiator (SoundToys)" "Radiator.dll" 0 "" 1380012882<56535452414449> ""
0 0
PARAM 0 "Drive" 0.25
PARAM 1 "Mix" 0.3
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 2 - 808 SUB BASS (fundamental del drill)
; Sub muy profundo, pitcheado en E minor
; Con glide/portamento entre notas
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000002}
NAME "808 SUB BASS"
PEAKCOL 33023
BEAT -1
AUTOMODE 0
VOLPAN 1.0 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Kontakt con patch de 808 sub
<VST "VST3: Kontakt 7 (Native Instruments)" "Kontakt 7.vst3" 0 "" 1315270729<> ""
0 0
PARAM 0 "Volume" 0.85
>
; Saturacion leve para que corte en monitores pequeños
<VST "VST: Decapitator (SoundToys)" "Decapitator.dll" 0 "" 1145980753<56535444454341> ""
0 0
PARAM 0 "Drive" 0.2
PARAM 2 "Mix" 0.35
>
; Compresor para controlar el sustain del 808
<VST "VST: Gullfoss Master (Soundtheory)" "Gullfoss Master.dll" 0 "" 0<> ""
0 0
PARAM 0 "Recover" 0.4
PARAM 1 "Tame" 0.5
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 3 - MELODIA SAMPLE (loop de "Yo Se Que Tu Quieres")
; Sample pitcheado y cortado, base melodica de la cancion
; Atmosfera oscura / misteriosa
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000003}
NAME "SAMPLE LOOP - Melodia"
PEAKCOL 65280
BEAT -1
AUTOMODE 0
VOLPAN 0.75 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Efecto de reverb oscuro para el sample
<VST "VST: EchoBoy (SoundToys)" "EchoBoy.dll" 0 "" 1163022169<565354454348> ""
0 0
PARAM 0 "Mix" 0.25
PARAM 1 "Time" 0.4
>
<VST "VST: PhaseMistress (SoundToys)" "PhaseMistress.dll" 0 "" 1347371358<56535450484153> ""
0 0
PARAM 0 "Speed" 0.2
PARAM 1 "Depth" 0.4
PARAM 2 "Mix" 0.2
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 4 - PAD SINTETICO (ambiente oscuro / minor)
; Pad que sostiene la tension armonica en Em
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000004}
NAME "PAD SINTETICO - Atmosfera"
PEAKCOL 16711935
BEAT -1
AUTOMODE 0
VOLPAN 0.6 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Diva para el pad sintetico oscuro
<VST "VST3: Diva (u-he)" "Diva(x64).vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.7
PARAM 1 "Pan" 0.0
>
; Reverb de sala para el pad
<VST "VST: ValhallaDelay x64 (Valhalla DSP)" "ValhallaDelay_x64.dll" 0 "" 0<> ""
0 0
PARAM 0 "Mix" 0.35
PARAM 1 "Feedback" 0.45
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 5 - MELODIA LEAD (sintetizador principal)
; Melodia tenebrosa caracteristica del track
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000005}
NAME "LEAD MELODIA"
PEAKCOL 65535
BEAT -1
AUTOMODE 0
VOLPAN 0.8 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST3: Serum 2 (Xfer Records)" "Serum2.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Volume" 0.75
>
<VST "VST: Tremolator (SoundToys)" "Tremolator.dll" 0 "" 1414677358<56535454524D4C> ""
0 0
PARAM 0 "Depth" 0.3
PARAM 1 "Rate" 0.5
PARAM 2 "Mix" 0.2
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 6 - VOZ PRINCIPAL (ITHAN NY)
; Voz con autotune pesado, reverb corto
; Procesamiento tipo trap/drill chileno
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000006}
NAME "VOZ LEAD - ITHAN NY"
PEAKCOL 16711680
BEAT -1
AUTOMODE 0
VOLPAN 0.9 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; Compresor VCA para controlar dinamica de voz
<VST "VST3: VC 160 FX (Audified)" "VC 160.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Threshold" 0.55
PARAM 1 "Ratio" 0.7
PARAM 2 "Attack" 0.3
PARAM 3 "Release" 0.45
>
; Saturacion suave en voz
<VST "VST: Radiator (SoundToys)" "Radiator.dll" 0 "" 1380012882<56535452414449> ""
0 0
PARAM 0 "Drive" 0.15
PARAM 1 "Mix" 0.3
>
; Delay en voz estilo trap (quarter note)
<VST "VST: EchoBoy (SoundToys)" "EchoBoy.dll" 0 "" 1163022169<565354454348> ""
0 0
PARAM 0 "Mix" 0.2
PARAM 1 "Time" 0.5
PARAM 2 "Feedback" 0.3
>
; Reverb de sala corta
<VST "VST: EffectRack (SoundToys)" "EffectRack.dll" 0 "" 0<> ""
0 0
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 7 - VOZ TUNECHIKIDD
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000007}
NAME "VOZ - TUNECHIKIDD"
PEAKCOL 16744448
BEAT -1
AUTOMODE 0
VOLPAN 0.8 -0.15
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST3: VC 76 FX (Audified)" "VC 76.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Threshold" 0.5
PARAM 1 "Ratio" 0.65
>
<VST "VST: EchoBoy (SoundToys)" "EchoBoy.dll" 0 "" 1163022169<565354454348> ""
0 0
PARAM 0 "Mix" 0.18
PARAM 1 "Time" 0.48
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 8 - VOZ NICKOOG CLK
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000008}
NAME "VOZ - NICKOOG CLK"
PEAKCOL 16744448
BEAT -1
AUTOMODE 0
VOLPAN 0.8 0.15
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST3: VC 76 FX (Audified)" "VC 76.vst3" 0 "" 0<> ""
0 0
PARAM 0 "Threshold" 0.5
PARAM 1 "Ratio" 0.65
>
<VST "VST: EchoBoy (SoundToys)" "EchoBoy.dll" 0 "" 1163022169<565354454348> ""
0 0
PARAM 0 "Mix" 0.18
PARAM 1 "Time" 0.48
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 9 - ARMONIAS VOZ (dobles y harmonizer)
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000009}
NAME "HARMONIAS - VOZ DOUBLES"
PEAKCOL 8388736
BEAT -1
AUTOMODE 0
VOLPAN 0.6 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST: LittleAlterBoy (SoundToys)" "LittleAlterBoy.dll" 0 "" 0<> ""
0 0
PARAM 0 "Pitch" -0.33
PARAM 1 "Mix" 0.5
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 10 - HI-HAT / PERCUSSION FX
; Hi-hats en triplets, shakers, claps
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000010}
NAME "HI-HATS y PERCUSION"
PEAKCOL 16776960
BEAT -1
AUTOMODE 0
VOLPAN 0.7 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
<VST "VST: FilterFreak1 (SoundToys)" "FilterFreak1.dll" 0 "" 0<> ""
0 0
PARAM 0 "Cutoff" 0.65
PARAM 1 "Resonance" 0.3
PARAM 2 "Mix" 0.4
>
<VST "VST: MicroShift (SoundToys)" "MicroShift.dll" 0 "" 0<> ""
0 0
PARAM 0 "Width" 0.5
PARAM 1 "Mix" 0.4
>
>
</TRACK>
; -------------------------------------------------------
; TRACK 11 - BUS MASTER MIX
; Procesamiento final del mix
; -------------------------------------------------------
<TRACK {11111111-0001-0001-0001-000000000011}
NAME "MASTER BUS"
PEAKCOL 16777215
BEAT -1
AUTOMODE 0
VOLPAN 1.0 0
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 1 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 -1 -1 -1
SEL 0
REC 0 0 1 0 0 0 0
VU 2
TRACKHEIGHT 70 0 0 0 0 0
NCHAN 2
FX 1
<FXCHAIN
WNDRECT 0 0 0 0
SHOW 0
LASTSEL 0
DOCKED 0
; EQ inteligente en master
<VST "VST: Gullfoss (Soundtheory)" "Gullfoss.dll" 0 "" 0<> ""
0 0
PARAM 0 "Recover" 0.3
PARAM 1 "Tame" 0.4
PARAM 2 "Bias" 0.0
>
; Limitador / glue final
<VST "VST: TheGlue (Cytomic)" "Cytomic.dll" 0 "" 0<> ""
0 0
PARAM 0 "Threshold" 0.8
PARAM 1 "Ratio" 0.6
PARAM 2 "Attack" 0.2
PARAM 3 "Release" 0.4
>
>
</TRACK>
</REAPER_PROJECT>

BIN
ejemplos/ejemplo1.mp3 Normal file

Binary file not shown.

BIN
ejemplos/ejemplo2.mp3 Normal file

Binary file not shown.

View File

@@ -0,0 +1,148 @@
<REAPER_PROJECT 0.1 "7.65/win64" 1777819992 0
<NOTES 0 2
>
RIPPLE 0 0
GROUPOVERRIDE 0 0 0 0
AUTOXFADE 129
ENVATTACH 3
POOLEDENVATTACH 0
TCPUIFLAGS 0
MIXERUIFLAGS 11 48
ENVFADESZ10 40
PEAKGAIN 1
FEEDBACK 0
PANLAW 1
PROJOFFS 0 0 0
MAXPROJLEN 0 0
GRID 3199 8 1 8 1 0 0 0
TIMEMODE 1 5 -1 30 0 0 -1 0
VIDEO_CONFIG 0 0 65792
PANMODE 3
PANLAWFLAGS 3
CURSOR 0
ZOOM 100 0 0
VZOOMEX 6 0
USE_REC_CFG 0
RECMODE 1
SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0
LOOP 0
LOOPGRAN 0 4
RECORD_PATH "Media" ""
<RECORD_CFG
ZXZhdxgAAQ==
>
<APPLYFX_CFG
>
RENDER_FILE ""
RENDER_PATTERN ""
RENDER_FMT 0 2 0
RENDER_1X 0
RENDER_RANGE 1 0 0 0 1000
RENDER_RESAMPLE 3 0 1
RENDER_ADDTOPROJ 0
RENDER_STEMS 0
RENDER_DITHER 0
RENDER_TRIM 0.000001 0.000001 0 0
TIMELOCKMODE 1
TEMPOENVLOCKMODE 1
ITEMMIX 1
DEFPITCHMODE 589824 0
TAKELANE 1
SAMPLERATE 44100 0 0
<RENDER_CFG
ZXZhdxgAAQ==
>
LOCK 1
<METRONOME 6 2
VOL 0.25 0.125
BEATLEN 4
FREQ 1760 880 1
SAMPLES "" "" "" ""
SPLIGNORE 0 0
SPLDEF 2 660 "" 0 ""
SPLDEF 3 440 "" 0 ""
PATTERN 0 169
PATTERNSTR ABBB
MULT 1
>
GLOBAL_AUTO -1
TEMPO 120 4 4 0
PLAYRATE 1 0 0.25 4
SELECTION 0 0
SELECTION2 0 0
MASTERAUTOMODE 0
MASTERTRACKHEIGHT 0 0
MASTERPEAKCOL 16576
MASTERMUTESOLO 0
MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 0
MASTERHWOUT 0 0 1 0 0 0 0 -1
MASTER_NCH 2 2
MASTER_VOLUME 1 0 -1 -1 1
MASTER_PANMODE 3
MASTER_PANLAWFLAGS 3
MASTER_FX 1
MASTER_SEL 0
<MASTERPLAYSPEEDENV
EGUID {DEF87440-E07C-4B72-B9F8-D2AC60A0D0AC}
ACT 0 -1
VIS 0 1 1
LANEHEIGHT 0 0
ARM 0
DEFSHAPE 0 -1 -1
>
<TEMPOENVEX
EGUID {15E58A72-7149-4783-9A04-838503786012}
ACT 1 -1
VIS 1 0 1
LANEHEIGHT 0 0
ARM 0
DEFSHAPE 1 -1 -1
>
RULERHEIGHT 86 86
RULERLANE 1 4 "" 0 -1
RULERLANE 2 8 "" 0 -1
<PROJBAY
>
<TRACK {2BF702FB-DCC4-4D83-88F1-7B4A8CC8C8A6}
NAME ""
PEAKCOL 16576
BEAT -1
AUTOMODE 0
PANLAWFLAGS 3
VOLPAN 1 0 -1 -1 1
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 0 0 0 0
FIXEDLANES 9 0 0 0 0
LANEREC -1 -1 -1 0
SEL 1
REC 0 0 1 0 0 0 0 0
VU 64
TRACKHEIGHT 0 0 0 0 0 0 0
INQ 0 0 0 0.5 100 0 0 100
NCHAN 2
FX 1
TRACKID {2BF702FB-DCC4-4D83-88F1-7B4A8CC8C8A6}
PERF 0
MIDIOUT -1
MAINSEND 1 0
<FXCHAIN
WNDRECT 24 52 655 408
SHOW 0
LASTSEL 0
DOCKED 0
BYPASS 0 0 0
<VST "VST3: Pro-Q 3 (FabFilter)" "FabFilter Pro-Q 3.vst3" 0 "" 756089518{72C4DB717A4D459AB97E51745D84B39D} ""
>
PRESETNAME "Program 1"
FLOATPOS 0 0 0 0
FXID {817BC720-1D44-41DB-99A6-3C29BB5029B5}
WAK 0 0
>
>
<EXTENSIONS
>
>

37136
output/all_plugins_v2.rpp Normal file

File diff suppressed because it is too large Load Diff

1103
output/del_lune_fixed.rpp Normal file

File diff suppressed because it is too large Load Diff

34055
output/parsed_plugins.json Normal file

File diff suppressed because it is too large Load Diff

21635
output/registry_code.py Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,155 @@
<REAPER_PROJECT 0.1 "7.65/win64" 1777838611 0
<NOTES 0 2
>
RIPPLE 0 0
GROUPOVERRIDE 0 0 0 0
AUTOXFADE 129
ENVATTACH 3
POOLEDENVATTACH 0
TCPUIFLAGS 0
MIXERUIFLAGS 11 48
ENVFADESZ10 40
PEAKGAIN 1
FEEDBACK 0
PANLAW 1
PROJOFFS 0 0 0
MAXPROJLEN 0 0
GRID 3199 8 1 8 1 0 0 0
TIMEMODE 1 5 -1 30 0 0 -1 0
VIDEO_CONFIG 0 0 65792
PANMODE 3
PANLAWFLAGS 3
CURSOR 0
ZOOM 100 0 0
VZOOMEX 6 0
USE_REC_CFG 0
RECMODE 1
SMPTESYNC 0 30 100 40 1000 300 0 0 1 0 0
LOOP 0
LOOPGRAN 0 4
RECORD_PATH "Media" ""
<RECORD_CFG
ZXZhdxgAAQ==
>
<APPLYFX_CFG
>
RENDER_FILE ""
RENDER_PATTERN ""
RENDER_FMT 0 2 0
RENDER_1X 0
RENDER_RANGE 1 0 0 0 1000
RENDER_RESAMPLE 3 0 1
RENDER_ADDTOPROJ 0
RENDER_STEMS 0
RENDER_DITHER 0
RENDER_TRIM 0.000001 0.000001 0 0
TIMELOCKMODE 1
TEMPOENVLOCKMODE 1
ITEMMIX 1
DEFPITCHMODE 589824 0
TAKELANE 1
SAMPLERATE 44100 0 0
<RENDER_CFG
ZXZhdxgAAQ==
>
LOCK 1
<METRONOME 6 2
VOL 0.25 0.125
BEATLEN 4
FREQ 1760 880 1
SAMPLES "" "" "" ""
SPLIGNORE 0 0
SPLDEF 2 660 "" 0 ""
SPLDEF 3 440 "" 0 ""
PATTERN 0 169
PATTERNSTR ABBB
MULT 1
>
GLOBAL_AUTO -1
TEMPO 120 4 4 0
PLAYRATE 1 0 0.25 4
SELECTION 0 0
SELECTION2 0 0
MASTERAUTOMODE 0
MASTERTRACKHEIGHT 0 0
MASTERPEAKCOL 16576
MASTERMUTESOLO 0
MASTERTRACKVIEW 0 0.6667 0.5 0.5 0 0 0 0 0 0 0 0 0 0 0
MASTERHWOUT 0 0 1 0 0 0 0 -1
MASTER_NCH 2 2
MASTER_VOLUME 1 0 -1 -1 1
MASTER_PANMODE 3
MASTER_PANLAWFLAGS 3
MASTER_FX 1
MASTER_SEL 0
<MASTERPLAYSPEEDENV
EGUID {DEF87440-E07C-4B72-B9F8-D2AC60A0D0AC}
ACT 0 -1
VIS 0 1 1
LANEHEIGHT 0 0
ARM 0
DEFSHAPE 0 -1 -1
>
<TEMPOENVEX
EGUID {15E58A72-7149-4783-9A04-838503786012}
ACT 0 -1
VIS 1 0 1
LANEHEIGHT 0 0
ARM 0
DEFSHAPE 1 -1 -1
>
RULERHEIGHT 86 86
RULERLANE 1 4 "" 0 -1
RULERLANE 2 8 "" 0 -1
<PROJBAY
>
<TRACK {CC7AF445-1A51-4FE2-A089-34F7AE50ED82}
NAME ""
PEAKCOL 16576
BEAT -1
AUTOMODE 0
PANLAWFLAGS 3
VOLPAN 1 0 -1 -1 1
MUTESOLO 0 0 0
IPHASE 0
PLAYOFFS 0 1
ISBUS 0 0
BUSCOMP 0 0 0 0 0
SHOWINMIX 1 0.6667 0.5 1 0.5 0 0 0 0
FIXEDLANES 9 0 0 0 0
SEL 1
REC 0 0 1 0 0 0 0 0
VU 64
TRACKHEIGHT 0 0 0 0 0 0 0
INQ 0 0 0 0.5 100 0 0 100
NCHAN 2
FX 1
TRACKID {CC7AF445-1A51-4FE2-A089-34F7AE50ED82}
PERF 0
MIDIOUT -1
MAINSEND 1 0
<FXCHAIN
WNDRECT 24 52 655 408
SHOW 0
LASTSEL 0
DOCKED 0
BYPASS 0 0 0
<VST "VST: Decapitator (SoundToys)" Decapitator.dll 0 "" 1400128611<56535453744463646563617069746174> ""
Y0R0U+5e7f4CAAAAAQAAAAAAAAACAAAAAAAAAAIAAAABAAAAAAAAAAIAAAAAAAAAOwIAAAEAAAAAAAAA
V0lER0VUID0gRGVjYXBpdGF0b3I7DVZFUlNJT04gPSA0Ow1TQVZFRF9CWV9WRVJTSU9OID0gNS4wLjE7DVBSRVNFVCA9MDEwMTAwMDAwNDg4MDEwMTAwMDFLNF1IRlE3
Sz9XPURnP1s8TEVUOD1aOl1IVlhZPlI+XUk0Z2xWYTVdMlhgNVtIMEpNZ2ZjYmM+RGw5WzRDS2tpW1ZnM25nP0pdXVxKTUZjNGU1XV5vZmM+QWw0TjJqWjlkOmxsOVI7
Wjs2SUpCTVY4N1U4XTlbPWtMbEdbaVo5RlJaXVVpMk1NQEFsbVViRWZlQT40blFjbUI7ODxSbFE+bG08WjA2MGs5QT9uaFI3MVxQWm5BYjo3TGw8ZTRSUl0/RmozRmdt
RDhvMGxKPzFHYF9tZFpBaWUzNmtqVjZvXWxtMzE4bkxtUDhfNlNlajRFVj1jMltlRDtFaDljVWlNajlQVEs3VkhfZEtoYzRpP11jUj1EZF5TXltiSjdDNEExMTlfVk5h
bTxWX2VrSlJlNzRmSmNIQExlMUVMWWBISEA9YjFZWEA2NjZYZ1xGWmE3NlFYQjYxVV9eaTpRXVFBPmNkazAxPDlqMDQxa1tAaU1MMG1HZzpjXlM4OGhVS1VSMVZZbmc/
TTJYYUdQTT9Ca10wOWI4ZW8wakZMWGteTk83U2I+ND5hUTdUVG1gM0VDVUc6UFRbbE5bTkZTUFFiYllCb21obDY/OjM4RjpvNVdiS2BNbGAwMDA1UDAwMDA7DQ==
AAAAAAAA
>
PRESETNAME Default
FLOATPOS 0 0 0 0
FXID {5DAAF240-C12C-4450-B2A4-31D522D4B9C5}
WAK 0 0
>
>
<EXTENSIONS
>
>

1218
output/tu_diablo_fixed.rpp Normal file

File diff suppressed because it is too large Load Diff

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'])}")

View File

@@ -0,0 +1,70 @@
# Design: `fix-drum-sample-paths`
**Type**: bugfix
**Bug**: Drum samples silent — `samples_dir/output/samples/` paths point to nonexistent directory
**Root cause**: `compose_full_track.py` passes bare filenames to skeleton, which joins them with `samples_dir` expecting `output/samples/`. But `pick()` returns absolute paths from the sample library. The mismatch makes FL Studio look in the wrong place.
---
## Changes
### 1. `scripts/compose_full_track.py` — lines 201210
**Problem**: `samples` dict receives bare filenames (`ch10_perc`, etc.) but skeleton needs absolute paths.
**Fix**: Change the `samples` dict to use the `path*` variables returned by `pick()`, which are absolute paths.
```python
# Before (broken)
samples = {
"channel10": ch10_perc, # bare filename e.g. "perc_loop.wav"
...
}
# After (fixed)
samples = {
"channel10": path10, # absolute path e.g. "C:\Users\...\library\perc_loop.wav"
...
}
```
The `path*` variables already exist and hold `m.get("original_path")` from `pick()`. No other code needs changing — the samples dict key→channel mapping is already correct.
---
### 2. `src/flp_builder/skeleton.py` — `_patch_sample_path()` around line 361
**Problem**: `_patch_sample_path()` always joins `wav_name` with `self.samples_dir`, which breaks when `wav_name` is already an absolute path (as it now is after fix #1).
**Fix**: Add an absolute-path guard before joining:
```python
# In _patch_sample_path(), replace line 361:
# Before
full_path = os.path.join(self.samples_dir, wav_name)
# After
if Path(wav_name).is_absolute():
full_path = wav_name
else:
full_path = os.path.join(self.samples_dir, wav_name)
```
This preserves existing behavior for relative paths (used elsewhere) while correctly handling the absolute paths now being passed.
---
## Files affected
| File | Lines | Change |
|------|-------|--------|
| `scripts/compose_full_track.py` | 201210 | Use `path*` vars instead of `ch*_var` vars |
| `src/flp_builder/skeleton.py` | 361 | Add `is_absolute()` guard |
---
## Verification
- **Before fix**: `compose_full_track.py` prints `ch10: some_file.wav` (bare filename)
- **After fix**: `compose_full_track.py` prints full paths, skeleton detects them and skips join
- Run `python scripts/compose_full_track.py` — FLP should load drum samples correctly

View File

@@ -0,0 +1,200 @@
# Tasks: `reggaeton-professional-mix`
## type: architecture
## status: draft
---
## Phase 1 — Note Validity (Foundation)
### 1.1 Clamp pitch in `encode_notes_block()`
**File**: `src/flp_builder/events.py`
**Location**: `encode_notes_block()`, lines 201223
**Change**: Replace `key=key & 0x7F` with explicit `max(0, min(127, key))`.
**Why**: `key & 0x7F` silently wraps values ≥128 rather than clamping, causing the FL Studio "invalid notes" warning.
**Validation**: Verify notes with key=128 produce no warning; key=0 and key=127 work.
### 1.2 Clamp velocity in `encode_notes_block()`
**File**: `src/flp_builder/events.py`
**Location**: `encode_notes_block()`, line 219 — `velocity=velocity & 0x7F`
**Change**: Replace with `max(1, min(127, velocity))`.
**Why**: Velocity 0 is invalid in MIDI; `& 0x7F` allows 0 from input.
**Validation**: velocity=0 → clamped to 1; velocity=127 stays 127; velocity=200 → clamped to 127.
### 1.3 Clamp duration ≥ 1 in `encode_notes_block()`
**File**: `src/flp_builder/events.py`
**Location**: `encode_notes_block()`, line 212 — `length=max(length, 1)`
**Change**: Already present. Confirm `max(length, 1)` is retained (was added in prior fix).
**Why**: Duration of 0 would produce 0-byte notes, which FL Studio rejects.
**Validation**: length=0 → stays 1 after max(); length=1 → stays 1.
---
## Phase 2 — Mix Settings
### 2.1 Add mix fields to `PatternDef`
**File**: `src/flp_builder/schema.py`
**Location**: `PatternDef` dataclass, lines 7287
**Change**: Add four fields to `PatternDef`:
```python
volume: float = 0.85 # 0.01.0, default -1.5 dB
pan: float = 0.0 # -1.0 to 1.0, center
reverb_send: float = 0.2 # 0.01.0
delay_send: float = 0.1 # 0.01.0
```
Add `volume`, `pan`, `reverb_send`, `delay_send` to `_PATTERN_KEYS` frozenset for validation.
**Why**: Needed so the composer can specify per-pattern mix settings; previously all channels used defaults.
**Validation**: JSON round-trip: `PatternDef(..., volume=0.8, pan=-0.3, reverb_send=0.4, delay_send=0.2)` survives `to_json()``from_json()`.
### 2.2 Patch mix events in `ChannelSkeletonLoader`
**File**: `src/flp_builder/skeleton.py`
**Location**: `ChannelSkeletonLoader.load()` — after sample patching, before assembly
**Change**: Accept `mix_map: dict[int, dict]` parameter (channel_index → {volume, pan, reverb_send, delay_send}). After patching samples, append/replace word events:
- `ChVolWord` (72): volume as 0255 word (`int(vol * 200)`, 200 = 0 dB)
- `ChPanWord` (73): pan as signed 0255 (`int((pan + 1.0) * 127.5)`)
- `ChReverb` (139): reverb_send as 0255 (`int(send * 255)`)
- `ChStereoDelay` (85): delay_send as 0255 (`int(send * 255)`)
**Why**: FL Studio stores these as word events on the channel; skeleton loader must inject them.
**Validation**: Load skeleton with mix_map, verify output bytes contain the four event IDs with expected values.
### 2.3 Pass mix_map from `FLPBuilder` to skeleton loader
**File**: `src/flp_builder/builder.py` (assumed existing — if not found, locate)
**Location**: `FLPBuilder.build()` call to `ChannelSkeletonLoader.load()`
**Change**: Build mix_map from `PatternDef` fields and pass to loader.
**Why**: Bridges schema (PatternDef) to FLP rendering (skeleton).
**Note**: If builder.py does not yet accept/forward mix_map, add it.
---
## Phase 3 — Groove
### 3.1 Add `groove_strength` to rhythm generators
**File**: `src/composer/rhythm.py`
**Location**: All generator functions + `get_notes()` dispatcher
**Change**: Add `groove_strength: float = 0.0` parameter (0.0 = no groove, 1.0 = max groove) to all generators:
- `kick_main_notes`, `kick_sparse_notes`, `kick_outro_notes`
- `snare_verse_notes`, `snare_fill_notes`, `snare_outro_notes`
- `hihat_16th_notes`, `hihat_8th_notes`
- `clap_24_notes`, `perc_combo_notes`, `rim_build_notes`
Apply groove via `_apply_groove(note_dict, groove_strength)` helper:
- Velocity jitter: `vel ±= random.randint(0, int(5 + groove_strength * 10))`
- Positional nudge: `pos += random.uniform(-groove_strength * 0.02, groove_strength * 0.02)`
- Swing (every other 16th): shift even-index 16ths forward by `swing_amount = groove_strength * 0.1`
**Why**: Groove makes drum patterns feel human and avoids mechanical timing.
**Validation**: `get_notes("kick_main_notes", bars=1, groove_strength=0.5)` returns notes with positional variation vs. `groove_strength=0.0`.
### 3.2 Update `get_notes()` dispatcher signature
**File**: `src/composer/rhythm.py`
**Location**: `get_notes()`, lines 303311
**Change**: Add `groove_strength: float = 0.0` to dispatcher and pass to generator.
**Why**: All generators now accept groove_strength; dispatcher must forward it.
---
## Phase 4 — Melodic Humanization
### 4.1 Add `humanize` to melodic generators
**File**: `src/composer/melodic.py`
**Location**: `bass_tresillo()`, `lead_hook()`, `chords_block()`, `pad_sustain()`
**Change**: Add `humanize: float = 0.0` parameter (0.0 = no humanization, 1.0 = max humanization).
Apply humanization via `_apply_humanize(note_dict, humanize)` helper:
- Velocity jitter: `vel ±= random.randint(0, int(humanize * 5))` — gentler than drums
- Micro-timing: `pos += random.uniform(-humanize * 0.03, humanize * 0.03)` — tighter than drums
**Why**: Melodic instruments (bass, lead, pad) need subtler humanization than drums to avoid sounding out of tune.
**Validation**: `bass_tresillo(key="Am", bars=1, humanize=0.5)` vs. `humanize=0.0` shows velocity/position variance.
### 4.2 Add humanization to melodic dispatcher (if exists)
**File**: `src/composer/melodic.py`
**Location**: Any `get_melodic_notes()` dispatcher function
**Change**: Forward `humanize` parameter to all generators.
**Why**: Single entry point for melodic generation.
---
## Phase 5 — Transitions
### 5.1 Create FX channel (ch21) with riser sample
**File**: `scripts/compose_full_track.py`
**Location**: After melodic track setup, before `SongDefinition`
**Change**: Add FX channel (channel_index=21) with role `"fx"` and riser sample from `SampleSelector`. Use `rim_build_notes` pattern at channel 21 for riser fills. Place riser clips before chorus sections at bars 19 and 43 (1-bar riser items).
**Why**: Transitions between verse→chorus need riser FX to build energy.
**Note**: Verify ch21 is available (not used by drums/melodic); if conflicts, use next available.
### 5.2 Add rim_build pre-chorus fills
**File**: `scripts/compose_full_track.py`
**Location**: Arrangement items for bars 1819 (pre-chorus 1) and 4243 (pre-chorus 2)
**Change**: Insert 2-bar rim_build pattern before each chorus:
- Pattern id 9: `rim_build_notes` at channel 13
- Place at bars 1819 for chorus 1 (bars 2031)
- Place at bars 4243 for chorus 2 (bars 4455)
**Why**: Rim roll builds tension going into the chorus ("rim_build" generator is designed for this).
### 5.3 Place riser FX before choruses
**File**: `scripts/compose_full_track.py`
**Location**: Arrangement items, pre-chorus sections
**Change**: Add 1-bar riser clip at bars 19 and 43 (before chorus 1 and 2). Riser should be on a dedicated FX track (arrangement track 7).
**Why**: Riser sample creates sonic "lift" into the chorus section.
---
## Phase 6 — Compose Script Update
### 6.1 Add reggaeton-standard mix values to PatternDefs
**File**: `scripts/compose_full_track.py`
**Location**: `patterns` list, lines 128137
**Change**: Set explicit mix values per pattern:
- `kick_main`: volume=0.88, pan=0.0, reverb_send=0.1, delay_send=0.0
- `kick_sparse`: volume=0.82, pan=0.0, reverb_send=0.1, delay_send=0.0
- `snare_main`: volume=0.80, pan=0.05, reverb_send=0.35, delay_send=0.15
- `hihat_main`: volume=0.65, pan=0.0, reverb_send=0.25, delay_send=0.0
- `clap_main`: volume=0.82, pan=0.0, reverb_send=0.45, delay_send=0.1
- `perc_main`: volume=0.72, pan=-0.15, reverb_send=0.3, delay_send=0.1
- `perc2_main`: volume=0.72, pan=0.15, reverb_send=0.3, delay_send=0.1
**Why**: These values are the reggaeton-standard mix targets documented in the design.
### 6.2 Enable `groove_strength` on drum patterns
**File**: `scripts/compose_full_track.py`
**Location**: `patterns` list — update call sites that generate drum notes
**Change**: Pass `groove_strength=0.3` to drum rhythm generators in compose script.
**Why**: Drums benefit from subtle groove humanization to avoid mechanical feel.
**Note**: This requires `get_notes()` to accept `groove_strength` (Phase 3 must be complete first).
### 6.3 Enable `humanize` on melodic tracks
**File**: `scripts/compose_full_track.py`
**Location**: `section_notes()` calls for bass, lead, pad, pluck
**Change**: Pass `humanize=0.2` to melodic generators.
**Why**: Melodic humanization makes bass/lead/pad feel organic and less quantized.
### 6.4 Add transition sections to arrangement
**File**: `scripts/compose_full_track.py`
**Location**: `items` list — insert pre-chorus transition blocks
**Change**: Insert pre-chorus sections at bars 1819 and 4243:
- Rim_build pattern at channel 13 (bars 1819 and 4243)
- Riser FX at channel 21 (bars 19 and 43, 1 bar each)
**Why**: Proper transitions create professional song flow rather than abrupt section changes.
---
## Dependencies
- Phase 2 depends on Phase 1 (schema fields needed before builder integration)
- Phase 3 can run in parallel with Phase 4 (separate modules)
- Phase 5 depends on Phase 2 (mix infrastructure for FX sends)
- Phase 6 depends on Phases 2, 3, 4, 5 (all capabilities wired up)
## Files Modified (summary)
| File | Phases |
|------|--------|
| `src/flp_builder/events.py` | 1 |
| `src/flp_builder/schema.py` | 2 |
| `src/flp_builder/skeleton.py` | 2 |
| `src/flp_builder/builder.py` | 2 |
| `src/composer/rhythm.py` | 3 |
| `src/composer/melodic.py` | 4 |
| `scripts/compose_full_track.py` | 5, 6 |

597
src/composer/templates.py Normal file
View File

@@ -0,0 +1,597 @@
"""RPP template extraction and generation.
Extracts track/FX chain structures from professionally-built .rpp files,
fixes GUIDs with real values from reaper-vstplugins64.ini, and generates
working .rpp files.
Usage:
from src.composer.templates import extract_template, generate_rpp
template = extract_template("ejemplos/TU_DIABLO_ITHAN_NY.rpp")
generate_rpp(template, "output/tu_diablo_fixed.rpp")
"""
from __future__ import annotations
import re
import uuid
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from rpp import Element, dumps
from src.reaper_builder import PLUGIN_REGISTRY, ALIAS_MAP, vst2_element, vst3_element, VST2_REGISTRY, VST3_REGISTRY, PLUGIN_PRESETS
# ---------------------------------------------------------------------------
# Template data classes
# ---------------------------------------------------------------------------
@dataclass
class PluginTemplate:
"""A plugin (instrument or effect) within a track FX chain."""
name: str # Registry key (e.g. "Serum2", "Decapitator")
path: str # Filename on disk (e.g. "Serum2.vst3", "Decapitator.dll")
display_name: str # Full REAPER display name
uniqueid_guid: str # uniqueid{GUID} from registry
preset_data: list[str] | None = None
is_vst2: bool = False # True for SoundToys .dll plugins
index: int = 0 # Position in FX chain
def build_element(self) -> Element:
"""Build a VST Element for this plugin."""
if self.is_vst2:
return vst2_element(self.display_name, self.path, self.uniqueid_guid, self.preset_data)
return vst3_element(self.display_name, self.path, self.uniqueid_guid, self.preset_data)
@dataclass
class TrackTemplate:
"""A track with its FX chain, extracted from an RPP template."""
name: str
volume: float = 0.85
pan: float = 0.0
color: int = 0
is_bus: bool = False
plugins: list[PluginTemplate] = field(default_factory=list)
@dataclass
class TemplateProject:
"""A complete project template extracted from an RPP file."""
name: str
bpm: float
key: str = "Am"
time_sig_num: int = 4
time_sig_den: int = 4
tracks: list[TrackTemplate] = field(default_factory=list)
def generate_rpp(self, output_path: str | Path) -> None:
"""Generate a working .rpp file from this template.
Uses RPPBuilder to produce REAPER 7.65 format with proper GUIDs,
FXCHAIN structure, and base64 preset data.
"""
from src.core.schema import SongDefinition, SongMeta, TrackDef, PluginDef
from src.reaper_builder import RPPBuilder
meta = SongMeta(
bpm=self.bpm,
key=self.key,
title=self.name,
time_sig_num=self.time_sig_num,
time_sig_den=self.time_sig_den,
)
# Convert TrackTemplate → TrackDef
tracks: list[TrackDef] = []
for tt in self.tracks:
# Build PluginDef list for RPPBuilder
plugin_defs: list[PluginDef] = []
for pt in tt.plugins:
plugin_defs.append(PluginDef(
name=pt.name,
path=pt.path,
index=pt.index,
preset_data=pt.preset_data,
))
track_def = TrackDef(
name=tt.name,
volume=tt.volume,
pan=tt.pan,
color=tt.color,
clips=[],
plugins=plugin_defs,
)
tracks.append(track_def)
song = SongDefinition(meta=meta, tracks=tracks)
builder = RPPBuilder(song)
builder.write(str(output_path))
# ---------------------------------------------------------------------------
# Plugin substitution map (for non-existent plugins in source RPPs)
# ---------------------------------------------------------------------------
# Maps plugin names that appear in source RPPs but don't exist → substitute plugin key
SUBSTITUTE: dict[str, str] = {
# Transient Master not installed — replace with FabFilter Pro-C 2 (similar transient shaping)
"Transient Master.dll": "FabFilter_Pro-C_2",
"NI Transient Master": "FabFilter_Pro-C_2",
"Transient Master (Native Instruments)": "FabFilter_Pro-C_2",
# The Glue standalone .dll doesn't exist — use VST2 version from registry
"Cytomic.dll": "The_Glue",
# ValhallaDelay_x64.dll not in scan — use ValhallaDelay (VST2)
"ValhallaDelay_x64.dll": "ValhallaDelay",
# Pigments.exe wrong extension
"Pigments.exe": "Pigments",
# Gullfoss VST2 .dll — use registry keys
"Gullfoss.dll": "Gullfoss",
"Gullfoss Master.dll": "Gullfoss_Master",
"Gullfoss Live.dll": "Gullfoss_Live",
# VC 160/76 — fix display names from "VC 160 FX (Audified)" → registry keys
"VC 160.vst3": "VC_160",
"VC 76.vst3": "VC_76",
"VC 2A.vst3": "VC_2A",
# Kontakt 7 — fix display name
"Kontakt 7.vst3": "Kontakt_7",
# Source RPP display name → registry key (for non-matching display names)
"VST3: VC 160 FX (Audified)": "VC_160",
"VST3: VC 76 FX (Audified)": "VC_76",
"VST3: VC 2A FX (Audified)": "VC_2A",
"VST: TheGlue (Cytomic)": "The_Glue",
"VST: ValhallaDelay x64 (Valhalla DSP)": "ValhallaDelay",
"VST3: Kontakt 7 (Native Instruments)": "Kontakt_7",
"VST: Gullfoss Master (Soundtheory)": "Gullfoss_Master",
"VST: Gullfoss (Soundtheory)": "Gullfoss",
"VST: Gullfoss Live (Soundtheory)": "Gullfoss_Live",
}
# ---------------------------------------------------------------------------
# GUID lookup helpers
# ---------------------------------------------------------------------------
def _lookup_vst3(name: str) -> tuple[str, str, str] | None:
"""Look up VST3 plugin by registry key or filename."""
# Try exact key match
if name in VST3_REGISTRY:
return VST3_REGISTRY[name]
# Try filename match
for display_name, filename, uid_guid in VST3_REGISTRY.values():
if filename == name:
return (display_name, filename, uid_guid)
return None
def _lookup_vst2(name: str) -> tuple[str, str, str] | None:
"""Look up VST2/SoundToys plugin by registry key or filename."""
# Try exact key match
if name in VST2_REGISTRY:
return VST2_REGISTRY[name]
# Try filename match
for display_name, filename, uid_guid in VST2_REGISTRY.values():
if filename == name:
return (display_name, filename, uid_guid)
return None
def _make_plugin_template(
name: str,
path: str,
index: int = 0,
) -> PluginTemplate | None:
"""Create a PluginTemplate from plugin name + path, with GUID lookup.
Handles plugin substitution for non-existent plugins.
Returns None if the plugin cannot be resolved.
"""
# Apply substitution if needed
resolved_name = SUBSTITUTE.get(name, name)
resolved_path = SUBSTITUTE.get(path, path)
# Try direct registry key lookup first (covers substituted names)
direct_entry = PLUGIN_REGISTRY.get(resolved_name) or PLUGIN_REGISTRY.get(resolved_path)
if direct_entry:
is_vst2 = direct_entry[2].startswith("<")
entry = direct_entry
else:
# Fall back to path-based detection
is_vst2 = path.endswith(".dll")
if is_vst2:
entry = _lookup_vst2(resolved_path) or _lookup_vst2(resolved_name)
else:
entry = _lookup_vst3(resolved_path) or _lookup_vst3(resolved_name)
if entry:
display_name, filename, uid_guid = entry
preset_data = PLUGIN_PRESETS.get(resolved_name) if is_vst2 else PLUGIN_PRESETS.get(resolved_name)
else:
# Unresolved — use name/path as display name with empty GUID
display_name = f"VST3: {resolved_name}" if not is_vst2 else f"VST: {resolved_name}"
filename = resolved_path
uid_guid = ""
return PluginTemplate(
name=resolved_name,
path=filename,
display_name=display_name,
uniqueid_guid=uid_guid,
preset_data=preset_data,
is_vst2=is_vst2,
index=index,
)
# ---------------------------------------------------------------------------
# RPP parser (regex-based, handles both RPP library format and raw text)
# ---------------------------------------------------------------------------
# Regex patterns for parsing RPP files
_TRACK_RE = re.compile(r'<TRACK\s+\{[^}]+\}', re.IGNORECASE)
_TRACK_CLOSE_RE = re.compile(r'^\s*</TRACK>', re.IGNORECASE)
_NAME_RE = re.compile(r'^\s*NAME\s+"([^"]*)"', re.IGNORECASE)
_VOLPAN_RE = re.compile(r'^\s*VOLPAN\s+([\d.e+-]+)\s+([\d.e+-]+)', re.IGNORECASE)
_ISBUS_RE = re.compile(r'^\s*ISBUS\s+(\d+)', re.IGNORECASE)
_PEAKCOL_RE = re.compile(r'^\s*PEAKCOL\s+(\d+)', re.IGNORECASE)
_FXCHAIN_RE = re.compile(r'^\s*<FXCHAIN', re.IGNORECASE)
_FXCHAIN_CLOSE_RE = re.compile(r'^\s*</FXCHAIN>', re.IGNORECASE)
# VST element: <VST "display name" "filename" 0 "" uniqueid{GUID} "" [preset lines] >
# The header line ends with "" (closing of last string arg) followed by optional preset lines
# and finally a > closing tag on its own line or after preset content.
# The GUID portion (inside {} or <>) may be prefixed with an id number.
# Examples from real RPPs:
# <VST "VST: Decapitator (SoundToys)" "Decapitator.dll" 0 "" 1145980753<GUID> ""
# <VST "VST3: Serum 2" "Serum2.vst3" 0 "" 0<> ""
_VST_RE = re.compile(
r'<VST\s+"([^"]+)"\s+"([^"]+)"\s+0\s+""\s*([\d<>{}a-fA-F0-9]+)\s*""',
re.IGNORECASE,
)
_VST_CLOSE_RE = re.compile(r'^\s*</VST>', re.IGNORECASE)
_PARAM_RE = re.compile(r'^\s*PARAM\s+\d+\s+"[^"]*"\s+', re.IGNORECASE)
_TEMPO_RE = re.compile(r'^\s*TEMPO\s+([\d.e+-]+)\s+(\d+)\s+(\d+)', re.IGNORECASE)
def _strip_invalid_params(lines: list[str]) -> list[str]:
"""Remove invalid PARAM lines from preset data lines.
Claude's RPPs contain lines like:
PARAM 0 "Drive" 0.35
which are NOT valid REAPER tokens. REAPER stores parameter values in
base64 preset data, not as named PARAM lines.
"""
result: list[str] = []
for line in lines:
stripped = line.strip()
# Skip PARAM lines that match our pattern
if _PARAM_RE.match(line):
continue
result.append(line)
return result
def _extract_guid(raw: str, filename: str, display_name: str) -> str:
"""Extract clean GUID from raw GUID string.
Handles formats like:
- "0<>" → lookup from registry (empty/fake GUID)
- "1145980753<56535444454341>" → extract <GUID>
- "691258006{56534558...}" → extract {GUID}
- plain "0<>" or "0{}" → lookup from registry
"""
import re as re_module
raw = raw.strip()
# Try extract <GUID> pattern (angle brackets)
m = re_module.search(r'<([^<>]+)>', raw)
if m:
guid = m.group(1)
# Check if it's a real GUID (not just angle bracket chars)
if guid and guid not in ('<>', '{}', ''):
return guid
# Try extract {GUID} pattern (curly braces)
m = re_module.search(r'\{([^}]+)\}', raw)
if m:
guid = m.group(1)
if guid and guid not in ('{}', ''):
return guid
# Empty or fake GUID — lookup from registry
is_vst2 = filename.endswith(".dll")
key = _resolve_registry_key(display_name, filename, is_vst2)
if key:
if is_vst2:
entry = VST2_REGISTRY.get(key)
else:
entry = VST3_REGISTRY.get(key)
if entry:
return entry[2] # Return the uid_guid from registry
return raw # Return as-is if no lookup found
def _parse_vst_block(lines: list[str], start_idx: int) -> tuple[PluginTemplate | None, int]:
"""Parse a VST element starting at start_idx in lines.
Returns (PluginTemplate, index_of_close_tag) or (None, start_idx) if not a VST block.
"""
line = lines[start_idx].strip()
m = _VST_RE.match(line)
if not m:
return None, start_idx
display_name = m.group(1)
filename = m.group(2)
raw_guid = m.group(3).strip()
# Extract clean GUID from various formats:
# - "0<>" (empty/fake GUID) → replace with registry lookup
# - "1315270729<>" → registry lookup
# - "1145980753<56535444454341>" → extract <GUID> portion
# - "691258006{56534558667350736572756D20320000}" → extract {GUID} portion
uid_guid = _extract_guid(raw_guid, filename, display_name)
# Collect preset data lines until closing >VST tag
preset_lines: list[str] = []
idx = start_idx + 1
while idx < len(lines):
l = lines[idx]
ls = l.strip()
if '</VST' in l or ls == '>' or ls.startswith('>'):
break
if ls.startswith('<VST') or ls.startswith('<TRACK') or ls.startswith('</TRACK') or ls.startswith('</FXCHAIN'):
# Hit next element — not a preset line
break
# Collect line (strip PARAM lines later)
preset_lines.append(l)
idx += 1
preset_lines = _strip_invalid_params(preset_lines)
is_vst2 = filename.endswith(".dll")
registry_key = _resolve_registry_key(display_name, filename, is_vst2)
if registry_key:
if is_vst2:
entry = VST2_REGISTRY.get(registry_key)
else:
entry = VST3_REGISTRY.get(registry_key)
if entry:
disp, fn, guid = entry
uid_guid = guid
display_name = disp
filename = fn
else:
sub_key = SUBSTITUTE.get(filename) or SUBSTITUTE.get(display_name)
if sub_key:
sub_entry = PLUGIN_REGISTRY.get(sub_key)
if sub_entry:
display_name, filename, uid_guid = sub_entry
is_vst2 = uid_guid.startswith("<")
registry_key = sub_key
is_fake_preset = (
not preset_lines
or all(pl.strip() in ("0 0", "0", "") for pl in preset_lines)
)
if is_fake_preset and registry_key:
registry_preset = PLUGIN_PRESETS.get(registry_key)
if registry_preset:
preset_lines = registry_preset
return PluginTemplate(
name=registry_key or display_name,
path=filename,
display_name=display_name,
uniqueid_guid=uid_guid,
preset_data=preset_lines if preset_lines else None,
is_vst2=is_vst2,
index=0,
), idx
def _resolve_registry_key(display_name: str, filename: str, is_vst2: bool) -> str | None:
"""Resolve a plugin display name/filename to a registry key."""
if is_vst2:
for key, (disp, fn, guid) in VST2_REGISTRY.items():
if disp == display_name or fn == filename:
return key
else:
for key, (disp, fn, guid) in VST3_REGISTRY.items():
if disp == display_name or fn == filename:
return key
return None
def _parse_track_block(lines: list[str], start_idx: int) -> tuple[TrackTemplate | None, int]:
"""Parse a TRACK element starting at start_idx in lines.
Returns (TrackTemplate, index_after_close_tag) or (None, start_idx).
"""
line = lines[start_idx].strip()
if not _TRACK_RE.match(line):
return None, start_idx
name = ""
volume = 0.85
pan = 0.0
color = 0
is_bus = False
plugins: list[PluginTemplate] = []
plugin_index = 0
idx = start_idx + 1
in_fxchain = False
while idx < len(lines):
l = lines[idx]
ls = l.strip()
if ls.startswith('</TRACK>') or ls.startswith('</TRACK'):
break
if in_fxchain:
if _FXCHAIN_CLOSE_RE.match(ls):
in_fxchain = False
idx += 1
continue
if _VST_RE.match(ls):
plugin, new_idx = _parse_vst_block(lines, idx)
if plugin:
plugin.index = plugin_index
plugin_index += 1
plugins.append(plugin)
idx = new_idx + 1
continue
else:
if _FXCHAIN_RE.match(ls):
in_fxchain = True
idx += 1
continue
m = _NAME_RE.match(l)
if m:
name = m.group(1)
idx += 1
continue
m = _VOLPAN_RE.match(l)
if m:
try:
volume = float(m.group(1))
except ValueError:
pass
try:
pan = float(m.group(2))
except ValueError:
pass
idx += 1
continue
m = _PEAKCOL_RE.match(l)
if m:
try:
color = int(m.group(1))
except ValueError:
pass
idx += 1
continue
m = _ISBUS_RE.match(l)
if m:
try:
is_bus = int(m.group(1)) == 1
except Value:
pass
idx += 1
continue
idx += 1
if not name:
name = "Unnamed Track"
return TrackTemplate(
name=name,
volume=volume,
pan=pan,
color=color,
is_bus=is_bus,
plugins=plugins,
), idx
def extract_template(rpp_path: str | Path) -> TemplateProject:
"""Extract a TemplateProject from an existing .rpp file.
Parses track structures, FX chains, and plugin configurations,
applying GUID corrections and stripping invalid PARAM lines.
Args:
rpp_path: Path to source .rpp file
Returns:
TemplateProject with all tracks and plugins resolved to registry entries
Raises:
FileNotFoundError: If rpp_path doesn't exist
ValueError: If file can't be parsed as RPP
"""
path = Path(rpp_path)
if not path.exists():
raise FileNotFoundError(f"RPP file not found: {path}")
content = path.read_text(encoding="utf-8")
lines = content.split('\n')
# Extract tempo, key from header comments or TEMPO line
bpm = 120.0
key = "Am"
time_sig_num, time_sig_den = 4, 4
project_name = path.stem
# Parse TEMPO line
for i, line in enumerate(lines):
m = _TEMPO_RE.match(line.strip())
if m:
try:
bpm = float(m.group(1))
except ValueError:
pass
try:
time_sig_num = int(m.group(2))
time_sig_den = int(m.group(3))
except ValueError:
pass
break
# Find project name from comments
for line in lines:
if 'PROYECTO:' in line:
# Extract project name from comment
m = re.search(r'PROYECTO:\s*(.+?)(?:\n|$)', line)
if m:
project_name = m.group(1).strip()
break
# Extract tracks
tracks: list[TrackTemplate] = []
idx = 0
while idx < len(lines):
if _TRACK_RE.match(lines[idx].strip()):
track, new_idx = _parse_track_block(lines, idx)
if track:
tracks.append(track)
idx = new_idx + 1
else:
idx += 1
return TemplateProject(
name=project_name,
bpm=bpm,
key=key,
time_sig_num=time_sig_num,
time_sig_den=time_sig_den,
tracks=tracks,
)
def generate_rpp(template: TemplateProject, output_path: str | Path) -> None:
"""Generate a working .rpp file from a TemplateProject.
Uses RPPBuilder to produce REAPER 7.65 format with correct GUIDs,
proper FXCHAIN structure, and base64 preset data. All plugins are
resolved via VST3_REGISTRY/VST2_REGISTRY.
Args:
template: TemplateProject to generate from
output_path: Destination .rpp path
"""
# Ensure output directory exists
p = Path(output_path)
p.parent.mkdir(parents=True, exist_ok=True)
template.generate_rpp(str(p))

View File

@@ -143,6 +143,7 @@ class PluginDef:
path: str
index: int = 0
params: dict[int, float] = field(default_factory=dict)
preset_data: list[str] | None = None
@dataclass

File diff suppressed because it is too large Load Diff

View File

@@ -337,7 +337,7 @@ class TestVST3GUIDPresence:
# Must contain the GUID token from VST3_REGISTRY["Serum2"]
assert "691258006{56534558667350736572756D20320000}" in content
# Must also contain correct display name and filename
assert "VST3: Serum 2 (Xfer Records)" in content
assert "VST3i: Serum 2 (Xfer Records)" in content
assert "Serum2.vst3" in content
finally:
Path(tmp_path).unlink(missing_ok=True)
@@ -361,7 +361,8 @@ class TestVST3GUIDPresence:
# Must contain the GUID token from VST3_REGISTRY["FabFilter Pro-Q 3"]
assert "756089518{72C4DB717A4D459AB97E51745D84B39D}" in content
assert "VST3: Pro-Q 3 (FabFilter)" in content
assert "FabFilter Pro-Q 3.vst3" in content
# Filename in RPP is "FabFilter" (shared binary for all FabFilter plugins)
assert "FabFilter 0" in content
finally:
Path(tmp_path).unlink(missing_ok=True)
@@ -424,9 +425,10 @@ class TestVST3PresetData:
"""
meta = SongMeta(bpm=95, key="Am", title="VST3 Preset Test")
# Use actual filenames from registry so _build_plugin recognizes them as VST3
from src.reaper_builder import VST3_REGISTRY
plugins = [
PluginDef(name=name, path=entry[1], index=i)
for i, (name, entry) in enumerate(RPPBuilder.VST3_REGISTRY.items())
for i, (name, entry) in enumerate(VST3_REGISTRY.items())
]
track = TrackDef(name="Test", clips=[], plugins=plugins)
song = SongDefinition(meta=meta, tracks=[track])
@@ -441,7 +443,12 @@ class TestVST3PresetData:
builder.write(tmp_path)
content = Path(tmp_path).read_text(encoding="utf-8")
# Check that plugins WITH preset data have that data in output
for name, preset_lines in RPPBuilder.VST3_PRESETS.items():
from src.reaper_builder import PLUGIN_PRESETS, VST3_REGISTRY
vst3_keys = set(VST3_REGISTRY.keys())
for name, preset_lines in PLUGIN_PRESETS.items():
# Only check VST3 plugins (skip VST2 plugins which are in the same dict now)
if name not in vst3_keys:
continue
if len(preset_lines) > 0:
# Check first preset line — most distinctive, no collision risk
first_line = preset_lines[0]

View File

@@ -55,22 +55,22 @@ class TestVST3Effects:
from scripts.compose import _VST3_EFFECTS
# Fruity Parametric EQ 2 normalizes to Pro-Q 3
registry_key, filename = _VST3_EFFECTS["Pro-Q 3"]
assert registry_key == "FabFilter Pro-Q 3"
assert filename == "FabFilter Pro-Q 3.vst3"
assert registry_key == "Pro-Q_3"
assert filename == "FabFilter"
def test_fruity_compressor_maps_to_proc2(self):
"""Fruity Compressor → FabFilter Pro-C 2 via normalization."""
from scripts.compose import _VST3_EFFECTS
registry_key, filename = _VST3_EFFECTS["Pro-C 2"]
assert registry_key == "FabFilter Pro-C 2"
assert filename == "FabFilter Pro-C 2.vst3"
assert registry_key == "Pro-C_2"
assert filename == "FabFilter"
def test_pro_r_maps_to_pror2(self):
"""Pro-R 2 → FabFilter Pro-R 2."""
from scripts.compose import _VST3_EFFECTS
registry_key, filename = _VST3_EFFECTS["Pro-R 2"]
assert registry_key == "FabFilter Pro-R 2"
assert filename == "FabFilter Pro-R 2.vst3"
assert registry_key == "Pro-R_2"
assert filename == "FabFilter"
def test_unknown_effect_returns_none(self):
"""Unknown effect names return no VST3 info."""
@@ -96,11 +96,11 @@ class TestBuildFxChain:
}
plugins = build_fx_chain("drums", genre_config, [])
assert len(plugins) == 2
# Fruity Parametric EQ 2 → Pro-Q 3
assert "FabFilter" in plugins[0].name
assert ".vst3" in plugins[0].path
# Pro-Q 3 via alias
assert plugins[0].name in ("Pro-Q_3", "FabFilter_Pro-Q_3")
assert plugins[0].path in ("FabFilter", "FabFilter Pro-Q 3.vst3")
# Fruity Compressor → Pro-C 2
assert "FabFilter" in plugins[1].name
assert plugins[1].name in ("Pro-C_2", "FabFilter_Pro-C_2")
def test_build_fx_chain_bass(self):
"""build_fx_chain returns PluginDef list for bass role."""
@@ -188,7 +188,7 @@ class TestCreateReturnTracks:
reverb = tracks[0]
assert len(reverb.plugins) == 1
assert "FabFilter" in reverb.plugins[0].name
assert ".vst3" in reverb.plugins[0].path
assert reverb.plugins[0].path in ("FabFilter", "FabFilter_Pro_R_2.vst3")
def test_delay_track_has_timeless3(self):
"""Delay return track has FabFilter Timeless 3 plugin."""
@@ -198,7 +198,7 @@ class TestCreateReturnTracks:
delay = tracks[1]
assert len(delay.plugins) == 1
assert "Timeless" in delay.plugins[0].name
assert ".vst3" in delay.plugins[0].path
assert delay.plugins[0].path in ("FabFilter", "FabFilter_Timeless_3.vst3")
def test_return_tracks_have_volume_0_7(self):
"""Return tracks have volume 0.7."""