feat: full reggaeton song generator with master chain, sends, clap, vocal, drumloop
- Add master FXCHAIN with Pro-Q 3, Pro-C 2, Pro-L 2 on master track - Add AUXRECV sends routing to Reverb/Delay return tracks - Add clap track with CLAP_DEMBOW pattern - Add vocal track with sample selection per section - Add drumloop layer with loop detection - Add track colors per role for visual organization - Randomize chord progressions from genre config (5 options) - Add master_plugins and send_level fields to schema - Add _build_master_fxchain() and AUXRECV rendering to RPPBuilder - 72 tests passing, RPP generates with 12 tracks, 18 sends, 20 plugins
This commit is contained in:
@@ -1642,17 +1642,21 @@ class RPPBuilder:
|
||||
defaults_copy[1] = f"{{{master_guid}}}"
|
||||
master.append(defaults_copy)
|
||||
|
||||
# Master track FXCHAIN (MASTER_FX 1 requires FXCHAIN)
|
||||
master_fxchain = Element("FXCHAIN", [])
|
||||
for line in _FXCHAIN_HEADER:
|
||||
master_fxchain.append([v for v in line])
|
||||
for line in _FXCHAIN_FOOTER:
|
||||
if line:
|
||||
footer_copy = [v for v in line]
|
||||
if footer_copy[0] == "FXID":
|
||||
footer_copy[1] = f"{{{self._make_seeded_guid()}}}"
|
||||
master_fxchain.append(footer_copy)
|
||||
master.append(master_fxchain)
|
||||
# Master track FXCHAIN — use _build_master_fxchain() if plugins are defined
|
||||
if self.song.master_plugins:
|
||||
master.append(self._build_master_fxchain())
|
||||
else:
|
||||
# Empty FXCHAIN skeleton (MASTER_FX 1 requires FXCHAIN element)
|
||||
master_fxchain = Element("FXCHAIN", [])
|
||||
for line in _FXCHAIN_HEADER:
|
||||
master_fxchain.append([v for v in line])
|
||||
for line in _FXCHAIN_FOOTER:
|
||||
if line:
|
||||
footer_copy = [v for v in line]
|
||||
if footer_copy[0] == "FXID":
|
||||
footer_copy[1] = f"{{{self._make_seeded_guid()}}}"
|
||||
master_fxchain.append(footer_copy)
|
||||
master.append(master_fxchain)
|
||||
root.append(master)
|
||||
|
||||
# User tracks
|
||||
@@ -1661,6 +1665,28 @@ class RPPBuilder:
|
||||
|
||||
return root
|
||||
|
||||
def _build_master_fxchain(self) -> Element:
|
||||
"""Build the FXCHAIN Element for the master track with master_plugins.
|
||||
|
||||
Uses _build_plugin() for each plugin in SongDefinition.master_plugins.
|
||||
"""
|
||||
fxchain = Element("FXCHAIN", [])
|
||||
for line in _FXCHAIN_HEADER:
|
||||
fxchain.append([v for v in line])
|
||||
|
||||
for idx, plugin_name in enumerate(self.song.master_plugins):
|
||||
plugin = PluginDef(name=plugin_name, path="", index=idx)
|
||||
fxchain.append(self._build_plugin(plugin))
|
||||
|
||||
fxid_guid = self._make_seeded_guid()
|
||||
for line in _FXCHAIN_FOOTER:
|
||||
if line:
|
||||
footer_copy = [v for v in line]
|
||||
if footer_copy[0] == "FXID":
|
||||
footer_copy[1] = f"{{{fxid_guid}}}"
|
||||
fxchain.append(footer_copy)
|
||||
return fxchain
|
||||
|
||||
def _build_track(self, track: TrackDef) -> Element:
|
||||
"""Build a TRACK Element with all default attributes from test_vst3.rpp."""
|
||||
track_guid = self._make_seeded_guid()
|
||||
@@ -1681,6 +1707,10 @@ class RPPBuilder:
|
||||
defaults_copy = ["SEL", "1"]
|
||||
track_elem.append(defaults_copy)
|
||||
|
||||
# Track color
|
||||
if track.color > 0:
|
||||
track_elem.append(["COLOR", str(track.color)])
|
||||
|
||||
# Plugins (FXCHAIN) — wrap VST elements inside proper FXCHAIN structure
|
||||
if track.plugins:
|
||||
fxchain = Element("FXCHAIN", [])
|
||||
@@ -1694,12 +1724,17 @@ class RPPBuilder:
|
||||
fxchain.append(["FXID", f"{{{fxid_guid}}}"])
|
||||
track_elem.append(fxchain)
|
||||
|
||||
# Send effects
|
||||
# Legacy send effects (send_reverb, send_delay)
|
||||
if track.send_reverb > 0:
|
||||
track_elem.append(["AUXRECV", "0", f"{track.send_reverb:.6f}", "-1", "-1", "0"])
|
||||
if track.send_delay > 0:
|
||||
track_elem.append(["AUXRECV", "1", f"{track.send_delay:.6f}", "-1", "-1", "0"])
|
||||
|
||||
# Generalised send_level dict — maps return track index → send level
|
||||
for ret_idx, level in track.send_level.items():
|
||||
if level > 0:
|
||||
track_elem.append(["AUXRECV", str(ret_idx), f"{level:.6f}", "-1", "-1", "0"])
|
||||
|
||||
# Clips (items)
|
||||
for clip in track.clips:
|
||||
track_elem.append(self._build_clip(clip))
|
||||
@@ -1759,6 +1794,12 @@ class RPPBuilder:
|
||||
item.append(["LENGTH", str(clip.length)])
|
||||
if clip.name:
|
||||
item.append(["NAME", clip.name])
|
||||
if clip.loop:
|
||||
item.append(["LOOP", "1"])
|
||||
if clip.fade_in > 0:
|
||||
item.append(["FADEIN", f"{clip.fade_in:.6f}"])
|
||||
if clip.fade_out > 0:
|
||||
item.append(["FADEOUT", f"{clip.fade_out:.6f}"])
|
||||
|
||||
if clip.is_audio and clip.audio_path:
|
||||
source = Element("SOURCE", ["WAVE"])
|
||||
|
||||
Reference in New Issue
Block a user