@mthines/reaper-mcp 0.10.0 → 0.10.1-beta.13.1
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/main.js +275 -1
- package/package.json +1 -1
- package/reaper/mcp_bridge.lua +190 -0
package/main.js
CHANGED
|
@@ -1703,6 +1703,270 @@ function registerEnvelopeTools(server) {
|
|
|
1703
1703
|
);
|
|
1704
1704
|
}
|
|
1705
1705
|
|
|
1706
|
+
// apps/reaper-mcp-server/src/tools/batch.ts
|
|
1707
|
+
import { z as z15 } from "zod/v4";
|
|
1708
|
+
function registerBatchTools(server) {
|
|
1709
|
+
server.tool(
|
|
1710
|
+
"set_multiple_track_properties",
|
|
1711
|
+
"Batch set properties (volume, pan, mute, solo, recordArm, phase, input) on multiple tracks in a single call. Much more efficient than calling set_track_property repeatedly. Pass an array of track update objects, each with trackIndex and any properties to change.",
|
|
1712
|
+
{
|
|
1713
|
+
tracks: z15.array(z15.object({
|
|
1714
|
+
trackIndex: z15.coerce.number().int().min(0).describe("0-based track index"),
|
|
1715
|
+
volume: z15.coerce.number().optional().describe("Volume in dB (0 = unity gain)"),
|
|
1716
|
+
pan: z15.coerce.number().min(-1).max(1).optional().describe("Pan position -1.0 (left) to 1.0 (right)"),
|
|
1717
|
+
mute: z15.coerce.number().int().min(0).max(1).optional().describe("Mute state (0=unmuted, 1=muted)"),
|
|
1718
|
+
solo: z15.coerce.number().int().min(0).max(1).optional().describe("Solo state (0=unsolo, 1=solo)"),
|
|
1719
|
+
recordArm: z15.coerce.number().int().min(0).max(1).optional().describe("Record arm state (0=unarmed, 1=armed)"),
|
|
1720
|
+
phase: z15.coerce.number().int().min(0).max(1).optional().describe("Phase inversion (0=normal, 1=inverted)"),
|
|
1721
|
+
input: z15.coerce.number().int().optional().describe("REAPER input index (-1 = no input)")
|
|
1722
|
+
})).describe("Array of track property updates to apply")
|
|
1723
|
+
},
|
|
1724
|
+
async ({ tracks }) => {
|
|
1725
|
+
const res = await sendCommand("set_multiple_track_properties", { tracks });
|
|
1726
|
+
if (!res.success) {
|
|
1727
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
|
|
1728
|
+
}
|
|
1729
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
1730
|
+
}
|
|
1731
|
+
);
|
|
1732
|
+
server.tool(
|
|
1733
|
+
"setup_fx_chain",
|
|
1734
|
+
"Add multiple FX plugins to a track with optional initial parameter values in a single call. Returns the FX chain indices of added plugins. More efficient than calling add_fx and set_fx_parameter repeatedly.",
|
|
1735
|
+
{
|
|
1736
|
+
trackIndex: z15.coerce.number().int().min(0).describe("0-based track index"),
|
|
1737
|
+
plugins: z15.array(z15.object({
|
|
1738
|
+
fxName: z15.string().describe('FX plugin name (partial match supported, e.g. "ReaEQ", "VST: Pro-Q 3")'),
|
|
1739
|
+
enabled: z15.coerce.number().int().min(0).max(1).optional().describe("Initial enabled state (1=enabled default, 0=bypassed)"),
|
|
1740
|
+
parameters: z15.array(z15.object({
|
|
1741
|
+
index: z15.coerce.number().int().min(0).describe("Parameter index"),
|
|
1742
|
+
value: z15.coerce.number().min(0).max(1).describe("Normalized parameter value 0.0\u20131.0")
|
|
1743
|
+
})).optional().describe("Initial parameter values to set after adding the plugin")
|
|
1744
|
+
})).describe("Array of FX plugins to add, in order")
|
|
1745
|
+
},
|
|
1746
|
+
async ({ trackIndex, plugins }) => {
|
|
1747
|
+
const res = await sendCommand("setup_fx_chain", { trackIndex, plugins });
|
|
1748
|
+
if (!res.success) {
|
|
1749
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
|
|
1750
|
+
}
|
|
1751
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
1752
|
+
}
|
|
1753
|
+
);
|
|
1754
|
+
server.tool(
|
|
1755
|
+
"set_multiple_fx_parameters",
|
|
1756
|
+
"Batch set parameters across multiple FX plugins, potentially on different tracks, in a single call. Much faster than repeated set_fx_parameter calls. Pass an array of update objects with trackIndex, fxIndex, paramIndex, and value.",
|
|
1757
|
+
{
|
|
1758
|
+
updates: z15.array(z15.object({
|
|
1759
|
+
trackIndex: z15.coerce.number().int().min(0).describe("0-based track index"),
|
|
1760
|
+
fxIndex: z15.coerce.number().int().min(0).describe("0-based FX index in the chain"),
|
|
1761
|
+
paramIndex: z15.coerce.number().int().min(0).describe("0-based parameter index"),
|
|
1762
|
+
value: z15.coerce.number().min(0).max(1).describe("Normalized parameter value 0.0\u20131.0")
|
|
1763
|
+
})).describe("Array of FX parameter updates to apply")
|
|
1764
|
+
},
|
|
1765
|
+
async ({ updates }) => {
|
|
1766
|
+
const res = await sendCommand("set_multiple_fx_parameters", { updates });
|
|
1767
|
+
if (!res.success) {
|
|
1768
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
|
|
1769
|
+
}
|
|
1770
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
1771
|
+
}
|
|
1772
|
+
);
|
|
1773
|
+
}
|
|
1774
|
+
|
|
1775
|
+
// apps/reaper-mcp-server/src/tools/categories.ts
|
|
1776
|
+
import { z as z16 } from "zod/v4";
|
|
1777
|
+
var TOOL_CATEGORIES = {
|
|
1778
|
+
project: {
|
|
1779
|
+
name: "project",
|
|
1780
|
+
description: "Project-level information: name, tempo, time signature, sample rate, transport state",
|
|
1781
|
+
tools: ["get_project_info"]
|
|
1782
|
+
},
|
|
1783
|
+
tracks: {
|
|
1784
|
+
name: "tracks",
|
|
1785
|
+
description: "Track management: list, inspect, and set properties (volume, pan, mute, solo, record arm, phase, input). Includes batch set for multiple tracks at once.",
|
|
1786
|
+
tools: ["list_tracks", "get_track_properties", "set_track_property", "set_multiple_track_properties"]
|
|
1787
|
+
},
|
|
1788
|
+
fx: {
|
|
1789
|
+
name: "fx",
|
|
1790
|
+
description: "FX chain management: add, remove, inspect, and set parameters. Includes batch setup of an entire FX chain and batch parameter updates across multiple plugins.",
|
|
1791
|
+
tools: ["add_fx", "remove_fx", "get_fx_parameters", "set_fx_parameter", "set_fx_enabled", "set_fx_offline", "setup_fx_chain", "set_multiple_fx_parameters"]
|
|
1792
|
+
},
|
|
1793
|
+
transport: {
|
|
1794
|
+
name: "transport",
|
|
1795
|
+
description: "Transport control: play, stop, record, get transport state, set cursor position",
|
|
1796
|
+
tools: ["play", "stop", "record", "get_transport_state", "set_cursor_position"]
|
|
1797
|
+
},
|
|
1798
|
+
midi: {
|
|
1799
|
+
name: "midi",
|
|
1800
|
+
description: "MIDI item editing: create items, insert/edit/delete notes and CC events, batch operations, analysis. Use analyze_midi first for large items; paginate with get_midi_notes offset/limit.",
|
|
1801
|
+
tools: [
|
|
1802
|
+
"create_midi_item",
|
|
1803
|
+
"list_midi_items",
|
|
1804
|
+
"get_midi_notes",
|
|
1805
|
+
"analyze_midi",
|
|
1806
|
+
"insert_midi_note",
|
|
1807
|
+
"insert_midi_notes",
|
|
1808
|
+
"edit_midi_note",
|
|
1809
|
+
"edit_midi_notes",
|
|
1810
|
+
"delete_midi_note",
|
|
1811
|
+
"get_midi_cc",
|
|
1812
|
+
"insert_midi_cc",
|
|
1813
|
+
"delete_midi_cc",
|
|
1814
|
+
"get_midi_item_properties",
|
|
1815
|
+
"set_midi_item_properties"
|
|
1816
|
+
]
|
|
1817
|
+
},
|
|
1818
|
+
media: {
|
|
1819
|
+
name: "media",
|
|
1820
|
+
description: "Media item editing: list, inspect, set properties, split, delete, move, trim, stretch markers. Includes batch set for multiple items at once.",
|
|
1821
|
+
tools: [
|
|
1822
|
+
"list_media_items",
|
|
1823
|
+
"get_media_item_properties",
|
|
1824
|
+
"set_media_item_properties",
|
|
1825
|
+
"set_media_items_properties",
|
|
1826
|
+
"split_media_item",
|
|
1827
|
+
"delete_media_item",
|
|
1828
|
+
"move_media_item",
|
|
1829
|
+
"trim_media_item",
|
|
1830
|
+
"add_stretch_marker",
|
|
1831
|
+
"get_stretch_markers",
|
|
1832
|
+
"delete_stretch_marker"
|
|
1833
|
+
]
|
|
1834
|
+
},
|
|
1835
|
+
selection: {
|
|
1836
|
+
name: "selection",
|
|
1837
|
+
description: "Selection and navigation: get selected tracks, get/set time selection range",
|
|
1838
|
+
tools: ["get_selected_tracks", "get_time_selection", "set_time_selection"]
|
|
1839
|
+
},
|
|
1840
|
+
markers: {
|
|
1841
|
+
name: "markers",
|
|
1842
|
+
description: "Markers and regions: list, add, delete markers and regions with optional names and colors",
|
|
1843
|
+
tools: ["list_markers", "list_regions", "add_marker", "add_region", "delete_marker", "delete_region"]
|
|
1844
|
+
},
|
|
1845
|
+
tempo: {
|
|
1846
|
+
name: "tempo",
|
|
1847
|
+
description: "Tempo map: get all tempo and time signature changes with positions, BPM, and linear/step flags",
|
|
1848
|
+
tools: ["get_tempo_map"]
|
|
1849
|
+
},
|
|
1850
|
+
envelopes: {
|
|
1851
|
+
name: "envelopes",
|
|
1852
|
+
description: "Automation envelopes: create, inspect, insert/delete points, clear, batch insert, set properties. Supports volume, pan, mute, width, trim volume, and FX parameter envelopes.",
|
|
1853
|
+
tools: [
|
|
1854
|
+
"create_track_envelope",
|
|
1855
|
+
"get_track_envelopes",
|
|
1856
|
+
"get_envelope_points",
|
|
1857
|
+
"insert_envelope_point",
|
|
1858
|
+
"insert_envelope_points",
|
|
1859
|
+
"delete_envelope_point",
|
|
1860
|
+
"clear_envelope",
|
|
1861
|
+
"remove_envelope_points",
|
|
1862
|
+
"set_envelope_properties"
|
|
1863
|
+
]
|
|
1864
|
+
},
|
|
1865
|
+
analysis: {
|
|
1866
|
+
name: "analysis",
|
|
1867
|
+
description: "Audio metering and analysis: peak/RMS meters, FFT spectrum, LUFS loudness, stereo correlation, crest factor",
|
|
1868
|
+
tools: ["read_track_meters", "read_track_spectrum", "read_track_lufs", "read_track_correlation", "read_track_crest"]
|
|
1869
|
+
},
|
|
1870
|
+
discovery: {
|
|
1871
|
+
name: "discovery",
|
|
1872
|
+
description: "FX discovery and presets: list all installed plugins, fuzzy search by name, list and load presets",
|
|
1873
|
+
tools: ["list_available_fx", "search_fx", "get_fx_preset_list", "set_fx_preset"]
|
|
1874
|
+
},
|
|
1875
|
+
snapshots: {
|
|
1876
|
+
name: "snapshots",
|
|
1877
|
+
description: "Mixer state snapshots: save, restore, and list named snapshots of volumes, pans, FX, and mutes",
|
|
1878
|
+
tools: ["snapshot_save", "snapshot_restore", "snapshot_list"]
|
|
1879
|
+
},
|
|
1880
|
+
routing: {
|
|
1881
|
+
name: "routing",
|
|
1882
|
+
description: "Track routing: inspect sends, receives, parent/folder relationships for a track",
|
|
1883
|
+
tools: ["get_track_routing"]
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
function registerCategoryTools(server) {
|
|
1887
|
+
server.tool(
|
|
1888
|
+
"list_tool_categories",
|
|
1889
|
+
"List all available tool categories with descriptions and tool names. Use this to discover what tools are available without loading all 72+ tool schemas. Categories: project, tracks, fx, transport, midi, media, selection, markers, tempo, envelopes, analysis, discovery, snapshots, routing.",
|
|
1890
|
+
{},
|
|
1891
|
+
async () => {
|
|
1892
|
+
const categories = Object.values(TOOL_CATEGORIES).map((cat) => ({
|
|
1893
|
+
name: cat.name,
|
|
1894
|
+
description: cat.description,
|
|
1895
|
+
toolCount: cat.tools.length,
|
|
1896
|
+
tools: cat.tools
|
|
1897
|
+
}));
|
|
1898
|
+
return {
|
|
1899
|
+
content: [{
|
|
1900
|
+
type: "text",
|
|
1901
|
+
text: JSON.stringify({ categories, totalTools: categories.reduce((sum, c) => sum + c.toolCount, 0) }, null, 2)
|
|
1902
|
+
}]
|
|
1903
|
+
};
|
|
1904
|
+
}
|
|
1905
|
+
);
|
|
1906
|
+
server.tool(
|
|
1907
|
+
"enable_tool_category",
|
|
1908
|
+
"Signal that you intend to use tools from a specific category. All tools are already registered and available \u2014 this call returns the full list of tools in the category so you know exactly which tool names to use. Use list_tool_categories first to see all available categories.",
|
|
1909
|
+
{
|
|
1910
|
+
category: z16.string().describe('Category name (e.g. "tracks", "fx", "midi", "media", "transport", "markers", "envelopes", "analysis", "discovery")')
|
|
1911
|
+
},
|
|
1912
|
+
async ({ category }) => {
|
|
1913
|
+
const cat = TOOL_CATEGORIES[category];
|
|
1914
|
+
if (!cat) {
|
|
1915
|
+
const available = Object.keys(TOOL_CATEGORIES).join(", ");
|
|
1916
|
+
return {
|
|
1917
|
+
content: [{
|
|
1918
|
+
type: "text",
|
|
1919
|
+
text: `Error: Unknown category "${category}". Available categories: ${available}`
|
|
1920
|
+
}],
|
|
1921
|
+
isError: true
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
return {
|
|
1925
|
+
content: [{
|
|
1926
|
+
type: "text",
|
|
1927
|
+
text: JSON.stringify({
|
|
1928
|
+
category: cat.name,
|
|
1929
|
+
description: cat.description,
|
|
1930
|
+
toolCount: cat.tools.length,
|
|
1931
|
+
tools: cat.tools,
|
|
1932
|
+
status: "ready"
|
|
1933
|
+
}, null, 2)
|
|
1934
|
+
}]
|
|
1935
|
+
};
|
|
1936
|
+
}
|
|
1937
|
+
);
|
|
1938
|
+
server.tool(
|
|
1939
|
+
"disable_tool_category",
|
|
1940
|
+
"Signal that you are done using tools from a specific category. This is a semantic hint to help manage your context budget \u2014 the tools remain registered but you can use this to indicate you no longer need them in the current workflow.",
|
|
1941
|
+
{
|
|
1942
|
+
category: z16.string().describe('Category name (e.g. "tracks", "fx", "midi", "media", "transport", "markers", "envelopes", "analysis", "discovery")')
|
|
1943
|
+
},
|
|
1944
|
+
async ({ category }) => {
|
|
1945
|
+
const cat = TOOL_CATEGORIES[category];
|
|
1946
|
+
if (!cat) {
|
|
1947
|
+
const available = Object.keys(TOOL_CATEGORIES).join(", ");
|
|
1948
|
+
return {
|
|
1949
|
+
content: [{
|
|
1950
|
+
type: "text",
|
|
1951
|
+
text: `Error: Unknown category "${category}". Available categories: ${available}`
|
|
1952
|
+
}],
|
|
1953
|
+
isError: true
|
|
1954
|
+
};
|
|
1955
|
+
}
|
|
1956
|
+
return {
|
|
1957
|
+
content: [{
|
|
1958
|
+
type: "text",
|
|
1959
|
+
text: JSON.stringify({
|
|
1960
|
+
category: cat.name,
|
|
1961
|
+
status: "disabled",
|
|
1962
|
+
note: "Tools remain available if needed. Use enable_tool_category to re-activate."
|
|
1963
|
+
}, null, 2)
|
|
1964
|
+
}]
|
|
1965
|
+
};
|
|
1966
|
+
}
|
|
1967
|
+
);
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1706
1970
|
// apps/reaper-mcp-server/src/server.ts
|
|
1707
1971
|
function instrumentToolHandlers(server) {
|
|
1708
1972
|
const originalTool = server.tool.bind(server);
|
|
@@ -1760,6 +2024,8 @@ function createServer() {
|
|
|
1760
2024
|
registerMarkerTools(server);
|
|
1761
2025
|
registerTempoTools(server);
|
|
1762
2026
|
registerEnvelopeTools(server);
|
|
2027
|
+
registerBatchTools(server);
|
|
2028
|
+
registerCategoryTools(server);
|
|
1763
2029
|
return server;
|
|
1764
2030
|
}
|
|
1765
2031
|
|
|
@@ -1919,7 +2185,15 @@ var MCP_TOOL_NAMES = [
|
|
|
1919
2185
|
"create_track_envelope",
|
|
1920
2186
|
"set_envelope_properties",
|
|
1921
2187
|
"clear_envelope",
|
|
1922
|
-
"remove_envelope_points"
|
|
2188
|
+
"remove_envelope_points",
|
|
2189
|
+
// composite batch tools
|
|
2190
|
+
"set_multiple_track_properties",
|
|
2191
|
+
"setup_fx_chain",
|
|
2192
|
+
"set_multiple_fx_parameters",
|
|
2193
|
+
// progressive discovery
|
|
2194
|
+
"list_tool_categories",
|
|
2195
|
+
"enable_tool_category",
|
|
2196
|
+
"disable_tool_category"
|
|
1923
2197
|
];
|
|
1924
2198
|
function ensureClaudeSettings(settingsPath) {
|
|
1925
2199
|
const allowList = MCP_TOOL_NAMES.map((t) => `mcp__reaper__${t}`);
|
package/package.json
CHANGED
package/reaper/mcp_bridge.lua
CHANGED
|
@@ -2201,6 +2201,196 @@ function handlers.set_fx_offline(params)
|
|
|
2201
2201
|
return { trackIndex = params.trackIndex, fxIndex = params.fxIndex, offline = params.offline == 1 }
|
|
2202
2202
|
end
|
|
2203
2203
|
|
|
2204
|
+
-- =============================================================================
|
|
2205
|
+
-- Composite batch tools
|
|
2206
|
+
-- =============================================================================
|
|
2207
|
+
|
|
2208
|
+
function handlers.set_multiple_track_properties(params)
|
|
2209
|
+
local tracks_input = params.tracks
|
|
2210
|
+
if not tracks_input then return nil, "tracks array required" end
|
|
2211
|
+
|
|
2212
|
+
local tracks = nil
|
|
2213
|
+
if type(tracks_input) == "string" then
|
|
2214
|
+
tracks = json_decode(tracks_input)
|
|
2215
|
+
elseif type(tracks_input) == "table" then
|
|
2216
|
+
tracks = tracks_input
|
|
2217
|
+
end
|
|
2218
|
+
if not tracks then return nil, "Failed to parse tracks array" end
|
|
2219
|
+
|
|
2220
|
+
local updated = 0
|
|
2221
|
+
local errors = {}
|
|
2222
|
+
|
|
2223
|
+
reaper.Undo_BeginBlock()
|
|
2224
|
+
|
|
2225
|
+
for _, t in ipairs(tracks) do
|
|
2226
|
+
local track_idx = t.trackIndex
|
|
2227
|
+
if track_idx == nil then
|
|
2228
|
+
errors[#errors + 1] = "track entry missing trackIndex"
|
|
2229
|
+
else
|
|
2230
|
+
local track = reaper.GetTrack(0, track_idx)
|
|
2231
|
+
if not track then
|
|
2232
|
+
errors[#errors + 1] = "Track " .. track_idx .. " not found"
|
|
2233
|
+
else
|
|
2234
|
+
if t.volume ~= nil then
|
|
2235
|
+
reaper.SetMediaTrackInfo_Value(track, "D_VOL", from_db(t.volume))
|
|
2236
|
+
end
|
|
2237
|
+
if t.pan ~= nil then
|
|
2238
|
+
reaper.SetMediaTrackInfo_Value(track, "D_PAN", t.pan)
|
|
2239
|
+
end
|
|
2240
|
+
if t.mute ~= nil then
|
|
2241
|
+
reaper.SetMediaTrackInfo_Value(track, "B_MUTE", t.mute)
|
|
2242
|
+
end
|
|
2243
|
+
if t.solo ~= nil then
|
|
2244
|
+
reaper.SetMediaTrackInfo_Value(track, "I_SOLO", t.solo)
|
|
2245
|
+
end
|
|
2246
|
+
if t.recordArm ~= nil then
|
|
2247
|
+
reaper.SetMediaTrackInfo_Value(track, "I_RECARM", t.recordArm)
|
|
2248
|
+
end
|
|
2249
|
+
if t.phase ~= nil then
|
|
2250
|
+
reaper.SetMediaTrackInfo_Value(track, "B_PHASE", t.phase)
|
|
2251
|
+
end
|
|
2252
|
+
if t.input ~= nil then
|
|
2253
|
+
reaper.SetMediaTrackInfo_Value(track, "I_RECINPUT", t.input)
|
|
2254
|
+
end
|
|
2255
|
+
updated = updated + 1
|
|
2256
|
+
end
|
|
2257
|
+
end
|
|
2258
|
+
end
|
|
2259
|
+
|
|
2260
|
+
reaper.Undo_EndBlock("MCP: Batch set " .. updated .. " track properties", -1)
|
|
2261
|
+
|
|
2262
|
+
local result = { success = true, updated = updated, total = #tracks }
|
|
2263
|
+
if #errors > 0 then result.errors = errors end
|
|
2264
|
+
return result
|
|
2265
|
+
end
|
|
2266
|
+
|
|
2267
|
+
function handlers.setup_fx_chain(params)
|
|
2268
|
+
local track_idx = params.trackIndex
|
|
2269
|
+
if track_idx == nil then return nil, "trackIndex required" end
|
|
2270
|
+
|
|
2271
|
+
local plugins_input = params.plugins
|
|
2272
|
+
if not plugins_input then return nil, "plugins array required" end
|
|
2273
|
+
|
|
2274
|
+
local plugins = nil
|
|
2275
|
+
if type(plugins_input) == "string" then
|
|
2276
|
+
plugins = json_decode(plugins_input)
|
|
2277
|
+
elseif type(plugins_input) == "table" then
|
|
2278
|
+
plugins = plugins_input
|
|
2279
|
+
end
|
|
2280
|
+
if not plugins then return nil, "Failed to parse plugins array" end
|
|
2281
|
+
|
|
2282
|
+
local track = reaper.GetTrack(0, track_idx)
|
|
2283
|
+
if not track then return nil, "Track " .. track_idx .. " not found" end
|
|
2284
|
+
|
|
2285
|
+
local added = 0
|
|
2286
|
+
local errors = {}
|
|
2287
|
+
local added_plugins = {}
|
|
2288
|
+
|
|
2289
|
+
reaper.Undo_BeginBlock()
|
|
2290
|
+
|
|
2291
|
+
for i, plugin in ipairs(plugins) do
|
|
2292
|
+
local fx_name = plugin.fxName
|
|
2293
|
+
if not fx_name or fx_name == "" then
|
|
2294
|
+
errors[#errors + 1] = "plugin[" .. (i - 1) .. "] missing fxName"
|
|
2295
|
+
else
|
|
2296
|
+
local fx_idx = reaper.TrackFX_AddByName(track, fx_name, false, -1)
|
|
2297
|
+
if fx_idx < 0 then
|
|
2298
|
+
errors[#errors + 1] = "FX not found: " .. fx_name
|
|
2299
|
+
else
|
|
2300
|
+
-- Set enabled state if specified (default is enabled)
|
|
2301
|
+
if plugin.enabled ~= nil then
|
|
2302
|
+
reaper.TrackFX_SetEnabled(track, fx_idx, plugin.enabled == 1)
|
|
2303
|
+
end
|
|
2304
|
+
|
|
2305
|
+
-- Set initial parameters if provided
|
|
2306
|
+
local param_errors = {}
|
|
2307
|
+
if plugin.parameters and type(plugin.parameters) == "table" then
|
|
2308
|
+
for _, p in ipairs(plugin.parameters) do
|
|
2309
|
+
if p.index ~= nil and p.value ~= nil then
|
|
2310
|
+
local ok = reaper.TrackFX_SetParam(track, fx_idx, p.index, p.value)
|
|
2311
|
+
if not ok then
|
|
2312
|
+
param_errors[#param_errors + 1] = "Failed to set param " .. p.index
|
|
2313
|
+
end
|
|
2314
|
+
end
|
|
2315
|
+
end
|
|
2316
|
+
end
|
|
2317
|
+
|
|
2318
|
+
local _, actual_name = reaper.TrackFX_GetFXName(track, fx_idx)
|
|
2319
|
+
added_plugins[#added_plugins + 1] = {
|
|
2320
|
+
pluginIndex = i - 1,
|
|
2321
|
+
fxName = actual_name or fx_name,
|
|
2322
|
+
fxIndex = fx_idx,
|
|
2323
|
+
}
|
|
2324
|
+
if #param_errors > 0 then
|
|
2325
|
+
for _, pe in ipairs(param_errors) do
|
|
2326
|
+
errors[#errors + 1] = fx_name .. ": " .. pe
|
|
2327
|
+
end
|
|
2328
|
+
end
|
|
2329
|
+
added = added + 1
|
|
2330
|
+
end
|
|
2331
|
+
end
|
|
2332
|
+
end
|
|
2333
|
+
|
|
2334
|
+
reaper.Undo_EndBlock("MCP: Setup FX chain (" .. added .. " plugins) on track " .. track_idx, -1)
|
|
2335
|
+
|
|
2336
|
+
local result = {
|
|
2337
|
+
success = true,
|
|
2338
|
+
trackIndex = track_idx,
|
|
2339
|
+
added = added,
|
|
2340
|
+
total = #plugins,
|
|
2341
|
+
plugins = added_plugins,
|
|
2342
|
+
}
|
|
2343
|
+
if #errors > 0 then result.errors = errors end
|
|
2344
|
+
return result
|
|
2345
|
+
end
|
|
2346
|
+
|
|
2347
|
+
function handlers.set_multiple_fx_parameters(params)
|
|
2348
|
+
local updates_input = params.updates
|
|
2349
|
+
if not updates_input then return nil, "updates array required" end
|
|
2350
|
+
|
|
2351
|
+
local updates = nil
|
|
2352
|
+
if type(updates_input) == "string" then
|
|
2353
|
+
updates = json_decode(updates_input)
|
|
2354
|
+
elseif type(updates_input) == "table" then
|
|
2355
|
+
updates = updates_input
|
|
2356
|
+
end
|
|
2357
|
+
if not updates then return nil, "Failed to parse updates array" end
|
|
2358
|
+
|
|
2359
|
+
local updated = 0
|
|
2360
|
+
local errors = {}
|
|
2361
|
+
|
|
2362
|
+
reaper.Undo_BeginBlock()
|
|
2363
|
+
|
|
2364
|
+
for _, u in ipairs(updates) do
|
|
2365
|
+
local track_idx = u.trackIndex
|
|
2366
|
+
local fx_idx = u.fxIndex
|
|
2367
|
+
local param_idx = u.paramIndex
|
|
2368
|
+
local value = u.value
|
|
2369
|
+
|
|
2370
|
+
if track_idx == nil or fx_idx == nil or param_idx == nil or value == nil then
|
|
2371
|
+
errors[#errors + 1] = "update entry missing trackIndex, fxIndex, paramIndex, or value"
|
|
2372
|
+
else
|
|
2373
|
+
local track = reaper.GetTrack(0, track_idx)
|
|
2374
|
+
if not track then
|
|
2375
|
+
errors[#errors + 1] = "Track " .. track_idx .. " not found"
|
|
2376
|
+
else
|
|
2377
|
+
local ok = reaper.TrackFX_SetParam(track, fx_idx, param_idx, value)
|
|
2378
|
+
if not ok then
|
|
2379
|
+
errors[#errors + 1] = "Failed to set track " .. track_idx .. " fx " .. fx_idx .. " param " .. param_idx
|
|
2380
|
+
else
|
|
2381
|
+
updated = updated + 1
|
|
2382
|
+
end
|
|
2383
|
+
end
|
|
2384
|
+
end
|
|
2385
|
+
end
|
|
2386
|
+
|
|
2387
|
+
reaper.Undo_EndBlock("MCP: Batch set " .. updated .. " FX parameters", -1)
|
|
2388
|
+
|
|
2389
|
+
local result = { success = true, updated = updated, total = #updates }
|
|
2390
|
+
if #errors > 0 then result.errors = errors end
|
|
2391
|
+
return result
|
|
2392
|
+
end
|
|
2393
|
+
|
|
2204
2394
|
-- =============================================================================
|
|
2205
2395
|
-- Selection & Navigation
|
|
2206
2396
|
-- =============================================================================
|