Files
reaper-control/docs/LLM_CONTEXT.md
renato97 7bcd8052a9 docs: LLM-ready documentation suite — LLM_CONTEXT.md, module deep-dives, JSON schemas, CLI reference
Complete documentation system for LLM consumption: primary LLM_CONTEXT.md
(27KB system overview), 4 module deep-dives (composer, reaper-builder,
reaper-scripting, calibrator), 2 JSON Schema draft-07 contracts, CLI
reference, and README correction (FL Studio -> REAPER identity).
2026-05-04 10:30:24 -03:00

27 KiB
Raw Blame History

fl_control — LLM System Overview

About the name: The directory is named fl_control for historical reasons, but the system generates REAPER .rpp files, not FL Studio .flp files. There is NO FL Studio support. The name has been retained for backward compatibility with repository URLs and CI/CD pipelines.

What Is This?

fl_control is a Python system that generates complete REAPER .rpp projects from the command line. Given a key, BPM, and emotion, it composes a full reggaetón instrumental with drums, 808 bass, chord progressions (with voice leading), lead melodies (hook-based call-and-response), pads, percussion, and transition FX. The output is a valid .rpp file that REAPER opens directly, plus an auto-generated ReaScript (.py) for post-processing (plugin loading, mix calibration, rendering).

Target: REAPER (Windows, v7.x). Format: .rpp text files. No DAW dependencies beyond reading sample files from disk.

Quick Start

# Install dependencies
pip install -r requirements.txt

# Generate a song (defaults: 95 BPM, Am key, romantic emotion)
python scripts/generate.py --bpm 95 --key Am --output output/song.rpp --seed 42

# Generate with validation
python scripts/generate.py --bpm 95 --key Dm --output output/song.rpp --seed 42 --validate

# Compose directly (more detailed output)
python scripts/compose.py --bpm 99 --key Am --emotion romantic --output output/song.rpp

Output: a ready-to-open .rpp file at the specified path.

Architecture

                           ┌───────────────────────────────────────┐
                           │              CLI Layer                 │
                           │  scripts/generate.py (thin wrapper)    │
                           │  scripts/compose.py (full pipeline)    │
                           │  scripts/run_in_reaper.py (ReaScript)  │
                           └──────────────┬────────────────────────┘
                                          │
                   ┌──────────────────────┼──────────────────────┐
                   │                      ▼                      │
  ┌────────────────┴──────┐  ┌────────────────────┴──┐  ┌───────┴──────────┐
  │     src/composer/      │  │   src/calibrator/     │  │  src/selector/   │
  │  ┌──────────────────┐  │  │  ┌────────────────┐   │  │  SampleSelector  │
  │  │ chords.py         │  │  │  │ Calibrator      │  │  └────────────────┘
  │  │  ChordEngine      │  │  │  │ .apply()        │  │
  │  │ melody_engine.py  │  │  │  │ presets.py      │  └───────────────────┘
  │  │  build_motif()    │  │  │  │ VOLUME_PRESETS  │
  │  │ patterns.py       │  │  │  │ PAN_PRESETS     │         ┌──────────────────┐
  │  │ rhythm.py         │  │  │  │ SEND_PRESETS    │         │ src/core/        │
  │  │ templates.py      │  │  │  └────────────────┘         │  schema.py       │
  │  └──────────────────┘  │  └─────────────────────────────┘ │  SongDefinition  │
  └───────────┬────────────┘                                  │  TrackDef        │
              │                                               │  ClipDef         │
              ▼                                               │  MidiNote        │
  ┌──────────────────────────┐                               │  PluginDef       │
  │    SongDefinition        │◄──────────────────────────────│  SectionDef      │
  │  ┌────────────────────┐  │                               │  PatternDef      │
  │  │ meta: SongMeta     │  │                               │  Arrangement...  │
  │  │ tracks: [TrackDef] │  │                               │  CCEvent         │
  │  │ patterns: [...]    │  │                               └──────────────────┘
  │  │ sections: [...]    │  │
  │  │ master_plugins     │  │
  │  └────────────────────┘  │
  └───────────┬──────────────┘
              │
    ┌─────────┼─────────┐
    ▼         ▼         ▼
