feat: reggaeton production system with intelligent sample selection and FLP generation

This commit is contained in:
renato97
2026-05-02 21:40:18 -03:00
commit 4d941f3f90
62 changed files with 8656 additions and 0 deletions

160
scripts/build.py Normal file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env python
"""Build an FL Studio project from a composition plan JSON."""
import sys
import os
import json
import argparse
from pathlib import Path
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
sys.stdout.reconfigure(encoding="utf-8")
from src.flp_builder.project import FLPProject, Note
from src.flp_builder.writer import FLPWriter
PLUGIN_NAME_MAP = {
"Serum 2": "Serum2VST3",
"Omnisphere": "Omnisphere",
"Kontakt 7": "Kontakt 7",
"Diva": "Diva",
"Electra": "Electra",
"Pigments": "Pigments",
"ravity(S)": "ravity(S)",
"FL Keys": "FL Keys",
"FPC": "FPC",
"FLEX": "FLEX",
"Sytrus": "Sytrus",
"Harmor": "Harmor",
"3x Osc": "3x Osc",
"DirectWave": "DirectWave",
"Fruity DrumSynth Live": "Fruity DrumSynth Live",
"Transistor Bass": "Transistor Bass",
"Sakura": "Sakura",
"Sawer": "Sawer",
"Toxic Biohazard": "Toxic Biohazard",
"Harmless": "Harmless",
"GMS": "GMS",
"Minisynth": "Minisynth",
"Morphine": "Morphine",
"Soundfont Player": "Soundfont Player",
}
OUTPUT_DIR = Path(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) / "output"
def resolve_plugin(preferred_list):
for name in preferred_list:
if name in PLUGIN_NAME_MAP:
internal = PLUGIN_NAME_MAP[name]
is_vst = name in [
"Serum 2", "Omnisphere", "Kontakt 7", "Diva",
"Electra", "Pigments", "ravity(S)",
]
return {
"internal_name": "Fruity Wrapper" if is_vst else internal,
"display_name": name,
"is_vst": is_vst,
}
return {
"internal_name": "MIDI Out",
"display_name": "MIDI Out",
"is_vst": False,
}
def build_project(composition: dict) -> FLPProject:
meta = composition["meta"]
tracks = composition["tracks"]
project = FLPProject(
tempo=meta["bpm"],
title=meta.get("title", f"{meta.get('genre', 'Untitled')} - {meta.get('key', 'C')}"),
genre=meta.get("genre", ""),
fl_version="24.7.1.73",
ppq=meta.get("ppq", 96),
)
channel_map = {}
for i, track in enumerate(tracks):
role = track["role"]
plugin_info = resolve_plugin(track.get("preferred_plugins", []))
ch = project.add_channel(
name=f"{role}_{plugin_info['display_name']}",
plugin_internal_name=plugin_info["internal_name"],
plugin_display_name=plugin_info["display_name"],
mixer_track=track.get("mixer_slot", i),
channel_type=2,
)
channel_map[role] = ch.index
bars = meta.get("bars", 8)
ppq = meta.get("ppq", 96)
beats_per_chord = meta.get("beats_per_chord", 4)
for section_idx, track in enumerate(tracks):
role = track["role"]
ch_idx = channel_map.get(role, 0)
raw_notes = track.get("notes", [])
if not raw_notes:
continue
pat = project.add_pattern(name=f"{role}")
for n in raw_notes:
note = Note(
position=n["position"],
length=n["length"],
key=n.get("key", 60),
velocity=n.get("velocity", 100),
pan=n.get("pan", 0),
mod_x=n.get("mod_x", 0),
mod_y=n.get("mod_y", 0),
)
pat.add_note(ch_idx, note)
return project
def main():
parser = argparse.ArgumentParser(description="Build FL Studio project from composition plan")
parser.add_argument("plan", help="Path to composition plan JSON")
parser.add_argument("--output", "-o", help="Output .flp file path", default=None)
args = parser.parse_args()
with open(args.plan, "r", encoding="utf-8") as f:
composition = json.load(f)
project = build_project(composition)
OUTPUT_DIR.mkdir(parents=True, exist_ok=True)
if args.output:
output_path = args.output
else:
genre = composition["meta"].get("genre", "track")
key = composition["meta"].get("key", "C")
bpm = composition["meta"].get("bpm", 140)
output_path = str(OUTPUT_DIR / f"{genre}_{key}_{bpm}bpm.flp")
writer = FLPWriter(project)
writer.write(output_path)
result = {
"status": "ok",
"output": output_path,
"tempo": project.tempo,
"channels": len(project.channels),
"patterns": len(project.patterns),
"channel_names": [ch.name for ch in project.channels],
"pattern_names": [p.name for p in project.patterns],
"total_notes": sum(
len(notes)
for pat in project.patterns
for notes in pat.notes.values()
),
}
print(json.dumps(result, indent=2, ensure_ascii=False))
if __name__ == "__main__":
main()