- Reverse-engineer drum patterns from 2 real reggaeton tracks with librosa - Create patterns.py with extracted frequency data (kick/snare/hihat positions) - Rewrite rhythm.py with pattern-bank generators (dembow, dense, trapico, offbeat) - Rewrite melodic.py with section-aware generators and humanization - Add weighted random sample selection in SampleSelector (top-5 pool) - Add generate_structure() with randomized templates and energy variance - Fix RPP structure: TEMPO arity (3→4 args), string quoting for empty strings - Rewrite quick_drumloop_test.py with correct REAPER ground truth format - Add scripts/analyze_examples.py for reverse engineering audio tracks - Add --seed argument for reproducible generation - 72 tests passing
243 lines
9.6 KiB
Python
243 lines
9.6 KiB
Python
#!/usr/bin/env python
|
|
"""Quick test: one drum loop repeated 4 times in track 2.
|
|
|
|
This script demonstrates the CORRECT REAPER .rpp format by matching
|
|
the ground truth from output/all_plugins.rpp and output/test_vst3.rpp.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).parent.parent
|
|
sys.path.insert(0, str(ROOT))
|
|
|
|
from rpp import Element
|
|
import rpp
|
|
|
|
|
|
DRUM_LOOP = r"C:\Users\Administrator\Documents\fl_control\libreria\samples\drumloop\drumloop_D2_099_boomy_f8b5a5.wav"
|
|
OUTPUT = str(ROOT / "output" / "drumloop_test.rpp")
|
|
|
|
|
|
def build():
|
|
"""Build a valid REAPER .rpp with drum loop items."""
|
|
# Project root — matching ground truth format from all_plugins.rpp
|
|
project = Element("REAPER_PROJECT", ["0.1", "7.65/win64", "12345678901234567890", "0"])
|
|
|
|
# Project header from ground truth — tokens match exactly what REAPER expects
|
|
project.append(Element("NOTES", ["0", "2"]))
|
|
project.append([])
|
|
project.append(["RIPPLE", "0", "0"])
|
|
project.append(["GROUPOVERRIDE", "0", "0", "0", "0"])
|
|
project.append(["AUTOXFADE", "129"])
|
|
project.append(["ENVATTACH", "3"])
|
|
project.append(["POOLEDENVATTACH", "0"])
|
|
project.append(["TCPUIFLAGS", "0"])
|
|
project.append(["MIXERUIFLAGS", "11", "48"])
|
|
project.append(["ENVFADESZ10", "40"])
|
|
project.append(["PEAKGAIN", "1"])
|
|
project.append(["FEEDBACK", "0"])
|
|
project.append(["PANLAW", "1"])
|
|
project.append(["PROJOFFS", "0", "0", "0"])
|
|
project.append(["MAXPROJLEN", "0", "0"])
|
|
project.append(["GRID", "3199", "8", "1", "8", "1", "0", "0", "0"])
|
|
project.append(["TIMEMODE", "1", "5", "-1", "30", "0", "0", "-1", "0"])
|
|
project.append(["VIDEO_CONFIG", "0", "0", "65792"])
|
|
project.append(["PANMODE", "3"])
|
|
project.append(["PANLAWFLAGS", "3"])
|
|
project.append(["CURSOR", "0"])
|
|
project.append(["ZOOM", "100", "0", "0"])
|
|
project.append(["VZOOMEX", "6", "0"])
|
|
project.append(["USE_REC_CFG", "0"])
|
|
project.append(["RECMODE", "1"])
|
|
project.append(["SMPTESYNC", "0", "30", "100", "40", "1000", "300", "0", "0", "1", "0", "0"])
|
|
project.append(["LOOP", "0"])
|
|
project.append(["LOOPGRAN", "0", "4"])
|
|
project.append(["RECORD_PATH", "Media", ""])
|
|
project.append(Element("RECORD_CFG", [], children=["ZXZhdxgAAQ=="]))
|
|
project.append([])
|
|
project.append(Element("APPLYFX_CFG", [], children=[]))
|
|
project.append([])
|
|
project.append(["RENDER_FILE", ""])
|
|
project.append(["RENDER_PATTERN", ""])
|
|
project.append(["RENDER_FMT", "0", "2", "0"])
|
|
project.append(["RENDER_1X", "0"])
|
|
project.append(["RENDER_RANGE", "1", "0", "0", "0", "1000"])
|
|
project.append(["RENDER_RESAMPLE", "3", "0", "1"])
|
|
project.append(["RENDER_ADDTOPROJ", "0"])
|
|
project.append(["RENDER_STEMS", "0"])
|
|
project.append(["RENDER_DITHER", "0"])
|
|
project.append(["RENDER_TRIM", "0.000001", "0.000001", "0", "0"])
|
|
project.append(["TIMELOCKMODE", "1"])
|
|
project.append(["TEMPOENVLOCKMODE", "1"])
|
|
project.append(["ITEMMIX", "1"])
|
|
project.append(["DEFPITCHMODE", "589824", "0"])
|
|
project.append(["TAKELANE", "1"])
|
|
project.append(["SAMPLERATE", "44100", "0", "0"])
|
|
project.append([])
|
|
project.append(["LOCK", "1"])
|
|
project.append(Element("METRONOME", ["6", "2"], children=[
|
|
["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"],
|
|
]))
|
|
project.append([])
|
|
project.append(["GLOBAL_AUTO", "-1"])
|
|
# TEMPO with 4 args: bpm, time_sig_num, time_sig_den, flag
|
|
project.append(["TEMPO", "99", "4", "4", "0"])
|
|
project.append(["PLAYRATE", "1", "0", "0.25", "4"])
|
|
project.append(["SELECTION", "0", "0"])
|
|
project.append(["SELECTION2", "0", "0"])
|
|
project.append(["MASTERAUTOMODE", "0"])
|
|
project.append(["MASTERTRACKHEIGHT", "0", "0"])
|
|
project.append(["MASTERPEAKCOL", "16576"])
|
|
project.append(["MASTERMUTESOLO", "0"])
|
|
project.append(["MASTERTRACKVIEW", "0", "0.6667", "0.5", "0.5", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0", "0"])
|
|
project.append(["MASTERHWOUT", "0", "0", "1", "0", "0", "0", "0", "-1"])
|
|
project.append(["MASTER_NCH", "2", "2"])
|
|
project.append(["MASTER_VOLUME", "1", "0", "-1", "-1", "1"])
|
|
project.append(["MASTER_PANMODE", "3"])
|
|
project.append(["MASTER_PANLAWFLAGS", "3"])
|
|
project.append(["MASTER_FX", "1"])
|
|
project.append(["MASTER_SEL", "0"])
|
|
project.append(Element("MASTERPLAYSPEEDENV", [], children=[
|
|
["EGUID", "{DEF87440-E07C-4B72-B9F8-D2AC60A0D0AC}"],
|
|
["ACT", "0", "-1"],
|
|
["VIS", "0", "1", "1"],
|
|
["LANEHEIGHT", "0", "0"],
|
|
["ARM", "0"],
|
|
["DEFSHAPE", "0", "-1", "-1"],
|
|
]))
|
|
project.append([])
|
|
project.append(Element("TEMPOENVEX", [], children=[
|
|
["EGUID", "{15E58A72-7149-4783-9A04-838503786012}"],
|
|
["ACT", "1", "-1"],
|
|
["VIS", "1", "0", "1"],
|
|
["LANEHEIGHT", "0", "0"],
|
|
["ARM", "0"],
|
|
["DEFSHAPE", "1", "-1", "-1"],
|
|
]))
|
|
project.append([])
|
|
project.append(["RULERHEIGHT", "86", "86"])
|
|
project.append(["RULERLANE", "1", "4", "", "0", "-1"])
|
|
project.append(["RULERLANE", "2", "8", "", "0", "-1"])
|
|
project.append([])
|
|
|
|
# Master track
|
|
master_guid = "{00000000-0000-0000-0000-000000000001}"
|
|
master = Element("TRACK", [master_guid])
|
|
master.append(["NAME", "master"])
|
|
master.append(["VOLPAN", "1", "0", "-1", "-1", "1"])
|
|
master.append(["PEAKCOL", "16576"])
|
|
master.append(["BEAT", "-1"])
|
|
master.append(["AUTOMODE", "0"])
|
|
master.append(["PANLAWFLAGS", "3"])
|
|
master.append(["MUTESOLO", "0", "0", "0"])
|
|
master.append(["IPHASE", "0"])
|
|
master.append(["PLAYOFFS", "0", "1"])
|
|
master.append(["ISBUS", "0", "0"])
|
|
master.append(["BUSCOMP", "0", "0", "0", "0", "0"])
|
|
master.append(["SHOWINMIX", "1", "0.6667", "0.5", "1", "0.5", "0", "0", "0", "0"])
|
|
master.append(["FIXEDLANES", "9", "0", "0", "0", "0"])
|
|
master.append(["SEL", "0"])
|
|
master.append(["REC", "0", "0", "1", "0", "0", "0", "0", "0"])
|
|
master.append(["VU", "64"])
|
|
master.append(["TRACKHEIGHT", "0", "0", "0", "0", "0", "0", "0"])
|
|
master.append(["INQ", "0", "0", "0", "0.5", "100", "0", "0", "100"])
|
|
master.append(["NCHAN", "2"])
|
|
master.append(["FX", "1"])
|
|
master.append(["TRACKID", f"{{{master_guid}}}"])
|
|
master.append(["PERF", "0"])
|
|
master.append(["MIDIOUT", "-1"])
|
|
master.append(["MAINSEND", "1", "0"])
|
|
# Master FXCHAIN
|
|
master_fxchain = Element("FXCHAIN", [])
|
|
master_fxchain.append(["WNDRECT", "24", "52", "655", "408"])
|
|
master_fxchain.append(["SHOW", "0"])
|
|
master_fxchain.append(["LASTSEL", "0"])
|
|
master_fxchain.append(["DOCKED", "0"])
|
|
master_fxchain.append(["BYPASS", "0", "0", "0"])
|
|
master_fxchain.append(["PRESETNAME", "Program 1"])
|
|
master_fxchain.append(["FLOATPOS", "0", "0", "0", "0"])
|
|
master_fxchain.append(["FXID", "{A0F6CA8C-99E7-4B1A-8411-CA7201811EAD}"])
|
|
master.append(master_fxchain)
|
|
project.append(master)
|
|
|
|
# Track 2: Drum Loop
|
|
track_guid = "{00000000-0000-0000-0000-000000000002}"
|
|
track = Element("TRACK", [track_guid])
|
|
track.append(["NAME", "Drum Loop"])
|
|
track.append(["VOLPAN", "0.85", "0", "-1", "-1", "1"])
|
|
track.append(["PEAKCOL", "16576"])
|
|
track.append(["BEAT", "-1"])
|
|
track.append(["AUTOMODE", "0"])
|
|
track.append(["PANLAWFLAGS", "3"])
|
|
track.append(["MUTESOLO", "0", "0", "0"])
|
|
track.append(["IPHASE", "0"])
|
|
track.append(["PLAYOFFS", "0", "1"])
|
|
track.append(["ISBUS", "0", "0"])
|
|
track.append(["BUSCOMP", "0", "0", "0", "0", "0"])
|
|
track.append(["SHOWINMIX", "1", "0.6667", "0.5", "1", "0.5", "0", "0", "0", "0"])
|
|
track.append(["FIXEDLANES", "9", "0", "0", "0", "0"])
|
|
track.append(["SEL", "1"])
|
|
track.append(["REC", "0", "0", "1", "0", "0", "0", "0", "0"])
|
|
track.append(["VU", "64"])
|
|
track.append(["TRACKHEIGHT", "0", "0", "0", "0", "0", "0", "0"])
|
|
track.append(["INQ", "0", "0", "0", "0.5", "100", "0", "0", "100"])
|
|
track.append(["NCHAN", "2"])
|
|
track.append(["FX", "1"])
|
|
track.append(["TRACKID", f"{{{track_guid}}}"])
|
|
track.append(["PERF", "0"])
|
|
track.append(["MIDIOUT", "-1"])
|
|
track.append(["MAINSEND", "1", "0"])
|
|
|
|
# 4 clips of the drum loop, each 16 beats (4 bars)
|
|
loop_duration_beats = 16.0
|
|
|
|
for i in range(4):
|
|
position = i * loop_duration_beats
|
|
item = Element("ITEM", [])
|
|
item.append(["POSITION", f"{position:.6f}"])
|
|
item.append(["LENGTH", f"{loop_duration_beats:.6f}"])
|
|
item.append(["NAME", f"Drum Loop {i+1}"])
|
|
item.append(["SOFFS", "0.0"])
|
|
item.append(["PLAYRATE", "1", "0", "0.25", "4"])
|
|
item.append(["CHANMODE", "0"])
|
|
item.append(["GUID", f"{{00000000-0000-0000-0000-0000000000{i+10:02X}}}"])
|
|
item.append(["MUTE", "0", "0"])
|
|
item.append(["LOOP", "1"])
|
|
item.append(["COLOR", "0"])
|
|
|
|
# Audio source — uses correct <SOURCE WAVE> format
|
|
source = Element("SOURCE", ["WAVE"])
|
|
source.append(["FILE", DRUM_LOOP])
|
|
item.append(source)
|
|
|
|
track.append(item)
|
|
|
|
project.append(track)
|
|
|
|
# Write
|
|
output_str = rpp.dumps(project)
|
|
# Quote the version string in the header
|
|
output_str = output_str.replace(
|
|
"<REAPER_PROJECT 0.1 7.65/win64",
|
|
'<REAPER_PROJECT 0.1 "7.65/win64"'
|
|
)
|
|
|
|
Path(OUTPUT).parent.mkdir(parents=True, exist_ok=True)
|
|
Path(OUTPUT).write_text(output_str, encoding="utf-8")
|
|
print(f"Written: {OUTPUT}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
build()
|