┌──────────────┐ ┌───────────────┐ ┌─────────────────┐
│ RPPBuilder   │ │ ReaScriptGen  │ │ Validator       │
│ .write() →   │ │ generate() →  │ │ validate_rpp()   │
│ song.rpp     │ │ fl_control_   │ │                  │
│              │ │ phase2.py     │ │                  │
└──────────────┘ └───────┬───────┘ └─────────────────┘
                         │
                         ▼
                  ┌──────────────┐
                  │   REAPER     │
                  │  opens .rpp  │
                  │  runs script │
                  │  renders WAV │
                  └──────────────┘

Pipeline: CLI → compose SongDefinition → Calibrator.apply() (post-processing) → RPPBuilder.write() → .rpp file → ReaScriptGenerator.generate() → ReaScript → REAPER execution.

Data Model

All types live in src/core/schema.py. Every entity is a Python dataclass.

SongMeta

Song-level metadata.

Field Type Default Description
bpm float (required) Tempo 20999
key str (required) Key string, e.g. "Am", "Dm", "G"
title str "" Song title
ppq int 960 Ticks per quarter note (REAPER default)
time_sig_num int 4 Time signature numerator
time_sig_den int 4 Time signature denominator
calibrate bool True Enable post-processing mix calibration

MidiNote

A single MIDI note event within a clip.

Field Type Default Description
pitch int (required) MIDI note number 0127 (60 = middle C)
start float (required) Start time in beats from item start
duration float (required) Duration in beats
velocity int 64 Velocity 0127

CCEvent

A MIDI CC event within a clip.

Field Type Default Description
controller int (required) CC number (e.g. 11 = Expression)
time float (required) Position in beats from clip start
value int (required) CC value 0127

ClipDef

A clip placed on a track — either audio or MIDI.

Field Type Default Description
position float (required) Start position in beats
length float (required) Duration in beats
name str "" Display name
audio_path str | None None Absolute path to audio file (audio clips)
midi_notes list[MidiNote] [] MIDI notes (MIDI clips)
midi_cc list[CCEvent] [] MIDI CC events
loop bool False Whether the audio clip loops
fade_in float 0.0 Fade-in duration in seconds
fade_out float 0.0 Fade-out duration in seconds
vol_mult float 1.0 Volume multiplier applied at clip level

Properties: is_midi → True when midi_notes is non-empty; is_audio → True when audio_path is not None.

PluginDef

A VST plugin instance on a track.

Field Type Default Description
name str (required) Display name (e.g. "Serum 2")
path str (required) Plugin path/identifier
index int 0 Chain position (0 = first)
params dict[int, float] {} Parameter index → value
preset_data list[str] | None None Base64 preset chunks
role str "" Track role for role-aware preset lookup
builtin bool False True → deferred to ReaScript, not written to .rpp

TrackDef

A track in the REAPER project.

Field Type Default Description
name str (required) Track display name
volume float 0.85 0.01.0 (maps to REAPER volume fader)
pan float 0.0 -1.0 (left) to 1.0 (right)
color int 0 REAPER color index 067
clips list[ClipDef] [] Audio/MIDI clips
plugins list[PluginDef] [] VST plugins on this track
send_reverb float 0.0 Reverb send level 0.01.0
send_delay float 0.0 Delay send level 0.01.0
send_level dict[int, float] {} Return track index → send level

SectionDef

A section in the song arrangement.

Field Type Default Description
name str (required) Display name (e.g. "intro", "chorus")
bars int (required) Length in bars
energy float 0.5 Energy level 0.01.0
velocity_mult float 1.0 Velocity multiplier for all notes in section
vol_mult float 1.0 Volume multiplier for tracks in section

PatternDef

A pattern definition with generator and variation axes.

Field Type Default Description
id int (required) Unique pattern ID
name str (required) Display name
instrument str (required) Sample/instrument key
channel int (required) MIDI channel
bars int (required) Length in bars
generator str (required) Generator function name
velocity_mult float 1.0 Velocity multiplier 0.851.1
density float 1.0 Note density 0.01.0

ArrangementItemDef

An item placed in the arrangement referencing a pattern on a track.

Field Type Default Description
pattern int (required) Pattern ID
bar float (required) Start position in bars
bars float (required) Length in bars
track int (required) Track index

ArrangementTrack

A track in the REAPER arrangement with index and display name.

Field Type Default Description
index int (required) Track index
name str (required) Display name

SongDefinition

Complete song definition — the source of truth for one .rpp file.

