Files
reaper-control/scripts/batch_generate.py

123 lines
4.0 KiB
Python

#!/usr/bin/env python3
"""Batch FLP generator — produces 50 unique reggaeton FLP+JSON pairs.
Usage:
python scripts/batch_generate.py [--count 50] [--out-dir output/batch]
Output structure:
output/batch_{timestamp}/
reggaeton_000_95bpm_Am_i-VII-VI-VII.json
reggaeton_000_95bpm_Am_i-VII-VI-VII.flp
reggaeton_001_90bpm_Dm_i-iv-VII-III.json
...
manifest.json ← list of all generated songs with metadata
"""
import argparse
import json
import re
import sys
from datetime import datetime
from pathlib import Path
sys.path.insert(0, str(Path(__file__).parents[1]))
from src.composer.variation import generate_batch
from src.flp_builder.builder import FLPBuilder
from src.flp_builder.schema import SongDefinition
# ---------------------------------------------------------------------------
# Filename helpers
# ---------------------------------------------------------------------------
_UNSAFE_RE = re.compile(r'[^\w\-]')
def sanitize_filename(s: str) -> str:
"""Replace unsafe filename chars with _."""
return _UNSAFE_RE.sub('_', s)
def make_filename(idx: int, song: SongDefinition) -> str:
"""Build stem like ``reggaeton_000_95bpm_Am_i_VII_VI_VII`` (no extension)."""
prog_safe = sanitize_filename(song.progression_name)
return f"reggaeton_{idx:03d}_{song.meta.bpm}bpm_{song.meta.key}_{prog_safe}"
# ---------------------------------------------------------------------------
# Manifest
# ---------------------------------------------------------------------------
def build_manifest(songs: list[SongDefinition], filenames: list[str]) -> dict:
"""Build manifest dict with per-song metadata."""
entries = []
for idx, (song, stem) in enumerate(zip(songs, filenames)):
bar_count = int(max(item.bar + item.bars for item in song.items))
entries.append({
"idx": idx,
"filename": stem,
"bpm": song.meta.bpm,
"key": song.meta.key,
"progression": song.progression_name,
"title": song.meta.title,
"bars": bar_count,
})
return {
"generated_at": datetime.now().isoformat(),
"count": len(songs),
"songs": entries,
}
# ---------------------------------------------------------------------------
# Main
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(description="Batch FLP generator")
parser.add_argument("--count", type=int, default=50,
help="Number of songs to generate (default: 50)")
parser.add_argument("--out-dir", default="",
help="Output directory (default: output/batch_{timestamp})")
args = parser.parse_args()
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
out_dir = Path(args.out_dir) if args.out_dir else Path("output") / f"batch_{timestamp}"
out_dir.mkdir(parents=True, exist_ok=True)
print(f"Generating {args.count} songs -> {out_dir}")
songs = generate_batch(args.count)
builder = FLPBuilder()
filenames: list[str] = []
for idx, song in enumerate(songs):
stem = make_filename(idx, song)
filenames.append(stem)
# Write JSON
json_path = out_dir / f"{stem}.json"
json_path.write_text(song.to_json(), encoding="utf-8")
# Write FLP
flp_path = out_dir / f"{stem}.flp"
flp_bytes = builder.build(song)
flp_path.write_bytes(flp_bytes)
bar_count = int(max(item.bar + item.bars for item in song.items))
print(f" [{idx+1:>3}/{args.count}] {stem}.flp {len(flp_bytes):>9,}b {bar_count}bars")
# Write manifest
manifest = build_manifest(songs, filenames)
(out_dir / "manifest.json").write_text(
json.dumps(manifest, indent=2), encoding="utf-8"
)
total_size = sum((out_dir / f"{f}.flp").stat().st_size for f in filenames)
print(f"\nDone. {args.count} FLPs in {out_dir}")
print(f" Total size: {total_size:,} bytes")
if __name__ == "__main__":
main()