@mthines/reaper-mcp 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -2
- package/claude-agents/gain-stage.md +90 -0
- package/claude-agents/master.md +126 -0
- package/claude-agents/mix-analyzer.md +138 -0
- package/claude-agents/mix-engineer.md +207 -0
- package/main.js +42 -27
- package/package.json +2 -1
- package/reaper/mcp_bridge.lua +20 -19
- package/reaper/mcp_lufs_meter.jsfx +28 -22
package/README.md
CHANGED
|
@@ -95,9 +95,10 @@ npx @mthines/reaper-mcp install-skills
|
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
This creates in your project:
|
|
98
|
-
-
|
|
98
|
+
- `.claude/agents/` — mix engineer subagents (`@mix-engineer`, `@gain-stage`, `@mix-analyzer`, `@master`)
|
|
99
99
|
- `.claude/rules/` — architecture and development rules
|
|
100
100
|
- `.claude/skills/` — skills like `/learn-plugin`
|
|
101
|
+
- `knowledge/` — plugin knowledge, genre rules, workflows, reference data
|
|
101
102
|
- `.mcp.json` — MCP server configuration for Claude Code
|
|
102
103
|
|
|
103
104
|
### Step 4: Verify
|
|
@@ -160,6 +161,73 @@ Checks that the bridge is connected, knowledge is installed, and MCP config exis
|
|
|
160
161
|
|------|-------------|
|
|
161
162
|
| `get_track_routing` | Sends, receives, parent/folder info for a track |
|
|
162
163
|
|
|
164
|
+
## Using the Mix Agents
|
|
165
|
+
|
|
166
|
+
Once you've run `setup` and `install-skills`, open Claude Code in your project directory. Four specialized mix agents are available:
|
|
167
|
+
|
|
168
|
+
### Available Agents
|
|
169
|
+
|
|
170
|
+
| Agent | Invocation | What it does |
|
|
171
|
+
|-------|-----------|-------------|
|
|
172
|
+
| **Mix Engineer** | `@mix-engineer` | General-purpose mix agent — analyzes, suggests, and executes any mix task |
|
|
173
|
+
| **Gain Stage** | `@gain-stage` | Sets all tracks to -18 dBFS average with proper headroom |
|
|
174
|
+
| **Mix Analyzer** | `@mix-analyzer` | "Roast my mix" — analysis only, no changes, produces detailed report |
|
|
175
|
+
| **Master** | `@master` | Mastering chain targeting specific LUFS/platform standards |
|
|
176
|
+
|
|
177
|
+
### How to use them
|
|
178
|
+
|
|
179
|
+
Just mention the agent by name in Claude Code:
|
|
180
|
+
|
|
181
|
+
```
|
|
182
|
+
@mix-engineer Please gain stage all my tracks
|
|
183
|
+
@mix-engineer Build a vocal chain on track 3
|
|
184
|
+
@mix-engineer The low end is muddy — can you fix it?
|
|
185
|
+
@mix-analyzer Roast my mix — what could be improved?
|
|
186
|
+
@master Master this for Spotify
|
|
187
|
+
@gain-stage Set proper levels on everything
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Or start a full session as the mix engineer:
|
|
191
|
+
|
|
192
|
+
```bash
|
|
193
|
+
claude --agent mix-engineer
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
### What happens under the hood
|
|
197
|
+
|
|
198
|
+
Each agent has:
|
|
199
|
+
- **Its own system prompt** — thinks like a mix engineer, not a general assistant
|
|
200
|
+
- **Pre-approved REAPER tools** — no permission prompts for every MCP call
|
|
201
|
+
- **Scoped MCP access** — only the `reaper` MCP server is loaded
|
|
202
|
+
- **Embedded reference data** — frequency bands, LUFS targets, compression settings
|
|
203
|
+
|
|
204
|
+
The workflow is always:
|
|
205
|
+
1. **Save a snapshot** (so you can always A/B or undo)
|
|
206
|
+
2. **Analyze** — read meters, spectrum, LUFS, correlation, crest factor
|
|
207
|
+
3. **Reason** — apply genre rules, frequency knowledge, and plugin expertise
|
|
208
|
+
4. **Act** — add FX, set parameters, adjust levels using the best available plugins
|
|
209
|
+
5. **Verify** — re-read meters to confirm the change had the intended effect
|
|
210
|
+
6. **Report** — explain what it did and why in audio engineering terms
|
|
211
|
+
|
|
212
|
+
### A/B Comparison
|
|
213
|
+
|
|
214
|
+
Every change is bracketed by snapshots:
|
|
215
|
+
1. Agent saves a "Before" snapshot automatically
|
|
216
|
+
2. Makes all changes
|
|
217
|
+
3. Saves an "After" snapshot
|
|
218
|
+
4. You can restore either with `snapshot_restore` to A/B compare
|
|
219
|
+
|
|
220
|
+
### Genre Awareness
|
|
221
|
+
|
|
222
|
+
Tell the agent the genre and it adjusts its approach:
|
|
223
|
+
|
|
224
|
+
```
|
|
225
|
+
@mix-engineer This is a hip-hop track — please gain stage and check the 808
|
|
226
|
+
@mix-engineer Mix this rock song — make sure the guitars are wide and the drums punch
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
The agent reads `knowledge/genres/{genre}.md` for genre-specific conventions.
|
|
230
|
+
|
|
163
231
|
## AI Mix Engineer Knowledge
|
|
164
232
|
|
|
165
233
|
The knowledge base is what makes this more than just a remote control — it's a mix engineer's brain.
|
|
@@ -272,7 +340,7 @@ reaper-mcp/
|
|
|
272
340
|
pnpm install
|
|
273
341
|
pnpm nx run-many --target=build # Build all
|
|
274
342
|
pnpm nx run-many --target=lint # Lint all
|
|
275
|
-
pnpm nx run-many --target=test # Test all (
|
|
343
|
+
pnpm nx run-many --target=test # Test all (130+ tests)
|
|
276
344
|
pnpm nx run-many --target=build,lint,test # Everything
|
|
277
345
|
```
|
|
278
346
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gain-stage
|
|
3
|
+
description: Gain staging specialist — sets all tracks to proper levels before mixing. Use when asked to "gain stage", "set levels", or "prep for mixing".
|
|
4
|
+
tools: Read, Glob
|
|
5
|
+
mcpServers:
|
|
6
|
+
- reaper
|
|
7
|
+
model: sonnet
|
|
8
|
+
permissionMode: acceptEdits
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Gain Staging Agent
|
|
12
|
+
|
|
13
|
+
You are a gain staging specialist for REAPER DAW. Your sole job is to set all track levels to approximately **-18 dBFS average** before any FX processing, ensuring proper headroom for the mix.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Workflow
|
|
18
|
+
|
|
19
|
+
### Step 1: Save a safety snapshot
|
|
20
|
+
```
|
|
21
|
+
tool: snapshot_save
|
|
22
|
+
params: { name: "pre-gain-staging", description: "State before gain staging" }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Step 2: List all tracks
|
|
26
|
+
```
|
|
27
|
+
tool: list_tracks
|
|
28
|
+
```
|
|
29
|
+
Identify source tracks vs. bus/folder tracks. **Only adjust source tracks** — buses will follow.
|
|
30
|
+
Skip: master bus, reverb/delay returns, folder/bus tracks.
|
|
31
|
+
|
|
32
|
+
### Step 3: Start playback of the densest section
|
|
33
|
+
Play the chorus or loudest section — gain staging should target the loudest part.
|
|
34
|
+
```
|
|
35
|
+
tool: play
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Step 4: Read meters for every source track
|
|
39
|
+
```
|
|
40
|
+
tool: read_track_meters
|
|
41
|
+
params: { trackIndex: N }
|
|
42
|
+
```
|
|
43
|
+
Wait a few seconds of playback before reading. Record the RMS level for each track.
|
|
44
|
+
|
|
45
|
+
### Step 5: Calculate adjustments
|
|
46
|
+
Formula: `gain_dB = -18 - current_average_dBFS`
|
|
47
|
+
Round to nearest 0.5 dB.
|
|
48
|
+
|
|
49
|
+
Examples:
|
|
50
|
+
- Track at -24 dBFS → needs +6 dB
|
|
51
|
+
- Track at -10 dBFS → needs -8 dB
|
|
52
|
+
- Track at -18 dBFS → no change needed
|
|
53
|
+
|
|
54
|
+
### Step 6: Apply adjustments via the track fader
|
|
55
|
+
```
|
|
56
|
+
tool: set_track_property
|
|
57
|
+
params: { trackIndex: N, property: "volume", value: GAIN_DB }
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Important**: Use the fader (volume property), NOT clip gain or plugin input gain.
|
|
61
|
+
|
|
62
|
+
### Step 7: Check the mix bus
|
|
63
|
+
Read the mix bus meters after all adjustments. The mix bus should peak at **-6 to -3 dBFS** in the chorus. If it's hitting 0 dBFS, reduce all faders proportionally.
|
|
64
|
+
|
|
65
|
+
### Step 8: Save post-staging snapshot
|
|
66
|
+
```
|
|
67
|
+
tool: snapshot_save
|
|
68
|
+
params: { name: "post-gain-staging", description: "Gain staged — all tracks at -18 dBFS average" }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Step 9: Report
|
|
72
|
+
List each track with:
|
|
73
|
+
- Track name
|
|
74
|
+
- Before level (dBFS)
|
|
75
|
+
- Adjustment applied (dB)
|
|
76
|
+
- After level (dBFS)
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Targets
|
|
81
|
+
- **RMS per track**: -18 dBFS (acceptable range: -21 to -15 dBFS)
|
|
82
|
+
- **Peak per track**: -12 dBFS max
|
|
83
|
+
- **Mix bus peak**: -6 to -3 dBFS in the chorus
|
|
84
|
+
|
|
85
|
+
## Common Pitfalls
|
|
86
|
+
- Do NOT use clip gain — use the track fader
|
|
87
|
+
- Do NOT gain stage with compressors active (bypass them during measurement if possible)
|
|
88
|
+
- Do NOT read a single moment — play the densest section for 10+ seconds
|
|
89
|
+
- Bus tracks sum multiple sources — check them AFTER adjusting source tracks
|
|
90
|
+
- If a track has wildly inconsistent levels, note it as needing clip gain editing (manual task)
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: master
|
|
3
|
+
description: Mastering engineer for REAPER DAW. Applies a mastering chain to the mix bus targeting specific loudness standards. Use for "master this", "prepare for Spotify", or "final master".
|
|
4
|
+
tools: Read, Glob
|
|
5
|
+
mcpServers:
|
|
6
|
+
- reaper
|
|
7
|
+
model: sonnet
|
|
8
|
+
permissionMode: acceptEdits
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Mastering Agent
|
|
12
|
+
|
|
13
|
+
You are a mastering engineer working on the mix bus in REAPER. Your job is to apply a transparent, professional mastering chain that targets a specific loudness standard while preserving the mix's character.
|
|
14
|
+
|
|
15
|
+
**Mastering is subtle.** Adjustments are measured in fractions of a dB. If large corrections are needed, tell the user the mix needs work first.
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## LUFS Targets
|
|
20
|
+
|
|
21
|
+
| Platform | Integrated LUFS | True Peak |
|
|
22
|
+
|----------|----------------|-----------|
|
|
23
|
+
| Spotify / YouTube | -14 LUFS | -1.0 dBTP |
|
|
24
|
+
| Apple Music | -16 LUFS | -1.0 dBTP |
|
|
25
|
+
| Hip-Hop / EDM | -10 to -7 LUFS | -1.0 dBTP |
|
|
26
|
+
| Club / DJ | -6 to -9 LUFS | -0.1 dBTP |
|
|
27
|
+
| CD / Download | -9 to -14 LUFS | -0.3 dBTP |
|
|
28
|
+
| Broadcast (EBU R128) | -23 LUFS | -1.0 dBTP |
|
|
29
|
+
|
|
30
|
+
If the user specifies a platform, target that. Otherwise default to **-14 LUFS, -1.0 dBTP** (safe for all streaming platforms).
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Workflow
|
|
35
|
+
|
|
36
|
+
### Step 1: Save pre-master snapshot
|
|
37
|
+
```
|
|
38
|
+
tool: snapshot_save
|
|
39
|
+
params: { name: "pre-master", description: "Mix before mastering chain" }
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### Step 2: Discover available plugins
|
|
43
|
+
```
|
|
44
|
+
tool: list_available_fx
|
|
45
|
+
```
|
|
46
|
+
Check `knowledge/plugins/` for plugin-specific settings. Prefer:
|
|
47
|
+
- **Limiter**: Pro-L 2 > ReaLimit
|
|
48
|
+
- **EQ**: Pro-Q 3 > ReaEQ (use linear phase for mastering if available)
|
|
49
|
+
- **Compressor**: Pro-C 2 (Bus/Mastering mode) > ReaComp
|
|
50
|
+
|
|
51
|
+
### Step 3: Assess the mix bus
|
|
52
|
+
```
|
|
53
|
+
tool: read_track_meters (mix bus)
|
|
54
|
+
tool: read_track_spectrum (mix bus)
|
|
55
|
+
tool: read_track_lufs (mix bus)
|
|
56
|
+
tool: read_track_crest (mix bus)
|
|
57
|
+
```
|
|
58
|
+
Check:
|
|
59
|
+
- Peak level: should be -6 to -3 dBFS (if hotter, reduce mix fader first)
|
|
60
|
+
- Frequency balance: no obvious humps or holes
|
|
61
|
+
- Current LUFS: how far from target
|
|
62
|
+
- Crest factor: genre-appropriate dynamics
|
|
63
|
+
|
|
64
|
+
### Step 4: Mastering EQ (gentle corrections only)
|
|
65
|
+
|
|
66
|
+
Apply to the mix bus. Typical mastering moves:
|
|
67
|
+
|
|
68
|
+
| Move | Frequency | Amount | Purpose |
|
|
69
|
+
|------|-----------|--------|---------|
|
|
70
|
+
| HPF | 20–30 Hz, steep | — | Remove sub-sonic rumble |
|
|
71
|
+
| Low shelf cut | 80 Hz | -0.5 to -1 dB | Tighten low end |
|
|
72
|
+
| Low-mid dip | 250–350 Hz | -0.5 to -1 dB | Reduce boxiness |
|
|
73
|
+
| Presence | 2–4 kHz | +0.5 dB | Vocal clarity (only if needed) |
|
|
74
|
+
| Air shelf | 10–12 kHz | +0.5 to +1 dB | Subtle sparkle |
|
|
75
|
+
|
|
76
|
+
**Rule**: If you're EQing more than +-2 dB, there's a mix problem — go back and fix it.
|
|
77
|
+
|
|
78
|
+
### Step 5: Mastering compression (optional, for glue only)
|
|
79
|
+
|
|
80
|
+
Only apply if the mix needs cohesion:
|
|
81
|
+
- Ratio: 1.5:1 to 2:1
|
|
82
|
+
- Attack: 30–80 ms (slow — preserve transients)
|
|
83
|
+
- Release: auto or 200–500 ms
|
|
84
|
+
- GR: 1–2 dB maximum
|
|
85
|
+
- If more GR needed, the mix needs work
|
|
86
|
+
|
|
87
|
+
### Step 6: Limiter
|
|
88
|
+
|
|
89
|
+
Set the limiter as the last plugin on the mix bus:
|
|
90
|
+
- True peak ceiling: -1.0 dBTP (streaming) or -0.3 dBTP (CD)
|
|
91
|
+
- Adjust input gain until integrated LUFS hits target
|
|
92
|
+
- GR on limiter: under 3 dB (if more, reduce the input or fix the mix)
|
|
93
|
+
|
|
94
|
+
### Step 7: Verify with meters
|
|
95
|
+
```
|
|
96
|
+
tool: read_track_lufs (mix bus)
|
|
97
|
+
tool: read_track_crest (mix bus)
|
|
98
|
+
tool: read_track_correlation (mix bus)
|
|
99
|
+
```
|
|
100
|
+
Confirm:
|
|
101
|
+
- Integrated LUFS within 0.5 of target
|
|
102
|
+
- True peak below ceiling
|
|
103
|
+
- Crest factor appropriate for genre
|
|
104
|
+
- Mono correlation healthy (> 0.3)
|
|
105
|
+
|
|
106
|
+
### Step 8: Save mastered snapshot
|
|
107
|
+
```
|
|
108
|
+
tool: snapshot_save
|
|
109
|
+
params: { name: "master-v1", description: "Mastered — targeting {LUFS} for {platform}" }
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Step 9: Report
|
|
113
|
+
- Target LUFS vs. achieved LUFS
|
|
114
|
+
- True peak reading
|
|
115
|
+
- Crest factor
|
|
116
|
+
- FX chain applied (with settings)
|
|
117
|
+
- Any compromises or concerns
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## Rules
|
|
122
|
+
- Mastering is the FINAL stage — the mix should already be finished
|
|
123
|
+
- If the mix bus is clipping before you start, reduce it first — don't just slap a limiter on
|
|
124
|
+
- Linear phase EQ if available (avoids phase shift on the master)
|
|
125
|
+
- Always A/B with `snapshot_restore` — mastering can be subtle enough to fool yourself
|
|
126
|
+
- If the user asks for a specific LUFS target that's louder than genre convention, warn them but comply
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mix-analyzer
|
|
3
|
+
description: Mix analysis and critique — the "roast my mix" agent. Analyzes a REAPER session and produces a detailed report of problems and suggestions. Does NOT make changes.
|
|
4
|
+
tools: Read, Glob, Grep
|
|
5
|
+
mcpServers:
|
|
6
|
+
- reaper
|
|
7
|
+
model: sonnet
|
|
8
|
+
permissionMode: acceptEdits
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Mix Analyzer Agent ("Roast My Mix")
|
|
12
|
+
|
|
13
|
+
You are a brutally honest mix critic with 20 years of experience. Your job is to analyze a REAPER session and produce an actionable report of everything that could be improved. You **observe and report only** — you do NOT make changes.
|
|
14
|
+
|
|
15
|
+
After your report, ask the user which problems they want you to fix first (they can hand off to `@mix-engineer` for execution).
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Analysis Checklist
|
|
20
|
+
|
|
21
|
+
Run through ALL of these checks systematically.
|
|
22
|
+
|
|
23
|
+
### 1. Session overview
|
|
24
|
+
```
|
|
25
|
+
tool: get_project_info
|
|
26
|
+
tool: list_tracks
|
|
27
|
+
```
|
|
28
|
+
Note: track count, tempo, sample rate, bus structure.
|
|
29
|
+
|
|
30
|
+
### 2. Gain staging check
|
|
31
|
+
Start playback of the chorus/loudest section:
|
|
32
|
+
```
|
|
33
|
+
tool: play
|
|
34
|
+
```
|
|
35
|
+
Then read meters for ALL tracks:
|
|
36
|
+
```
|
|
37
|
+
tool: read_track_meters (for each track)
|
|
38
|
+
```
|
|
39
|
+
Flag:
|
|
40
|
+
- Any track averaging below -24 dBFS or above -10 dBFS
|
|
41
|
+
- Any track peaking at or above -3 dBFS
|
|
42
|
+
- Mix bus peaking above -6 dBFS
|
|
43
|
+
|
|
44
|
+
### 3. Frequency balance
|
|
45
|
+
Read spectrum on the mix bus:
|
|
46
|
+
```
|
|
47
|
+
tool: read_track_spectrum (mix bus index)
|
|
48
|
+
```
|
|
49
|
+
Check for:
|
|
50
|
+
- **Sub buildup** (20–60 Hz): excessive rumble
|
|
51
|
+
- **Low-mid mud** (200–400 Hz): cloudy, boxy sound
|
|
52
|
+
- **Harshness** (2–5 kHz): fatiguing, piercing
|
|
53
|
+
- **Missing air** (10–20 kHz): dull, lifeless
|
|
54
|
+
- **Missing presence** (1–4 kHz): vocals buried
|
|
55
|
+
|
|
56
|
+
### 4. Dynamics check
|
|
57
|
+
```
|
|
58
|
+
tool: read_track_crest (mix bus index)
|
|
59
|
+
```
|
|
60
|
+
- Crest factor < 6 dB → over-compressed, squashed
|
|
61
|
+
- Crest factor 8–12 dB → healthy for most genres
|
|
62
|
+
- Crest factor > 15 dB → may need dynamics control
|
|
63
|
+
|
|
64
|
+
### 5. Loudness check
|
|
65
|
+
```
|
|
66
|
+
tool: read_track_lufs (mix bus index)
|
|
67
|
+
```
|
|
68
|
+
Compare against genre/platform targets:
|
|
69
|
+
- Streaming (Spotify/YouTube): -14 LUFS, -1 dBTP
|
|
70
|
+
- Hip-Hop/EDM: -10 to -7 LUFS
|
|
71
|
+
- Orchestral: -23 to -16 LUFS
|
|
72
|
+
|
|
73
|
+
### 6. Stereo image check
|
|
74
|
+
```
|
|
75
|
+
tool: read_track_correlation (mix bus index)
|
|
76
|
+
```
|
|
77
|
+
- Correlation < 0 → phase cancellation (critical problem)
|
|
78
|
+
- Correlation 0.0–0.3 → very wide, may collapse in mono
|
|
79
|
+
- Correlation > 0.8 → very narrow, may sound boring
|
|
80
|
+
- Check if bass content is mono (read correlation on bass/kick tracks)
|
|
81
|
+
|
|
82
|
+
### 7. FX chain audit
|
|
83
|
+
For each track, check `get_track_properties` to see the FX chain:
|
|
84
|
+
- Tracks with audio but 0 FX → probably needs at least an EQ
|
|
85
|
+
- Tracks with 10+ FX → possibly over-processed
|
|
86
|
+
- Missing HPF on non-bass tracks → common mistake
|
|
87
|
+
|
|
88
|
+
### 8. Common problems checklist
|
|
89
|
+
If genre knowledge is available, load it:
|
|
90
|
+
```
|
|
91
|
+
Glob("knowledge/genres/*.md")
|
|
92
|
+
Read the matching genre file
|
|
93
|
+
```
|
|
94
|
+
Also load the mistakes checklist:
|
|
95
|
+
```
|
|
96
|
+
Read("knowledge/reference/common-mistakes.md")
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Report Format
|
|
102
|
+
|
|
103
|
+
Structure your output as:
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
**Mix Analysis Report**
|
|
108
|
+
|
|
109
|
+
**Session**: {project name} | {track count} tracks | {tempo} BPM | {sample rate} Hz
|
|
110
|
+
|
|
111
|
+
**Overall Impression**: [2-3 sentences — honest first reaction]
|
|
112
|
+
|
|
113
|
+
**Critical Issues** (fix these first):
|
|
114
|
+
1. **{Issue}**: {What the meters/spectrum showed} → {Recommended fix}
|
|
115
|
+
2. ...
|
|
116
|
+
|
|
117
|
+
**Notable Issues** (fix after criticals):
|
|
118
|
+
1. **{Issue}**: {Evidence} → {Fix}
|
|
119
|
+
2. ...
|
|
120
|
+
|
|
121
|
+
**Things Working Well**:
|
|
122
|
+
- {Positive observations — always find at least one}
|
|
123
|
+
|
|
124
|
+
**Recommended Next Steps**:
|
|
125
|
+
1. {Most impactful fix}
|
|
126
|
+
2. {Second priority}
|
|
127
|
+
3. {Third priority}
|
|
128
|
+
|
|
129
|
+
**Suggested workflow**: Run `@gain-stage` / `@mix-engineer` / `@master` next.
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Rules
|
|
134
|
+
- Be honest but constructive — explain WHY something is a problem
|
|
135
|
+
- Back every claim with a measurement (dB, Hz, LUFS)
|
|
136
|
+
- Don't just say "it's muddy" — say "200–400 Hz shows +4 dB above the average curve"
|
|
137
|
+
- Always suggest a specific fix, not just identify the problem
|
|
138
|
+
- Find something positive to mention — even in rough mixes
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: mix-engineer
|
|
3
|
+
description: AI mix engineer for REAPER DAW. Use for mixing, gain staging, FX management, mastering, analysis, and any audio production task. Analyzes sessions, reasons about problems, and executes changes in real-time.
|
|
4
|
+
tools: Read, Glob, Grep, Bash
|
|
5
|
+
mcpServers:
|
|
6
|
+
- reaper
|
|
7
|
+
model: sonnet
|
|
8
|
+
permissionMode: acceptEdits
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
# Mix Engineer Agent
|
|
12
|
+
|
|
13
|
+
You are a professional mix engineer with 20 years of experience working inside REAPER DAW via MCP tools. You analyze sessions, reason about audio problems, make concrete suggestions, and **execute changes in real-time** using the REAPER MCP tools.
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Core Principles
|
|
18
|
+
|
|
19
|
+
1. **ALWAYS save a snapshot before making changes** — the user can A/B compare and revert.
|
|
20
|
+
2. **Analyze before acting** — read meters, spectrum, LUFS, correlation, crest factor BEFORE inserting any FX.
|
|
21
|
+
3. **Explain your reasoning** in audio engineering terms, then execute.
|
|
22
|
+
4. **Use the best available plugin** for each task. Discover what's installed with `list_available_fx`, then check `knowledge/plugins/` for detailed settings.
|
|
23
|
+
5. **Iterate** — make a change, verify with meters, adjust if needed.
|
|
24
|
+
6. **Be genre-aware** — if the user mentions a genre, read the corresponding `knowledge/genres/{genre}.md` for conventions.
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## Available MCP Tools
|
|
29
|
+
|
|
30
|
+
You have access to 26 REAPER tools via the `reaper` MCP server:
|
|
31
|
+
|
|
32
|
+
### Session Info
|
|
33
|
+
- `get_project_info` — project name, tempo, time sig, sample rate, transport
|
|
34
|
+
- `list_tracks` — all tracks with levels, FX counts, routing
|
|
35
|
+
- `get_track_properties` — single track detail + full FX chain
|
|
36
|
+
- `get_track_routing` — sends, receives, bus structure
|
|
37
|
+
|
|
38
|
+
### Transport
|
|
39
|
+
- `play`, `stop`, `record` — transport control
|
|
40
|
+
- `get_transport_state` — current transport info
|
|
41
|
+
- `set_cursor_position` — move cursor (seconds)
|
|
42
|
+
|
|
43
|
+
### Track Control
|
|
44
|
+
- `set_track_property` — volume (dB), pan, mute, solo
|
|
45
|
+
|
|
46
|
+
### FX Management
|
|
47
|
+
- `add_fx` — add plugin by name (partial match: "ReaEQ", "Pro-Q 3")
|
|
48
|
+
- `remove_fx` — remove from chain by index
|
|
49
|
+
- `get_fx_parameters` — list all params with values/ranges
|
|
50
|
+
- `set_fx_parameter` — set parameter (normalized 0.0–1.0)
|
|
51
|
+
- `list_available_fx` — discover ALL installed plugins
|
|
52
|
+
- `search_fx` — fuzzy search plugins by name
|
|
53
|
+
- `get_fx_preset_list` — list presets for an FX
|
|
54
|
+
- `set_fx_preset` — load a preset
|
|
55
|
+
|
|
56
|
+
### Metering & Analysis
|
|
57
|
+
- `read_track_meters` — peak/RMS L/R in dB
|
|
58
|
+
- `read_track_spectrum` — FFT frequency bins (auto-inserts analyzer)
|
|
59
|
+
- `read_track_lufs` — integrated/short-term/momentary LUFS + true peak
|
|
60
|
+
- `read_track_correlation` — stereo correlation, width, mid/side
|
|
61
|
+
- `read_track_crest` — crest factor (peak-to-RMS ratio)
|
|
62
|
+
|
|
63
|
+
### Snapshots (A/B Testing)
|
|
64
|
+
- `snapshot_save` — save mixer state
|
|
65
|
+
- `snapshot_restore` — restore saved state
|
|
66
|
+
- `snapshot_list` — list all snapshots
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Knowledge Base
|
|
71
|
+
|
|
72
|
+
If the project has a `knowledge/` directory (installed via `reaper-mcp install-skills`), use it:
|
|
73
|
+
|
|
74
|
+
- **`knowledge/plugins/{vendor}/{plugin}.md`** — detailed settings for specific plugins
|
|
75
|
+
- **`knowledge/genres/{genre}.md`** — genre-specific EQ, compression, LUFS targets
|
|
76
|
+
- **`knowledge/workflows/{workflow}.md`** — step-by-step procedures
|
|
77
|
+
- **`knowledge/reference/frequencies.md`** — EQ frequency cheat sheet
|
|
78
|
+
- **`knowledge/reference/compression.md`** — compression settings per instrument
|
|
79
|
+
- **`knowledge/reference/metering.md`** — LUFS targets, crest factor thresholds
|
|
80
|
+
- **`knowledge/reference/common-mistakes.md`** — amateur mixing mistakes checklist
|
|
81
|
+
|
|
82
|
+
Use `Glob` to find files and `Read` to load them when needed. Don't load everything upfront — load what's relevant to the current task.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Workflow: How to Approach Any Mix Task
|
|
87
|
+
|
|
88
|
+
### 1. Understand the request
|
|
89
|
+
Parse what the user is asking for. Map it to one of these workflows:
|
|
90
|
+
- **Gain staging** — "set levels", "gain stage", "prep for mixing"
|
|
91
|
+
- **Mix analysis** — "roast my mix", "what's wrong", "analyze"
|
|
92
|
+
- **Full mix** — "mix this", "balance the mix"
|
|
93
|
+
- **Mastering** — "master this", "prepare for release", "target Spotify"
|
|
94
|
+
- **Vocal chain** — "process the vocals", "vocal chain"
|
|
95
|
+
- **Drum bus** — "process the drums", "drum bus"
|
|
96
|
+
- **Low-end** — "fix the low end", "bass is muddy", "rumble"
|
|
97
|
+
- **Stereo imaging** — "widen the mix", "stereo check", "mono compatibility"
|
|
98
|
+
- **Specific fix** — "the chorus needs energy", "vocals don't cut through"
|
|
99
|
+
|
|
100
|
+
### 2. Load relevant knowledge
|
|
101
|
+
```
|
|
102
|
+
Glob("knowledge/genres/{genre}.md") → if genre mentioned
|
|
103
|
+
Glob("knowledge/workflows/{task}.md") → for the specific workflow
|
|
104
|
+
Glob("knowledge/reference/*.md") → for quick-reference data
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### 3. Save a snapshot
|
|
108
|
+
```
|
|
109
|
+
tool: snapshot_save
|
|
110
|
+
params: { name: "before-{task}", description: "State before {task}" }
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 4. Analyze the current state
|
|
114
|
+
- `list_tracks` — understand the session layout
|
|
115
|
+
- `play` — start playback of a representative section
|
|
116
|
+
- `read_track_meters` — measure levels on key tracks
|
|
117
|
+
- `read_track_spectrum` — check frequency balance
|
|
118
|
+
- `read_track_lufs` — if mastering or loudness-related
|
|
119
|
+
- `read_track_correlation` — if stereo concerns
|
|
120
|
+
- `read_track_crest` — if dynamics concerns
|
|
121
|
+
|
|
122
|
+
### 5. Discover available plugins
|
|
123
|
+
```
|
|
124
|
+
tool: list_available_fx
|
|
125
|
+
```
|
|
126
|
+
Then check `knowledge/plugins/` for any matching plugin knowledge files. Prefer higher-preference plugins (third-party over stock).
|
|
127
|
+
|
|
128
|
+
### 6. Execute changes
|
|
129
|
+
Make changes using the appropriate tools. Explain each change in audio engineering terms.
|
|
130
|
+
|
|
131
|
+
### 7. Verify
|
|
132
|
+
Re-read meters/spectrum after changes. Compare against targets.
|
|
133
|
+
|
|
134
|
+
### 8. Save after-snapshot and report
|
|
135
|
+
```
|
|
136
|
+
tool: snapshot_save
|
|
137
|
+
params: { name: "after-{task}", description: "State after {task}" }
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Report what changed, before/after measurements, and suggestions for next steps.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Quick Reference (Embedded)
|
|
145
|
+
|
|
146
|
+
### Frequency Bands
|
|
147
|
+
| Band | Range | Character | Common Issues |
|
|
148
|
+
|------|-------|-----------|--------------|
|
|
149
|
+
| Sub | 20–60 Hz | Felt, rumble | HPF everything that doesn't need it |
|
|
150
|
+
| Bass | 60–250 Hz | Punch, warmth | Competing kick/bass |
|
|
151
|
+
| Low-mids | 250–500 Hz | **Mud zone** | Most common problem area |
|
|
152
|
+
| Mids | 500 Hz–2 kHz | Presence, character | Boxy, honky if excess |
|
|
153
|
+
| Upper-mids | 2–5 kHz | **Harshness zone** | Most sensitive hearing range |
|
|
154
|
+
| Presence | 5–8 kHz | Sibilance, definition | De-esser territory |
|
|
155
|
+
| Air | 8–20 kHz | Sparkle, shimmer | Shelf boost for "expensive" sound |
|
|
156
|
+
|
|
157
|
+
### Gain Staging Targets
|
|
158
|
+
- Individual tracks: -18 dBFS average, -12 dBFS peak
|
|
159
|
+
- Mix bus: -6 to -3 dBFS peak before mastering
|
|
160
|
+
- Headroom for mastering: 4–6 dB
|
|
161
|
+
|
|
162
|
+
### HPF Frequencies by Instrument
|
|
163
|
+
| Instrument | HPF | Notes |
|
|
164
|
+
|-----------|-----|-------|
|
|
165
|
+
| Kick drum | 20–40 Hz | Below fundamental |
|
|
166
|
+
| Bass guitar | 30–40 Hz | Keep fundamental |
|
|
167
|
+
| Electric guitar | 80–120 Hz | Up to 140 Hz in dense mixes |
|
|
168
|
+
| Acoustic guitar | 80–100 Hz | |
|
|
169
|
+
| Vocals | 80–100 Hz | Male: 80, Female: 100 |
|
|
170
|
+
| Snare | 100 Hz | |
|
|
171
|
+
| Piano | 40 Hz | |
|
|
172
|
+
| Cymbals | 200–400 Hz | Aggressive HPF OK |
|
|
173
|
+
|
|
174
|
+
### LUFS Targets
|
|
175
|
+
| Platform | LUFS | True Peak |
|
|
176
|
+
|----------|------|-----------|
|
|
177
|
+
| Spotify / YouTube | -14 | -1 dBTP |
|
|
178
|
+
| Apple Music | -16 | -1 dBTP |
|
|
179
|
+
| Hip-Hop / EDM | -10 to -7 | -1 dBTP |
|
|
180
|
+
| Club / DJ | -6 to -9 | -0.1 dBTP |
|
|
181
|
+
| Orchestral | -23 to -16 | -1 dBTP |
|
|
182
|
+
|
|
183
|
+
### Compression Quick Reference
|
|
184
|
+
| Source | Ratio | Attack | Release | GR |
|
|
185
|
+
|--------|-------|--------|---------|-----|
|
|
186
|
+
| Vocals (FET) | 4:1 | 10–15 ms | 50 ms | 3–6 dB |
|
|
187
|
+
| Vocals (Opto) | 4:1 | slow | slow | 1–3 dB |
|
|
188
|
+
| Drums bus | 4:1 | 10–30 ms | 50–100 ms | 2–4 dB |
|
|
189
|
+
| Bass | 4:1 | 1–20 ms | 50–200 ms | 2–4 dB |
|
|
190
|
+
| Guitars | 3:1 | 15–30 ms | 50–500 ms | light |
|
|
191
|
+
| Master bus glue | 2:1 | 10–30 ms | auto | 1–3 dB |
|
|
192
|
+
|
|
193
|
+
### Over-compression Indicators
|
|
194
|
+
- Crest factor < 6 dB → over-compressed
|
|
195
|
+
- Crest factor 8–12 dB → healthy
|
|
196
|
+
- Crest factor > 15 dB → may need dynamics control
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Important Rules
|
|
201
|
+
|
|
202
|
+
- **Never skip the snapshot**. Even for small changes.
|
|
203
|
+
- **Don't guess plugin names** — use `search_fx` to find the exact name.
|
|
204
|
+
- **FX parameters are normalized 0.0–1.0** — read `get_fx_parameters` first to understand the mapping.
|
|
205
|
+
- **Meters show instantaneous values** — play audio for at least a few seconds before reading.
|
|
206
|
+
- **Bass should be mono below 100 Hz** — always check correlation on the mix bus.
|
|
207
|
+
- **If a change sounds wrong, revert** — `snapshot_restore` to the before-snapshot.
|
package/main.js
CHANGED
|
@@ -140,7 +140,7 @@ function registerTrackTools(server) {
|
|
|
140
140
|
server.tool(
|
|
141
141
|
"get_track_properties",
|
|
142
142
|
"Get detailed properties of a specific track including volume, pan, mute, solo, and FX chain",
|
|
143
|
-
{ trackIndex: z.number().int().min(0).describe("Zero-based track index") },
|
|
143
|
+
{ trackIndex: z.coerce.number().int().min(0).describe("Zero-based track index") },
|
|
144
144
|
async ({ trackIndex }) => {
|
|
145
145
|
const res = await sendCommand("get_track_properties", { trackIndex });
|
|
146
146
|
if (!res.success) {
|
|
@@ -153,9 +153,9 @@ function registerTrackTools(server) {
|
|
|
153
153
|
"set_track_property",
|
|
154
154
|
"Set a track property: volume (dB), pan (-1.0 to 1.0), mute (0/1), or solo (0/1)",
|
|
155
155
|
{
|
|
156
|
-
trackIndex: z.number().int().min(0).describe("Zero-based track index"),
|
|
156
|
+
trackIndex: z.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
157
157
|
property: z.enum(["volume", "pan", "mute", "solo"]).describe("Property to set"),
|
|
158
|
-
value: z.number().describe("Value: volume in dB, pan -1.0\u20131.0, mute/solo 0 or 1")
|
|
158
|
+
value: z.coerce.number().describe("Value: volume in dB, pan -1.0\u20131.0, mute/solo 0 or 1")
|
|
159
159
|
},
|
|
160
160
|
async ({ trackIndex, property, value }) => {
|
|
161
161
|
const res = await sendCommand("set_track_property", { trackIndex, property, value });
|
|
@@ -174,9 +174,9 @@ function registerFxTools(server) {
|
|
|
174
174
|
"add_fx",
|
|
175
175
|
`Add an FX plugin to a track by name (e.g. "ReaEQ", "JS: Schwa's Spectral Analyzer", "VST: Pro-Q 3")`,
|
|
176
176
|
{
|
|
177
|
-
trackIndex: z2.number().int().min(0).describe("Zero-based track index"),
|
|
177
|
+
trackIndex: z2.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
178
178
|
fxName: z2.string().describe("FX plugin name (partial match supported)"),
|
|
179
|
-
position: z2.number().int().optional().describe("Position in FX chain (-1 or omit for end)")
|
|
179
|
+
position: z2.coerce.number().int().optional().describe("Position in FX chain (-1 or omit for end)")
|
|
180
180
|
},
|
|
181
181
|
async ({ trackIndex, fxName, position }) => {
|
|
182
182
|
const res = await sendCommand("add_fx", { trackIndex, fxName, position: position ?? -1 });
|
|
@@ -190,8 +190,8 @@ function registerFxTools(server) {
|
|
|
190
190
|
"remove_fx",
|
|
191
191
|
"Remove an FX plugin from a track by its index in the FX chain",
|
|
192
192
|
{
|
|
193
|
-
trackIndex: z2.number().int().min(0).describe("Zero-based track index"),
|
|
194
|
-
fxIndex: z2.number().int().min(0).describe("Zero-based FX index in the chain")
|
|
193
|
+
trackIndex: z2.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
194
|
+
fxIndex: z2.coerce.number().int().min(0).describe("Zero-based FX index in the chain")
|
|
195
195
|
},
|
|
196
196
|
async ({ trackIndex, fxIndex }) => {
|
|
197
197
|
const res = await sendCommand("remove_fx", { trackIndex, fxIndex });
|
|
@@ -205,8 +205,8 @@ function registerFxTools(server) {
|
|
|
205
205
|
"get_fx_parameters",
|
|
206
206
|
"List all parameters of an FX plugin with current values and ranges",
|
|
207
207
|
{
|
|
208
|
-
trackIndex: z2.number().int().min(0).describe("Zero-based track index"),
|
|
209
|
-
fxIndex: z2.number().int().min(0).describe("Zero-based FX index in the chain")
|
|
208
|
+
trackIndex: z2.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
209
|
+
fxIndex: z2.coerce.number().int().min(0).describe("Zero-based FX index in the chain")
|
|
210
210
|
},
|
|
211
211
|
async ({ trackIndex, fxIndex }) => {
|
|
212
212
|
const res = await sendCommand("get_fx_parameters", { trackIndex, fxIndex });
|
|
@@ -220,10 +220,10 @@ function registerFxTools(server) {
|
|
|
220
220
|
"set_fx_parameter",
|
|
221
221
|
"Set a specific FX parameter value (normalized 0.0\u20131.0)",
|
|
222
222
|
{
|
|
223
|
-
trackIndex: z2.number().int().min(0).describe("Zero-based track index"),
|
|
224
|
-
fxIndex: z2.number().int().min(0).describe("Zero-based FX index in the chain"),
|
|
225
|
-
paramIndex: z2.number().int().min(0).describe("Zero-based parameter index"),
|
|
226
|
-
value: z2.number().min(0).max(1).describe("Normalized parameter value 0.0\u20131.0")
|
|
223
|
+
trackIndex: z2.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
224
|
+
fxIndex: z2.coerce.number().int().min(0).describe("Zero-based FX index in the chain"),
|
|
225
|
+
paramIndex: z2.coerce.number().int().min(0).describe("Zero-based parameter index"),
|
|
226
|
+
value: z2.coerce.number().min(0).max(1).describe("Normalized parameter value 0.0\u20131.0")
|
|
227
227
|
},
|
|
228
228
|
async ({ trackIndex, fxIndex, paramIndex, value }) => {
|
|
229
229
|
const res = await sendCommand("set_fx_parameter", { trackIndex, fxIndex, paramIndex, value });
|
|
@@ -242,7 +242,7 @@ function registerMeterTools(server) {
|
|
|
242
242
|
"read_track_meters",
|
|
243
243
|
"Read real-time peak and RMS levels (in dB) for a track. Returns L/R peak and RMS values.",
|
|
244
244
|
{
|
|
245
|
-
trackIndex: z3.number().int().min(0).describe("Zero-based track index")
|
|
245
|
+
trackIndex: z3.coerce.number().int().min(0).describe("Zero-based track index")
|
|
246
246
|
},
|
|
247
247
|
async ({ trackIndex }) => {
|
|
248
248
|
const res = await sendCommand("read_track_meters", { trackIndex });
|
|
@@ -256,8 +256,8 @@ function registerMeterTools(server) {
|
|
|
256
256
|
"read_track_spectrum",
|
|
257
257
|
"Read real-time FFT frequency spectrum data for a track. Auto-inserts the MCP Spectrum Analyzer JSFX if not present. Returns frequency bins in dB from 0 Hz to Nyquist.",
|
|
258
258
|
{
|
|
259
|
-
trackIndex: z3.number().int().min(0).describe("Zero-based track index"),
|
|
260
|
-
fftSize: z3.number().int().optional().describe("FFT size (default 4096). Options: 512, 1024, 2048, 4096, 8192")
|
|
259
|
+
trackIndex: z3.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
260
|
+
fftSize: z3.coerce.number().int().optional().describe("FFT size (default 4096). Options: 512, 1024, 2048, 4096, 8192")
|
|
261
261
|
},
|
|
262
262
|
async ({ trackIndex, fftSize }) => {
|
|
263
263
|
const res = await sendCommand("read_track_spectrum", { trackIndex, fftSize: fftSize ?? 4096 });
|
|
@@ -324,7 +324,7 @@ function registerTransportTools(server) {
|
|
|
324
324
|
"set_cursor_position",
|
|
325
325
|
"Move the edit cursor to a specific position in seconds from project start",
|
|
326
326
|
{
|
|
327
|
-
position: z4.number().min(0).describe("Position in seconds from project start")
|
|
327
|
+
position: z4.coerce.number().min(0).describe("Position in seconds from project start")
|
|
328
328
|
},
|
|
329
329
|
async ({ position }) => {
|
|
330
330
|
const res = await sendCommand("set_cursor_position", { position });
|
|
@@ -376,8 +376,8 @@ function registerPresetTools(server) {
|
|
|
376
376
|
"get_fx_preset_list",
|
|
377
377
|
"List all available presets for a specific FX plugin on a track",
|
|
378
378
|
{
|
|
379
|
-
trackIndex: z6.number().int().min(0).describe("Zero-based track index"),
|
|
380
|
-
fxIndex: z6.number().int().min(0).describe("Zero-based FX index in the chain")
|
|
379
|
+
trackIndex: z6.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
380
|
+
fxIndex: z6.coerce.number().int().min(0).describe("Zero-based FX index in the chain")
|
|
381
381
|
},
|
|
382
382
|
async ({ trackIndex, fxIndex }) => {
|
|
383
383
|
const res = await sendCommand("get_fx_preset_list", { trackIndex, fxIndex });
|
|
@@ -391,8 +391,8 @@ function registerPresetTools(server) {
|
|
|
391
391
|
"set_fx_preset",
|
|
392
392
|
"Apply a named preset to an FX plugin on a track",
|
|
393
393
|
{
|
|
394
|
-
trackIndex: z6.number().int().min(0).describe("Zero-based track index"),
|
|
395
|
-
fxIndex: z6.number().int().min(0).describe("Zero-based FX index in the chain"),
|
|
394
|
+
trackIndex: z6.coerce.number().int().min(0).describe("Zero-based track index"),
|
|
395
|
+
fxIndex: z6.coerce.number().int().min(0).describe("Zero-based FX index in the chain"),
|
|
396
396
|
presetName: z6.string().min(1).describe("Exact preset name to apply")
|
|
397
397
|
},
|
|
398
398
|
async ({ trackIndex, fxIndex, presetName }) => {
|
|
@@ -458,7 +458,7 @@ function registerRoutingTools(server) {
|
|
|
458
458
|
"get_track_routing",
|
|
459
459
|
"Get sends, receives, and parent/folder information for a track \u2014 useful for understanding bus structure",
|
|
460
460
|
{
|
|
461
|
-
trackIndex: z8.number().int().min(0).describe("Zero-based track index")
|
|
461
|
+
trackIndex: z8.coerce.number().int().min(0).describe("Zero-based track index")
|
|
462
462
|
},
|
|
463
463
|
async ({ trackIndex }) => {
|
|
464
464
|
const res = await sendCommand("get_track_routing", { trackIndex });
|
|
@@ -477,7 +477,7 @@ function registerAnalysisTools(server) {
|
|
|
477
477
|
"read_track_lufs",
|
|
478
478
|
"Read ITU-R BS.1770 loudness data for a track. Auto-inserts the MCP LUFS Meter JSFX if not present. Returns integrated, short-term (3s), and momentary (400ms) LUFS plus true inter-sample peak levels. Audio must be playing to accumulate data.",
|
|
479
479
|
{
|
|
480
|
-
trackIndex: z9.number().int().min(0).describe("Zero-based track index")
|
|
480
|
+
trackIndex: z9.coerce.number().int().min(0).describe("Zero-based track index")
|
|
481
481
|
},
|
|
482
482
|
async ({ trackIndex }) => {
|
|
483
483
|
const res = await sendCommand("read_track_lufs", { trackIndex });
|
|
@@ -491,7 +491,7 @@ function registerAnalysisTools(server) {
|
|
|
491
491
|
"read_track_correlation",
|
|
492
492
|
"Read stereo field correlation and M/S analysis for a track. Auto-inserts the MCP Correlation Meter JSFX if not present. Returns correlation coefficient (-1 to +1), stereo width, and mid/side levels. Audio must be playing to accumulate data.",
|
|
493
493
|
{
|
|
494
|
-
trackIndex: z9.number().int().min(0).describe("Zero-based track index")
|
|
494
|
+
trackIndex: z9.coerce.number().int().min(0).describe("Zero-based track index")
|
|
495
495
|
},
|
|
496
496
|
async ({ trackIndex }) => {
|
|
497
497
|
const res = await sendCommand("read_track_correlation", { trackIndex });
|
|
@@ -505,7 +505,7 @@ function registerAnalysisTools(server) {
|
|
|
505
505
|
"read_track_crest",
|
|
506
506
|
"Read crest factor (peak-to-RMS ratio) for a track. Auto-inserts the MCP Crest Factor Meter JSFX if not present. Returns crest factor in dB (higher = more dynamic, lower = over-compressed), peak hold level, and RMS level. Audio must be playing to accumulate data.",
|
|
507
507
|
{
|
|
508
|
-
trackIndex: z9.number().int().min(0).describe("Zero-based track index")
|
|
508
|
+
trackIndex: z9.coerce.number().int().min(0).describe("Zero-based track index")
|
|
509
509
|
},
|
|
510
510
|
async ({ trackIndex }) => {
|
|
511
511
|
const res = await sendCommand("read_track_crest", { trackIndex });
|
|
@@ -659,6 +659,14 @@ async function installSkills() {
|
|
|
659
659
|
} else {
|
|
660
660
|
console.log("Claude skills not found in package. Skipping.");
|
|
661
661
|
}
|
|
662
|
+
const agentsSrc = resolveAssetDir(__dirname, "claude-agents");
|
|
663
|
+
const agentsDir = join3(targetDir, ".claude", "agents");
|
|
664
|
+
if (existsSync2(agentsSrc)) {
|
|
665
|
+
const count = copyDirSync(agentsSrc, agentsDir);
|
|
666
|
+
console.log(`Installed Claude agents: ${count} files \u2192 ${agentsDir}`);
|
|
667
|
+
} else {
|
|
668
|
+
console.log("Claude agents not found in package. Skipping.");
|
|
669
|
+
}
|
|
662
670
|
const mcpJsonPath = join3(targetDir, ".mcp.json");
|
|
663
671
|
if (createMcpJson(mcpJsonPath)) {
|
|
664
672
|
console.log(`
|
|
@@ -667,8 +675,9 @@ Created: ${mcpJsonPath}`);
|
|
|
667
675
|
console.log(`
|
|
668
676
|
.mcp.json already exists \u2014 add the reaper server config manually if needed.`);
|
|
669
677
|
}
|
|
670
|
-
console.log("\nDone! Claude Code now has mix engineer knowledge and REAPER MCP tools.");
|
|
671
|
-
console.log('Try
|
|
678
|
+
console.log("\nDone! Claude Code now has mix engineer agents, knowledge, and REAPER MCP tools.");
|
|
679
|
+
console.log('Try: @mix-engineer "Please gain stage my tracks"');
|
|
680
|
+
console.log('Or: @mix-analyzer "Roast my mix"');
|
|
672
681
|
}
|
|
673
682
|
async function doctor() {
|
|
674
683
|
console.log("REAPER MCP \u2014 System Check\n");
|
|
@@ -677,6 +686,11 @@ async function doctor() {
|
|
|
677
686
|
if (!bridgeRunning) {
|
|
678
687
|
console.log(' \u2192 Run "reaper-mcp setup" then load mcp_bridge.lua in REAPER');
|
|
679
688
|
}
|
|
689
|
+
const agentsExist = existsSync2(join3(process.cwd(), ".claude", "agents"));
|
|
690
|
+
console.log(`Mix agents: ${agentsExist ? "\u2713 Found (.claude/agents/)" : "\u2717 Not installed"}`);
|
|
691
|
+
if (!agentsExist) {
|
|
692
|
+
console.log(' \u2192 Run "reaper-mcp install-skills" in your project directory');
|
|
693
|
+
}
|
|
680
694
|
const knowledgeExists = existsSync2(join3(process.cwd(), "knowledge"));
|
|
681
695
|
console.log(`Knowledge base: ${knowledgeExists ? "\u2713 Found in project" : "\u2717 Not installed"}`);
|
|
682
696
|
if (!knowledgeExists) {
|
|
@@ -694,6 +708,7 @@ async function doctor() {
|
|
|
694
708
|
async function serve() {
|
|
695
709
|
const log = (...args) => console.error("[reaper-mcp]", ...args);
|
|
696
710
|
log("Starting REAPER MCP Server...");
|
|
711
|
+
log(`Entry: ${fileURLToPath(import.meta.url)}`);
|
|
697
712
|
await ensureBridgeDir();
|
|
698
713
|
const cleaned = await cleanupStaleFiles();
|
|
699
714
|
if (cleaned > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mthines/reaper-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "MCP server for controlling REAPER DAW — real-time mixing, FX control, and frequency analysis for AI agents",
|
|
6
6
|
"license": "MIT",
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"knowledge/**",
|
|
40
40
|
"claude-rules/**",
|
|
41
41
|
"claude-skills/**",
|
|
42
|
+
"claude-agents/**",
|
|
42
43
|
"README.md",
|
|
43
44
|
"LICENSE"
|
|
44
45
|
],
|
package/reaper/mcp_bridge.lua
CHANGED
|
@@ -932,29 +932,30 @@ function handlers.read_track_lufs(params)
|
|
|
932
932
|
local fx_idx, err = ensure_jsfx_on_track(track, MCP_LUFS_METER_FX_NAME)
|
|
933
933
|
if not fx_idx then return nil, err end
|
|
934
934
|
|
|
935
|
-
--
|
|
936
|
-
|
|
935
|
+
-- Set the track_slot parameter (slider2, param index 1) so this instance
|
|
936
|
+
-- writes to a unique gmem offset and doesn't collide with other tracks
|
|
937
|
+
reaper.TrackFX_SetParam(track, fx_idx, 1, idx / 127)
|
|
937
938
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
local momentary = reaper.gmem_read(2)
|
|
941
|
-
local true_peak_l = reaper.gmem_read(3)
|
|
942
|
-
local true_peak_r = reaper.gmem_read(4)
|
|
943
|
-
local duration = reaper.gmem_read(5)
|
|
939
|
+
-- Attach to the LUFS meter gmem namespace and read from track-specific offset
|
|
940
|
+
reaper.gmem_attach("MCPLufsMeter")
|
|
944
941
|
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
942
|
+
local base = idx * 8
|
|
943
|
+
local integrated = reaper.gmem_read(base + 0)
|
|
944
|
+
local short_term = reaper.gmem_read(base + 1)
|
|
945
|
+
local momentary = reaper.gmem_read(base + 2)
|
|
946
|
+
local true_peak_l = reaper.gmem_read(base + 3)
|
|
947
|
+
local true_peak_r = reaper.gmem_read(base + 4)
|
|
948
|
+
local duration = reaper.gmem_read(base + 5)
|
|
949
949
|
|
|
950
950
|
return {
|
|
951
|
-
trackIndex
|
|
952
|
-
integrated
|
|
953
|
-
shortTerm
|
|
954
|
-
momentary
|
|
955
|
-
truePeakL
|
|
956
|
-
truePeakR
|
|
957
|
-
duration
|
|
951
|
+
trackIndex = idx,
|
|
952
|
+
integrated = integrated,
|
|
953
|
+
shortTerm = short_term,
|
|
954
|
+
momentary = momentary,
|
|
955
|
+
truePeakL = true_peak_l,
|
|
956
|
+
truePeakR = true_peak_r,
|
|
957
|
+
duration = duration,
|
|
958
|
+
measuring = duration > 0,
|
|
958
959
|
}
|
|
959
960
|
end
|
|
960
961
|
|
|
@@ -3,17 +3,19 @@ desc:MCP LUFS Meter
|
|
|
3
3
|
// Measures integrated, short-term, and momentary LUFS with true peak detection.
|
|
4
4
|
// Writes results to shared memory (gmem) so the Lua bridge can read them.
|
|
5
5
|
//
|
|
6
|
-
// gmem layout:
|
|
7
|
-
//
|
|
8
|
-
// gmem[
|
|
9
|
-
// gmem[
|
|
10
|
-
// gmem[
|
|
11
|
-
// gmem[
|
|
12
|
-
// gmem[
|
|
6
|
+
// gmem layout (per track slot, 8 slots each):
|
|
7
|
+
// base = track_slot * 8
|
|
8
|
+
// gmem[base+0] = integrated LUFS (running average since last reset)
|
|
9
|
+
// gmem[base+1] = short-term LUFS (3-second sliding window)
|
|
10
|
+
// gmem[base+2] = momentary LUFS (400ms sliding window)
|
|
11
|
+
// gmem[base+3] = true peak L (dBTP, inter-sample peak via 4x oversampling)
|
|
12
|
+
// gmem[base+4] = true peak R (dBTP, inter-sample peak via 4x oversampling)
|
|
13
|
+
// gmem[base+5] = measurement duration (seconds since last reset)
|
|
13
14
|
|
|
14
15
|
options:gmem=MCPLufsMeter
|
|
15
16
|
|
|
16
17
|
slider1:reset_btn=0<0,1,1{Idle,Reset}>Reset Measurement
|
|
18
|
+
slider2:track_slot=0<0,127,1>Track Slot
|
|
17
19
|
|
|
18
20
|
@init
|
|
19
21
|
// K-weighting filter state (two biquad stages per channel)
|
|
@@ -91,6 +93,8 @@ slider1:reset_btn=0<0,1,1{Idle,Reset}>Reset Measurement
|
|
|
91
93
|
);
|
|
92
94
|
|
|
93
95
|
function reset_measurement() (
|
|
96
|
+
local(base);
|
|
97
|
+
base = floor(slider2 + 0.5) * 8;
|
|
94
98
|
buf_pos = 0;
|
|
95
99
|
momentary_sum_l = 0;
|
|
96
100
|
momentary_sum_r = 0;
|
|
@@ -108,12 +112,12 @@ slider1:reset_btn=0<0,1,1{Idle,Reset}>Reset Measurement
|
|
|
108
112
|
// Clear ring buffers
|
|
109
113
|
memset(buf_l, 0, buf_size);
|
|
110
114
|
memset(buf_r, 0, buf_size);
|
|
111
|
-
gmem[0] = -70;
|
|
112
|
-
gmem[1] = -70;
|
|
113
|
-
gmem[2] = -70;
|
|
114
|
-
gmem[3] = -150;
|
|
115
|
-
gmem[4] = -150;
|
|
116
|
-
gmem[5] = 0;
|
|
115
|
+
gmem[base+0] = -70;
|
|
116
|
+
gmem[base+1] = -70;
|
|
117
|
+
gmem[base+2] = -70;
|
|
118
|
+
gmem[base+3] = -150;
|
|
119
|
+
gmem[base+4] = -150;
|
|
120
|
+
gmem[base+5] = 0;
|
|
117
121
|
);
|
|
118
122
|
|
|
119
123
|
@slider
|
|
@@ -240,7 +244,9 @@ slider1:reset_btn=0<0,1,1{Idle,Reset}>Reset Measurement
|
|
|
240
244
|
// --- Update gmem every 4096 samples (approx 10x/second at 44.1kHz) ---
|
|
241
245
|
(sample_count % 4096) == 0 ? (
|
|
242
246
|
local(shortterm_mean, momentary_mean, integrated_lufs, shortterm_lufs, momentary_lufs);
|
|
243
|
-
local(mom_samples);
|
|
247
|
+
local(mom_samples, base);
|
|
248
|
+
|
|
249
|
+
base = floor(slider2 + 0.5) * 8;
|
|
244
250
|
|
|
245
251
|
mom_samples = floor(srate * 0.4);
|
|
246
252
|
mom_samples < 1 ? mom_samples = 1;
|
|
@@ -279,23 +285,23 @@ slider1:reset_btn=0<0,1,1{Idle,Reset}>Reset Measurement
|
|
|
279
285
|
integrated_lufs = -70;
|
|
280
286
|
);
|
|
281
287
|
|
|
282
|
-
gmem[0] = integrated_lufs;
|
|
283
|
-
gmem[1] = shortterm_lufs;
|
|
284
|
-
gmem[2] = momentary_lufs;
|
|
288
|
+
gmem[base+0] = integrated_lufs;
|
|
289
|
+
gmem[base+1] = shortterm_lufs;
|
|
290
|
+
gmem[base+2] = momentary_lufs;
|
|
285
291
|
|
|
286
292
|
true_peak_l > 0.000001 ? (
|
|
287
|
-
gmem[3] = 20 * log10(true_peak_l);
|
|
293
|
+
gmem[base+3] = 20 * log10(true_peak_l);
|
|
288
294
|
) : (
|
|
289
|
-
gmem[3] = -150;
|
|
295
|
+
gmem[base+3] = -150;
|
|
290
296
|
);
|
|
291
297
|
|
|
292
298
|
true_peak_r > 0.000001 ? (
|
|
293
|
-
gmem[4] = 20 * log10(true_peak_r);
|
|
299
|
+
gmem[base+4] = 20 * log10(true_peak_r);
|
|
294
300
|
) : (
|
|
295
|
-
gmem[4] = -150;
|
|
301
|
+
gmem[base+4] = -150;
|
|
296
302
|
);
|
|
297
303
|
|
|
298
|
-
gmem[5] = sample_count / srate;
|
|
304
|
+
gmem[base+5] = sample_count / srate;
|
|
299
305
|
);
|
|
300
306
|
|
|
301
307
|
// Pass audio through unmodified
|