Field Type Default Description
meta SongMeta (required) Song metadata
tracks list[TrackDef] [] REAPER tracks with clips and plugins
patterns list[PatternDef] [] Pattern definitions for arrangement
items list[ArrangementItemDef] [] Arrangement items referencing patterns
progression_name str "i-VII-VI-VII" Chord progression label
section_template str "standard" Section template name
samples dict[str, str] {} Sample file map (name → filename)
sections list[SectionDef] [] Section definitions in playback order
master_plugins list[str] [] Plugin registry keys for master FX chain

Key methods:

  • validate()list[str] — returns list of validation errors (empty = valid)
  • length_beats (property) → float — total length computed from all clips
  • to_json(indent=2)str — serialize to JSON via dataclasses.asdict

Module Index

Module Location Role
schema src/core/schema.py All dataclass definitions. Single source of truth for data model.
composer src/composer/ Composition engine: chord progression, melody generation, rhythm patterns, drum analysis
reaper_builder src/reaper_builder/ RPPBuilder: .rpp file generation from SongDefinition. Plugin registry (~97 entries). VST2/VST3 element builders. Headless render.
reaper_scripting src/reaper_scripting/ ReaScript generation: self-contained Python scripts for REAPER post-processing
calibrator src/calibrator/ Post-processing mix calibration: volume, pan, sends, master chain by track role
selector src/selector/ Sample selection: scores samples by key compatibility, BPM proximity, character
validator src/validator/ .rpp output validation
CLI scripts scripts/ compose.py (full pipeline), generate.py (thin wrapper), run_in_reaper.py (ReaScript execution)

composer/ sub-modules

File Role
chords.py ChordEngine — emotion-aware chord progressions with voice leading
melody_engine.py build_motif(), build_call_response() — hook-based melody generation
patterns.py Pattern weight tables extracted from real reggaetón tracks
rhythm.py Dembow rhythm generators (classic, perreo, trápico)
templates.py RPP template extraction and generation
converters.py Conversion utilities
variation.py Pattern variation logic
melodic.py Melodic pattern generators
drum_analyzer.py DrumLoopAnalyzer — transient detection for sidechain CC generation
__init__.py Scale intervals, chord type tables, legacy pattern generators

Pipeline

The complete composition and build pipeline, step by step:

Phase 1: Composition (scripts/compose.py)

  1. Parse CLI args: --bpm, --key, --emotion, --inversion, --seed, --no-calibrate
  2. Load SampleSelector: Reads data/sample_index.json for clap/snare/FX sample selection
  3. Build sections: 9-section structure (intro → verse → pre-chorus → chorus → verse2 → chorus2 → bridge → final → outro), each with energy level and velocity/volume multipliers
  4. Build tracks (in order):
    • Drumloop — audio clips cycling between seco/filtrado variants per section
    • Perc — percussion audio loops per section
    • 808 Bass — MIDI notes using proven i-iv-i-V harmonic pattern with CC11 sidechain ducking
    • ChordsChordEngine.progression() with voice leading; i-VII-VI-VII progression
    • Leadbuild_motif("hook") + build_call_response() via melody engine
    • Clap — Short audio samples on dembow grid (beats 2.0, 3.5)
    • Transition FX — Audio clips at section boundaries (risers, impacts, sweeps)
    • Pad — Arpeggiated chord tones on eighth notes
    • Reverb / Delay — Return tracks with Pro-R 2 and ValhallaDelay
  5. Wire sends: Return track sends by role from SEND_LEVELS
  6. Assemble SongDefinition: Meta + tracks + sections + master_plugins
  7. Calibrate (unless --no-calibrate): Calibrator.apply(song) — role-based volume, pan, sends, master chain

Phase 2: Build (src/reaper_builder/RPPBuilder)

  1. RPPBuilder(song, seed).write(path) serializes to .rpp:
    • Project header (static metadata from ground-truth test_vst3.rpp)
    • Dynamic TEMPO line
    • Master track with FX chain (Ozone 12 triplet or FabFilter fallback)
    • Per-track TRACK elements with VOLPAN, AUXRECV (sends)
    • Per-track FXCHAIN with VST elements (resolved from PLUGIN_REGISTRY)
    • Per-clip ITEM elements with SOURCE (WAVE or MIDI with E-lines)
    • Built-in plugins (Cockos) deferred to ReaScript insertion

Phase 3: ReaScript (Phase 2B)

