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).
27 KiB
fl_control — LLM System Overview
About the name: The directory is named
fl_controlfor historical reasons, but the system generates REAPER.rppfiles, not FL Studio.flpfiles. 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 20–999 |
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 0–127 (60 = middle C) |
start |
float |
(required) | Start time in beats from item start |
duration |
float |
(required) | Duration in beats |
velocity |
int |
64 |
Velocity 0–127 |
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 0–127 |
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.0–1.0 (maps to REAPER volume fader) |
pan |
float |
0.0 |
-1.0 (left) to 1.0 (right) |
color |
int |
0 |
REAPER color index 0–67 |
clips |
list[ClipDef] |
[] |
Audio/MIDI clips |
plugins |
list[PluginDef] |
[] |
VST plugins on this track |
send_reverb |
float |
0.0 |
Reverb send level 0.0–1.0 |
send_delay |
float |
0.0 |
Delay send level 0.0–1.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.0–1.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.85–1.1 |
density |
float |
1.0 |
Note density 0.0–1.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 clipsto_json(indent=2)→str— serialize to JSON viadataclasses.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)
- Parse CLI args:
--bpm,--key,--emotion,--inversion,--seed,--no-calibrate - Load
SampleSelector: Readsdata/sample_index.jsonfor clap/snare/FX sample selection - Build sections: 9-section structure (intro → verse → pre-chorus → chorus → verse2 → chorus2 → bridge → final → outro), each with energy level and velocity/volume multipliers
- Build tracks (in order):
Drumloop— audio clips cycling between seco/filtrado variants per sectionPerc— percussion audio loops per section808 Bass— MIDI notes using proven i-iv-i-V harmonic pattern with CC11 sidechain duckingChords—ChordEngine.progression()with voice leading; i-VII-VI-VII progressionLead—build_motif("hook")+build_call_response()via melody engineClap— 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 notesReverb/Delay— Return tracks with Pro-R 2 and ValhallaDelay
- Wire sends: Return track sends by role from
SEND_LEVELS - Assemble
SongDefinition: Meta + tracks + sections + master_plugins - Calibrate (unless
--no-calibrate):Calibrator.apply(song)— role-based volume, pan, sends, master chain
Phase 2: Build (src/reaper_builder/RPPBuilder)
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
- Project header (static metadata from ground-truth
Phase 3: ReaScript (Phase 2B)
scripts/run_in_reaper.py generates a self-contained Python ReaScript that REAPER executes:
- Reads
fl_control_command.json(command file) - Opens the
.rppproject - Executes action pipeline:
add_plugins→configure_fx_params→verify_fx→calibrate→render - Writes
fl_control_result.json(LUFS metrics, FX errors, plugin results)
Validation
SongDefinition.validate() checks: BPM range (20–999), 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) → fallback → None.
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)— SerializeReaScriptCommandto JSON fileread_result(path, expected_version=1)— DeserializeReaScriptResultfrom JSON file. RaisesProtocolVersionErroron 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:
pytestonly. 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
- Add role to
TRACK_ACTIVITYinscripts/compose.py— define which sections it plays in - Add volume/presets to
src/calibrator/presets.py(VOLUME_PRESETS,PAN_PRESETS,SEND_PRESETS) - Add FX chain to
FX_CHAINSinscripts/compose.py - Write a
build_<role>_track()function - Add the track to the
trackslist inmain() - Add calibration role mapping in
Calibrator._resolve_role()
Adding a New Plugin to the Registry
- Scan the plugin in REAPER or extract from a ground-truth
.rpp - Add entry to
PLUGIN_REGISTRY:"key": ("display_name", "filename", "uid_guid") - If needed, add preset data to
_PRESETS_FLATwith the key - Add aliases to
ALIAS_MAPif there are alternate names - Use
make_plugin(registry_key, index, role)to createPluginDefinstances
Adding a New CLI Flag
- Add
parser.add_argument()inscripts/compose.py(andscripts/generate.pyif wrapping) - Thread the flag value through the composition pipeline
- Pass to relevant builder/track functions
Adding a New Emotion to ChordEngine
- Add entry to
EMOTION_PROGRESSIONSinsrc/composer/chords.py - Format:
"name": [(semitone_offset, quality), ...]for a 4-chord loop - Add choice to
--emotionargparse 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_REGISTRYwas generated from a specific REAPER/plugin installation. Different machines may have different plugin paths or GUIDs. - Windows only: REAPER executable path in
render.pydefaults toC:\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.pygenerates 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
- CLI Reference — Complete CLI documentation
- Module: Composer — Composition engine deep-dive
- Module: Reaper Builder — RPP format and plugin registry
- Module: Reaper Scripting — ReaScript protocol
- Module: Calibrator — Mix calibration presets
- JSON Schema: Song Definition — Data model schema
- JSON Schema: ReaScript Protocol — Command/result schema