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).
This commit is contained in:
544
docs/LLM_CONTEXT.md
Normal file
544
docs/LLM_CONTEXT.md
Normal file
@@ -0,0 +1,544 @@
|
||||
# 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
|
||||
|
||||
```bash
|
||||
# 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 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
|
||||
- `Chords` — `ChordEngine.progression()` with voice leading; i-VII-VI-VII progression
|
||||
- `Lead` — `build_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_plugins` → `configure_fx_params` → `verify_fx` → `calibrate` → `render`
|
||||
4. 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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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:
|
||||
|
||||
```python
|
||||
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)` — 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:
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
- [CLI Reference](CLI.md) — Complete CLI documentation
|
||||
- [Module: Composer](modules/composer.md) — Composition engine deep-dive
|
||||
- [Module: Reaper Builder](modules/reaper-builder.md) — RPP format and plugin registry
|
||||
- [Module: Reaper Scripting](modules/reaper-scripting.md) — ReaScript protocol
|
||||
- [Module: Calibrator](modules/calibrator.md) — Mix calibration presets
|
||||
- [JSON Schema: Song Definition](schemas/song-definition.json) — Data model schema
|
||||
- [JSON Schema: ReaScript Protocol](schemas/reascript-protocol.json) — Command/result schema
|
||||
Reference in New Issue
Block a user