scripts/run_in_reaper.py generates a self-contained Python ReaScript that REAPER executes:

  1. Reads fl_control_command.json (command file)
  2. Opens the .rpp project
  3. Executes action pipeline: add_pluginsconfigure_fx_paramsverify_fxcalibraterender
  4. Writes fl_control_result.json (LUFS metrics, FX errors, plugin results)

Validation

SongDefinition.validate() checks: BPM range (20999), key format (regex), unique track names, clips with neither audio nor MIDI.

Plugin System

PLUGIN_REGISTRY

Located in src/reaper_builder/__init__.py (~97 entries). Format:

PLUGIN_REGISTRY: dict[str, tuple[str, str, str]] = {
    "key": ("display_name", "filename", "uid_guid"),
}
  • key: Short registry key (e.g. "Serum_2", "Pro-Q_3", "Decapitator")
  • display_name: Full REAPER display name (e.g. "VST3i: Serum 2 (Xfer Records)")
  • filename: File on disk (e.g. "Serum2.vst3", "Decapitator.dll")
  • uid_guid: Unique identifier — VST2 uses <GUID>, VST3 uses {GUID}

ALIAS_MAP

Backward compatibility mapping: old key names → new PLUGIN_REGISTRY keys. Example:

ALIAS_MAP = {
    "Serum2":            "Serum_2",
    "FabFilter Pro-Q 3": "Pro-Q_3",
    "Valhalla Delay":    "ValhallaDelay",
    "Pro-Q 3":           "Pro-Q_3",
}

FabFilter plugins map space-separated names to short VST3 keys. SoundToys plugins keep their underscore names.

PLUGIN_PRESETS

Role-aware preset data. Format:

PLUGIN_PRESETS: dict[tuple[str, str], list[str]] = {
    ("Pro-Q_3", ""):       [chunk1, chunk2, ...],  # default
    ("Serum_2", "bass"):   [chunk1, chunk2, ...],  # role-specific
}

Lookup chain: (key, role)(key, "") (default) → fallbackNone.

REAPER_BUILTINS

Set of Cockos native plugin keys (ReaEQ, ReaComp, ReaVerb, etc.). Plugins matching this are deferred to ReaScript insertion (TrackFX_AddByName) rather than written to .rpp as VST elements.

Plugin Lookup in RPPBuilder

PluginDef.name → ALIAS_MAP.get(name, name) → PLUGIN_REGISTRY.get(resolved)
                                                                   │
                                                   ┌───────────────┤
                                              found│               │not found
                                                   ▼               ▼
                                         Build VST element    Fallback: .dll
                                         with display_name,    with 19 param
                                         filename, uid_guid    slots

ReaScript Protocol

Overview

ReaScript is a self-contained Python file generated by ReaScriptGenerator that REAPER executes internally. It communicates via JSON files on disk:

fl_control_command.json  ──read──►  ReaScript  ──write──►  fl_control_result.json

ReaScriptCommand

Defined in src/reaper_scripting/commands.py:

Field Type Default Description
version int 1 Protocol version
action str | list[str] "calibrate" Single action or pipeline list
rpp_path str "" Path to .rpp file to open
render_path str "" Path for rendered WAV output
timeout int 120 Polling timeout in seconds
track_calibration list[dict] [] Volume/pan/send calibration per track
plugins_to_add list[dict] [] {"track_name": str, "fx_name": str, "params": {...}}

ReaScriptResult

Field Type Default Description
version int 1 Protocol version
status str "ok" "ok", "error", or "timeout"
message str "" Error or status message
lufs float | None None Integrated LUFS
integrated_lufs float | None None Integrated LUFS reading
short_term_lufs float | None None Short-term LUFS
fx_errors list[dict] [] FX verification errors
tracks_verified int 0 Number of tracks verified
added_plugins list[dict] [] {"fx_name", "instance_id", "track_name", "status"}

ProtocolVersionError

Raised by read_result() when result version doesn't match expected version.

Key Functions

  • write_command(path, cmd) — Serialize ReaScriptCommand to JSON file
  • read_result(path, expected_version=1) — Deserialize ReaScriptResult from JSON file. Raises ProtocolVersionError on version mismatch.

Known Actions

add_plugins, configure_fx_params, verify_fx, calibrate, render

ReaScript JSON Parser

Since REAPER's internal Python has no json module, the ReaScript includes a hand-rolled JSON parser (~100 lines) that handles strings, integers, floats, booleans, nulls, nested objects, and arrays via string splitting.

CLI Reference

scripts/compose.py

