Setting REAPER Track Colors with Python/Claude/ReaperMCP
One of the first things I set up with the REAPER MCP integration was a coherent color scheme across all 9 tracks. Here’s the full procedure — what works, what doesn’t, and how to keep the TCP and mixer in sync.
Prerequisites
- REAPER is open
- reapy server is running inside REAPER (port 2306 — starts automatically if you have the reapy REAPER extension installed)
- Python environment:
~/claude/reaper-mcpwithuv
Run all snippets from that directory:
cd ~/claude/reaper-mcp
uv run python3 -c "..."
Check track order first
Colors are applied by index (0-based). Always verify the order before running the color script — tracks may have been reordered since the session was created:
import reapy
reapy.connect()
p = reapy.Project()
for i, t in enumerate(p.tracks):
print(i, t.name.strip())
Confirm the output matches the order you expect before proceeding.
Before and after
Without colors, all mixer channels look identical. At a glance you can’t tell which strip is which, especially once you’re deep in a session.
Before:

After:

Each track now has a color band at the top of its mixer strip and a colored number tab at the bottom. The TCP (track list on the left) gets the same color as a full background fill.
The sync problem
REAPER has two views that both display track colors:
- TCP — the track control panel (left side of the arrange view)
- MCP — the mixer control panel
They’re driven by the same underlying data (I_CUSTOMCOLOR) but don’t always
redraw together when colors are set via the API. Calling update_arrange() only
refreshes the arrange view. To get both in sync you need two extra calls:
rpr.TrackList_AdjustWindows(True) # redraws TCP
rpr.ThemeLayout_RefreshAll() # redraws MCP / mixer
Without ThemeLayout_RefreshAll(), the mixer strips stay grey even though the
color is stored correctly.
The wrong encoding
The first attempt used manual bit-packing:
# WRONG on macOS — swaps R and B channels
t.set_info_value('I_CUSTOMCOLOR', r | (g << 8) | (b << 16) | 0x1000000)
On macOS, I_CUSTOMCOLOR expects (r << 16) | (g << 8) | b | 0x1000000.
Getting this wrong produces wrong hues — teal becomes yellow, pink becomes purple.
The correct approach
Use SetTrackColor with ColorToNative to handle the platform encoding, then
force both panels to redraw:
import reapy
reapy.connect()
p = reapy.Project()
rpr = reapy.reascript_api
colors = [
( 76, 175, 80), # Guitar — green
(255, 87, 34), # Force — deep orange
(255, 152, 0), # MPC — amber
( 33, 150, 243), # FL Studio — blue
( 0, 188, 212), # DDJ 400 — teal
( 0, 0, 0), # Elektron — black
(233, 30, 99), # SP404 — pink
( 80, 100, 230), # Hydrasynth — indigo
(150, 150, 150), # Sync — grey
]
for idx, (r, g, b) in enumerate(colors):
rpr.SetTrackColor(p.tracks[idx].id, rpr.ColorToNative(r, g, b))
rpr.TrackList_AdjustWindows(True)
rpr.ThemeLayout_RefreshAll()
ColorToNative(r, g, b) does the platform-specific conversion. SetTrackColor
sets both the custom color and enables the custom color flag in one call — no
need to manually OR in 0x1000000.
Color grouping logic
Colors are grouped by instrument family so related tracks cluster visually:
| Track | Instrument | Family | Color |
|---|---|---|---|
| Guitar | Live → Axe-FX | Live | Green |
| Force | Akai Force | Groove box | Deep orange |
| MPC | Akai MPC | Sampler | Amber |
| FL Studio | DAW via ADAT | Software | Blue |
| DDJ 400 | DJ controller | DJ | Teal |
| Elektron | Digitakt / Syntakt | Drum machine | Black |
| SP404 | Roland SP-404 | Sampler | Pink |
| Hydrasynth | ASM Hydrasynth | Synth | Indigo |
| Sync | Click / MIDI clock | Utility | Grey |
Samplers stay warm (orange → amber → pink), synths stay cool (indigo, black), and the utility track is neutral so it never draws the eye accidentally.