@mthines/reaper-mcp 0.6.0-beta.3.1 → 0.6.0-beta.3.2
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/package.json +1 -1
- package/reaper/mcp_bridge.lua +62 -47
package/package.json
CHANGED
package/reaper/mcp_bridge.lua
CHANGED
|
@@ -58,6 +58,30 @@ local function json_decode(str)
|
|
|
58
58
|
return obj
|
|
59
59
|
end
|
|
60
60
|
|
|
61
|
+
-- Fallback JSON array-of-objects parser for when CF_Json_Parse is unavailable.
|
|
62
|
+
-- Handles: [{"key":val,...}, {"key":val,...}, ...]
|
|
63
|
+
local function json_decode_array(str)
|
|
64
|
+
if reaper.CF_Json_Parse then
|
|
65
|
+
local ok, val = reaper.CF_Json_Parse(str)
|
|
66
|
+
if ok then return val end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
-- Fallback: extract each {...} from the array and parse individually
|
|
70
|
+
local arr = {}
|
|
71
|
+
for obj_str in str:gmatch("%b{}") do
|
|
72
|
+
local obj = {}
|
|
73
|
+
for k, v in obj_str:gmatch('"([^"]+)"%s*:%s*"([^"]*)"') do
|
|
74
|
+
obj[k] = v
|
|
75
|
+
end
|
|
76
|
+
for k, v in obj_str:gmatch('"([^"]+)"%s*:%s*(-?%d+%.?%d*)') do
|
|
77
|
+
if not obj[k] then obj[k] = tonumber(v) end
|
|
78
|
+
end
|
|
79
|
+
arr[#arr + 1] = obj
|
|
80
|
+
end
|
|
81
|
+
if #arr > 0 then return arr end
|
|
82
|
+
return nil
|
|
83
|
+
end
|
|
84
|
+
|
|
61
85
|
local function json_encode(obj)
|
|
62
86
|
-- Simple JSON encoder for our response objects
|
|
63
87
|
local parts = {}
|
|
@@ -1092,12 +1116,12 @@ function handlers.create_midi_item(params)
|
|
|
1092
1116
|
local track = reaper.GetTrack(0, track_idx)
|
|
1093
1117
|
if not track then return nil, "Track " .. track_idx .. " not found" end
|
|
1094
1118
|
|
|
1095
|
-
reaper.Undo_BeginBlock()
|
|
1096
1119
|
local item = reaper.CreateNewMIDIItemInProj(track, start_pos, end_pos)
|
|
1097
|
-
reaper.Undo_EndBlock("MCP: Create MIDI item", -1)
|
|
1098
|
-
|
|
1099
1120
|
if not item then return nil, "Failed to create MIDI item" end
|
|
1100
1121
|
|
|
1122
|
+
reaper.Undo_BeginBlock()
|
|
1123
|
+
reaper.Undo_EndBlock("MCP: Create MIDI item", -1)
|
|
1124
|
+
|
|
1101
1125
|
-- Find the index of the new item on the track
|
|
1102
1126
|
local item_count = reaper.CountTrackMediaItems(track)
|
|
1103
1127
|
local new_idx = -1
|
|
@@ -1213,17 +1237,10 @@ function handlers.insert_midi_notes(params)
|
|
|
1213
1237
|
local notes_str = params.notes
|
|
1214
1238
|
if not notes_str or notes_str == "" then return nil, "notes JSON string required" end
|
|
1215
1239
|
|
|
1216
|
-
-- Parse notes JSON array
|
|
1217
|
-
local
|
|
1218
|
-
if not
|
|
1219
|
-
|
|
1220
|
-
-- Handle both array-style and object-style parsed data
|
|
1221
|
-
local notes_list = {}
|
|
1222
|
-
if notes_data[1] then
|
|
1223
|
-
notes_list = notes_data
|
|
1224
|
-
else
|
|
1225
|
-
-- Try to extract from numbered keys (fallback parser)
|
|
1226
|
-
return nil, "Notes must be a JSON array. Ensure REAPER 7+ with CF_Json_Parse for array support."
|
|
1240
|
+
-- Parse notes JSON array (uses dedicated array parser with fallback)
|
|
1241
|
+
local notes_list = json_decode_array(notes_str)
|
|
1242
|
+
if not notes_list or #notes_list == 0 then
|
|
1243
|
+
return nil, "Failed to parse notes JSON array. Expected: [{\"pitch\":60,\"velocity\":100,\"startPosition\":0,\"duration\":1}, ...]"
|
|
1227
1244
|
end
|
|
1228
1245
|
|
|
1229
1246
|
reaper.Undo_BeginBlock()
|
|
@@ -1318,7 +1335,6 @@ function handlers.delete_midi_note(params)
|
|
|
1318
1335
|
|
|
1319
1336
|
reaper.Undo_BeginBlock()
|
|
1320
1337
|
reaper.MIDI_DeleteNote(take, note_idx)
|
|
1321
|
-
reaper.MIDI_Sort(take)
|
|
1322
1338
|
reaper.Undo_EndBlock("MCP: Delete MIDI note " .. note_idx, -1)
|
|
1323
1339
|
|
|
1324
1340
|
reaper.UpdateArrange()
|
|
@@ -1400,7 +1416,6 @@ function handlers.delete_midi_cc(params)
|
|
|
1400
1416
|
|
|
1401
1417
|
reaper.Undo_BeginBlock()
|
|
1402
1418
|
reaper.MIDI_DeleteCC(take, cc_idx)
|
|
1403
|
-
reaper.MIDI_Sort(take)
|
|
1404
1419
|
reaper.Undo_EndBlock("MCP: Delete MIDI CC " .. cc_idx, -1)
|
|
1405
1420
|
|
|
1406
1421
|
reaper.UpdateArrange()
|
|
@@ -1514,7 +1529,8 @@ function handlers.get_media_item_properties(params)
|
|
|
1514
1529
|
start_offset = reaper.GetMediaItemTakeInfo_Value(take, "D_STARTOFFS")
|
|
1515
1530
|
local source = reaper.GetMediaItemTake_Source(take)
|
|
1516
1531
|
if source then
|
|
1517
|
-
|
|
1532
|
+
local _, src_fn = reaper.GetMediaSourceFileName(source, "")
|
|
1533
|
+
source_file = src_fn or ""
|
|
1518
1534
|
end
|
|
1519
1535
|
end
|
|
1520
1536
|
|
|
@@ -1634,21 +1650,26 @@ function handlers.move_media_item(params)
|
|
|
1634
1650
|
local track, item, err = get_media_item(params)
|
|
1635
1651
|
if err then return nil, err end
|
|
1636
1652
|
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
if params.newPosition ~= nil then
|
|
1640
|
-
reaper.SetMediaItemInfo_Value(item, "D_POSITION", params.newPosition)
|
|
1641
|
-
end
|
|
1642
|
-
|
|
1653
|
+
-- Validate destination track before starting undo block
|
|
1643
1654
|
if params.newTrackIndex ~= nil then
|
|
1644
1655
|
local dest_track = reaper.GetTrack(0, params.newTrackIndex)
|
|
1645
1656
|
if not dest_track then
|
|
1646
|
-
reaper.Undo_EndBlock("MCP: Move media item (failed)", -1)
|
|
1647
1657
|
return nil, "Destination track " .. params.newTrackIndex .. " not found"
|
|
1648
1658
|
end
|
|
1659
|
+
end
|
|
1660
|
+
|
|
1661
|
+
reaper.Undo_BeginBlock()
|
|
1662
|
+
|
|
1663
|
+
-- Move track first, then set position (MoveMediaItemToTrack preserves position)
|
|
1664
|
+
if params.newTrackIndex ~= nil then
|
|
1665
|
+
local dest_track = reaper.GetTrack(0, params.newTrackIndex)
|
|
1649
1666
|
reaper.MoveMediaItemToTrack(item, dest_track)
|
|
1650
1667
|
end
|
|
1651
1668
|
|
|
1669
|
+
if params.newPosition ~= nil then
|
|
1670
|
+
reaper.SetMediaItemInfo_Value(item, "D_POSITION", params.newPosition)
|
|
1671
|
+
end
|
|
1672
|
+
|
|
1652
1673
|
reaper.Undo_EndBlock("MCP: Move media item", -1)
|
|
1653
1674
|
reaper.UpdateArrange()
|
|
1654
1675
|
|
|
@@ -1663,39 +1684,33 @@ function handlers.trim_media_item(params)
|
|
|
1663
1684
|
local track, item, err = get_media_item(params)
|
|
1664
1685
|
if err then return nil, err end
|
|
1665
1686
|
|
|
1666
|
-
reaper.Undo_BeginBlock()
|
|
1667
|
-
|
|
1668
1687
|
local pos = reaper.GetMediaItemInfo_Value(item, "D_POSITION")
|
|
1669
1688
|
local len = reaper.GetMediaItemInfo_Value(item, "D_LENGTH")
|
|
1689
|
+
|
|
1690
|
+
-- Validate both trims upfront before applying either
|
|
1691
|
+
local trim_start = (params.trimStart ~= nil and params.trimStart ~= 0) and params.trimStart or 0
|
|
1692
|
+
local trim_end = (params.trimEnd ~= nil and params.trimEnd ~= 0) and params.trimEnd or 0
|
|
1693
|
+
local new_len = len - trim_start - trim_end
|
|
1694
|
+
if new_len <= 0 then
|
|
1695
|
+
return nil, "Trim would result in zero or negative length (current: " .. len .. "s, trimStart: " .. trim_start .. "s, trimEnd: " .. trim_end .. "s)"
|
|
1696
|
+
end
|
|
1697
|
+
|
|
1698
|
+
reaper.Undo_BeginBlock()
|
|
1699
|
+
|
|
1670
1700
|
local take = reaper.GetActiveTake(item)
|
|
1671
1701
|
|
|
1672
|
-
if
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
if new_len <= 0 then
|
|
1676
|
-
reaper.Undo_EndBlock("MCP: Trim media item (failed)", -1)
|
|
1677
|
-
return nil, "trimStart would result in zero or negative length"
|
|
1678
|
-
end
|
|
1679
|
-
reaper.SetMediaItemInfo_Value(item, "D_POSITION", new_pos)
|
|
1680
|
-
reaper.SetMediaItemInfo_Value(item, "D_LENGTH", new_len)
|
|
1702
|
+
if trim_start ~= 0 then
|
|
1703
|
+
pos = pos + trim_start
|
|
1704
|
+
reaper.SetMediaItemInfo_Value(item, "D_POSITION", pos)
|
|
1681
1705
|
-- Adjust take start offset
|
|
1682
1706
|
if take then
|
|
1683
1707
|
local offset = reaper.GetMediaItemTakeInfo_Value(take, "D_STARTOFFS")
|
|
1684
|
-
reaper.SetMediaItemTakeInfo_Value(take, "D_STARTOFFS", offset +
|
|
1708
|
+
reaper.SetMediaItemTakeInfo_Value(take, "D_STARTOFFS", offset + trim_start)
|
|
1685
1709
|
end
|
|
1686
|
-
pos = new_pos
|
|
1687
|
-
len = new_len
|
|
1688
1710
|
end
|
|
1689
1711
|
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
if new_len <= 0 then
|
|
1693
|
-
reaper.Undo_EndBlock("MCP: Trim media item (failed)", -1)
|
|
1694
|
-
return nil, "trimEnd would result in zero or negative length"
|
|
1695
|
-
end
|
|
1696
|
-
reaper.SetMediaItemInfo_Value(item, "D_LENGTH", new_len)
|
|
1697
|
-
len = new_len
|
|
1698
|
-
end
|
|
1712
|
+
reaper.SetMediaItemInfo_Value(item, "D_LENGTH", new_len)
|
|
1713
|
+
len = new_len
|
|
1699
1714
|
|
|
1700
1715
|
reaper.Undo_EndBlock("MCP: Trim media item", -1)
|
|
1701
1716
|
reaper.UpdateArrange()
|