Main composition pipeline. Builds a full reggaetón track from scratch.

Flag Type Default Description
--bpm float 99 Tempo
--key str "Am" Musical key
--output str "output/song.rpp" Output .rpp path
--seed int None Random seed for determinism
--emotion str "romantic" romantic / dark / club / classic
--inversion str "root" root / first / second
--no-calibrate flag False Skip post-processing mix calibration

scripts/generate.py

Thin wrapper around compose.py. Delegates via sys.argv manipulation.

Flag Type Default Description
--bpm float 95 Tempo
--key str "Am" Musical key
--output str "output/song.rpp" Output .rpp path
--seed int 42 Random seed
--emotion str "romantic" Chord emotion
--inversion str "root" Chord inversion
--validate flag False Run validate_rpp_output() after generation

scripts/run_in_reaper.py

ReaScript execution CLI. Generates and runs a Phase 2 ReaScript.

Arg/Flag Type Default Description
rpp_path positional (required) Path to .rpp file
--output, -o str auto-derived Rendered WAV output path
--timeout int 120 Seconds to poll for result JSON
--plugins-config str None JSON SongDefinition for deriving plugins_to_add
--action str "calibrate" Space-separated action pipeline

Example:

python scripts/run_in_reaper.py output/song.rpp --action "add_plugins calibrate render" --timeout 300

Conventions

From AGENTS.md:

  • Python: Type hints on all function signatures
  • Dataclasses: Over dicts for structured data
  • Deterministic output: Seed-based RNG (random.Random(seed)), no global random state
  • No bare except clauses
  • Testing: pytest only. Unit tests for all new functions. Integration tests for end-to-end flows.
  • Architecture: Separate modules by concern (calibrator, composer, builder, selector, validator)
  • Post-processing: Calibrator.apply() pattern — modify in-place after construction, not inline
  • Schema changes: Must be backward-compatible (new fields get defaults)
  • SDD: All changes follow spec-driven development pipeline (propose → spec → design → tasks → apply → verify)

How to Extend

Adding a New Track Role

  1. Add role to TRACK_ACTIVITY in scripts/compose.py — define which sections it plays in
  2. Add volume/presets to src/calibrator/presets.py (VOLUME_PRESETS, PAN_PRESETS, SEND_PRESETS)
  3. Add FX chain to FX_CHAINS in scripts/compose.py
  4. Write a build_<role>_track() function
  5. Add the track to the tracks list in main()
  6. Add calibration role mapping in Calibrator._resolve_role()

Adding a New Plugin to the Registry

  1. Scan the plugin in REAPER or extract from a ground-truth .rpp
  2. Add entry to PLUGIN_REGISTRY: "key": ("display_name", "filename", "uid_guid")
  3. If needed, add preset data to _PRESETS_FLAT with the key
  4. Add aliases to ALIAS_MAP if there are alternate names
  5. Use make_plugin(registry_key, index, role) to create PluginDef instances

Adding a New CLI Flag

  1. Add parser.add_argument() in scripts/compose.py (and scripts/generate.py if wrapping)
  2. Thread the flag value through the composition pipeline
  3. Pass to relevant builder/track functions

Adding a New Emotion to ChordEngine

  1. Add entry to EMOTION_PROGRESSIONS in src/composer/chords.py
  2. Format: "name": [(semitone_offset, quality), ...] for a 4-chord loop
  3. Add choice to --emotion argparse choices

Known Limitations

  • Hardcoded paths: Drumloop samples reference C:\ProgramData\Ableton\... paths. These must exist on the build machine or the pipeline will skip those clips.
  • Environment-specific registry: PLUGIN_REGISTRY was generated from a specific REAPER/plugin installation. Different machines may have different plugin paths or GUIDs.
  • Windows only: REAPER executable path in render.py defaults to C:\Program Files\REAPER (x64)\reaper.exe. The ReaScript REAPER API calls are Windows-specific.
  • No CI: No automated test/validation pipeline. Tests run manually with pytest.
  • Single genre: Only reggaetón is fully implemented. Other genres exist in knowledge/ as JSON definitions but the main pipeline (compose.py) is hardcoded for reggaetón.
  • Sample library: Requires pre-existing sample files in expected directories. No bundled samples.
  • ReaScript execution: run_in_reaper.py generates scripts but does NOT automatically launch REAPER. REAPER must be running and monitoring the scripts directory, or the script must be executed manually from within REAPER's action list.

Further Reading