@mthines/reaper-mcp 0.14.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mthines/reaper-mcp",
3
- "version": "0.14.0",
3
+ "version": "0.14.1",
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",
@@ -1000,8 +1000,25 @@ function handlers.snapshot_restore(params)
1000
1000
 
1001
1001
  local restored = 0
1002
1002
 
1003
- if state.tracks then
1004
- for _, track_state in ipairs(state.tracks) do
1003
+ -- Decode tracks array if the fallback JSON parser left it as a raw string
1004
+ local tracks = state.tracks
1005
+ if type(tracks) == "string" then
1006
+ tracks = json_decode_array(tracks)
1007
+ end
1008
+
1009
+ if tracks then
1010
+ for _, track_state in ipairs(tracks) do
1011
+ -- Decode nested arrays that may also be raw strings from fallback parser
1012
+ if type(track_state.fx) == "string" then
1013
+ track_state.fx = json_decode_array(track_state.fx)
1014
+ end
1015
+ if type(track_state.sends) == "string" then
1016
+ track_state.sends = json_decode_array(track_state.sends)
1017
+ end
1018
+ if type(track_state.fxEnabled) == "string" then
1019
+ track_state.fxEnabled = json_decode_array(track_state.fxEnabled)
1020
+ end
1021
+
1005
1022
  local track = reaper.GetTrack(0, track_state.index)
1006
1023
  if track then
1007
1024
  -- Basic mixer state (all versions)
@@ -1240,11 +1257,14 @@ function handlers.read_track_lufs(params)
1240
1257
  local fx_idx, err = ensure_jsfx_on_track(track, MCP_LUFS_METER_FX_NAME)
1241
1258
  if not fx_idx then return nil, err end
1242
1259
 
1243
- -- Set the track_slot parameter (slider2, param index 1) so this instance
1244
- -- writes to a unique gmem offset and doesn't collide with other tracks
1245
- reaper.TrackFX_SetParam(track, fx_idx, 1, idx / 127)
1260
+ -- Set the track_slot parameter so this instance writes to a unique gmem offset
1261
+ local desired_slot = idx / 127
1262
+ local current_slot = reaper.TrackFX_GetParam(track, fx_idx, 1)
1263
+ if math.abs(current_slot - desired_slot) > 0.001 then
1264
+ reaper.TrackFX_SetParam(track, fx_idx, 1, desired_slot)
1265
+ end
1246
1266
 
1247
- -- Attach to the LUFS meter gmem namespace and read from track-specific offset
1267
+ -- Read from gmem (JSFX writes here from @sample)
1248
1268
  reaper.gmem_attach("MCPLufsMeter")
1249
1269
 
1250
1270
  local base = idx * 8
@@ -1277,6 +1297,7 @@ function handlers.read_track_correlation(params)
1277
1297
  local fx_idx, err = ensure_jsfx_on_track(track, MCP_CORRELATION_METER_FX_NAME)
1278
1298
  if not fx_idx then return nil, err end
1279
1299
 
1300
+ -- Read from gmem (JSFX writes here from @sample)
1280
1301
  reaper.gmem_attach("MCPCorrelationMeter")
1281
1302
 
1282
1303
  local correlation = reaper.gmem_read(0)
@@ -1303,6 +1324,7 @@ function handlers.read_track_crest(params)
1303
1324
  local fx_idx, err = ensure_jsfx_on_track(track, MCP_CREST_FACTOR_FX_NAME)
1304
1325
  if not fx_idx then return nil, err end
1305
1326
 
1327
+ -- Read from gmem (JSFX writes here from @sample)
1306
1328
  reaper.gmem_attach("MCPCrestFactor")
1307
1329
 
1308
1330
  local crest_factor = reaper.gmem_read(0)
@@ -2861,8 +2883,9 @@ function handlers.create_track_envelope(params)
2861
2883
  return nil, "Unknown envelope name: " .. params.envelopeName .. ". Use Volume, Pan, Mute, Width, or Trim Volume"
2862
2884
  end
2863
2885
  -- Get track chunk, insert envelope chunk if missing
2886
+ -- Use anchored pattern "<VOLENV\n" to avoid matching VOLENV2 (Trim Volume)
2864
2887
  local _, chunk = reaper.GetTrackStateChunk(track, "", false)
2865
- if not chunk:find(chunk_key) then
2888
+ if not chunk:find("<" .. chunk_key .. "\n") then
2866
2889
  -- Insert a minimal envelope chunk before the closing >
2867
2890
  local env_chunk = "\n<" .. chunk_key .. "\nACT 1 -1\nVIS 1 1 1\nLANEHEIGHT 0 0\nARM 0\nDEFSHAPE 0 -1 -1\n>\n"
2868
2891
  -- Use position capture to find the last ">" (closing the <TRACK block).
@@ -2874,8 +2897,8 @@ function handlers.create_track_envelope(params)
2874
2897
  reaper.SetTrackStateChunk(track, chunk, false)
2875
2898
  else
2876
2899
  -- Envelope exists in chunk but may be hidden; make it visible
2877
- chunk = chunk:gsub("(" .. chunk_key .. "[^\n]*\n)ACT 0", "%1ACT 1")
2878
- chunk = chunk:gsub("(" .. chunk_key .. "[^\n]*\n[^\n]*\n)VIS 0", "%1VIS 1")
2900
+ chunk = chunk:gsub("(<" .. chunk_key .. "[^\n]*\n)ACT 0", "%1ACT 1")
2901
+ chunk = chunk:gsub("(<" .. chunk_key .. "[^\n]*\n[^\n]*\n)VIS 0", "%1VIS 1")
2879
2902
  reaper.SetTrackStateChunk(track, chunk, false)
2880
2903
  end
2881
2904
  env = reaper.GetTrackEnvelopeByName(track, params.envelopeName)
@@ -58,7 +58,6 @@ slider1:window_ms=300<50,2000,10>Window (ms)
58
58
  gmem[3] = -150;
59
59
  );
60
60
 
61
- local(l, r, m, s);
62
61
  l = spl0;
63
62
  r = spl1;
64
63
 
@@ -66,7 +65,6 @@ slider1:window_ms=300<50,2000,10>Window (ms)
66
65
  m = (l + r) * 0.5;
67
66
  s = (l - r) * 0.5;
68
67
 
69
- local(ll, rr, lr, mm, ss);
70
68
  ll = l * l;
71
69
  rr = r * r;
72
70
  lr = l * r;
@@ -99,8 +97,6 @@ slider1:window_ms=300<50,2000,10>Window (ms)
99
97
 
100
98
  // Update gmem every 512 samples (~86 Hz at 44.1kHz)
101
99
  (buf_pos % 512) == 0 ? (
102
- local(denom, corr, mid_rms, side_rms, mid_db, side_db, width);
103
-
104
100
  // Correlation: sum(L*R) / sqrt(sum(L*L) * sum(R*R))
105
101
  denom = sqrt(sum_ll * sum_rr);
106
102
  denom > 0.0000001 ? (
@@ -49,7 +49,6 @@ slider2:peak_hold_ms=1000<100,10000,100>Peak Hold (ms)
49
49
  );
50
50
 
51
51
  // Mono mix for metering
52
- local(mono, sq, peak_abs);
53
52
  mono = (spl0 + spl1) * 0.5;
54
53
  sq = mono * mono;
55
54
  peak_abs = abs(mono);
@@ -76,8 +75,6 @@ slider2:peak_hold_ms=1000<100,10000,100>Peak Hold (ms)
76
75
 
77
76
  // Update gmem every 512 samples
78
77
  (buf_pos % 512) == 0 ? (
79
- local(rms_val, rms_db, peak_db, crest_db);
80
-
81
78
  rms_val = sqrt(sum_sq / buf_size);
82
79
 
83
80
  rms_val > 0.000001 ? (
@@ -21,29 +21,18 @@ slider2:track_slot=0<0,127,1>Track Slot
21
21
  // K-weighting filter state (two biquad stages per channel)
22
22
  // Stage 1: High-shelf pre-filter (+4 dB at 1681 Hz)
23
23
  // Stage 2: High-pass RLB filter (100 Hz, -12 dB/oct)
24
-
25
- // Biquad state: [b0, b1, b2, a1, a2, x1, x2, y1, y2] per stage per channel
26
- // We store state variables in flat arrays
27
24
  hs_x1l = hs_x2l = hs_y1l = hs_y2l = 0;
28
25
  hs_x1r = hs_x2r = hs_y1r = hs_y2r = 0;
29
26
  hp_x1l = hp_x2l = hp_y1l = hp_y2l = 0;
30
27
  hp_x1r = hp_x2r = hp_y1r = hp_y2r = 0;
31
28
 
32
- // High-shelf filter coefficients (pre-computed for 48kHz; recomputed @slider)
33
- // Using bilinear transform of analog prototype per BS.1770-4 Annex 1
29
+ // High-shelf filter coefficients (recomputed on init and sample rate change)
34
30
  hs_b0 = hs_b1 = hs_b2 = hs_a1 = hs_a2 = 0;
35
31
  hp_b0 = hp_b1 = hp_b2 = hp_a1 = hp_a2 = 0;
36
32
 
37
- // Circular buffer for loudness blocks (sliding windows)
38
- // 400ms block = momentary, 3s = short-term
39
- // BS.1770 uses 100ms overlapping blocks (75% overlap)
40
- // We approximate with per-sample accumulation into sliding windows
41
-
42
- // Ring buffer for per-sample squared weighted values
43
- // momentary: 400ms window
44
- // short-term: 3s window
45
- // We store up to 3s of samples in a circular buffer
46
- buf_size = 0; // computed at runtime from srate
33
+ // Ring buffer for per-sample squared weighted values (up to 3s)
34
+ // momentary: 400ms window, short-term: 3s window
35
+ buf_size = 0;
47
36
  buf_l = 65536; // start address for left channel ring buffer
48
37
  buf_r = 131072; // start address for right channel ring buffer
49
38
  buf_pos = 0;
@@ -63,13 +52,11 @@ slider2:track_slot=0<0,127,1>Track Slot
63
52
  // Measurement duration
64
53
  sample_count = 0;
65
54
 
66
- // Flags
67
55
  needs_init = 1;
68
56
 
69
57
  function compute_kweight_filters() (
70
58
  // High-shelf pre-filter: +4 dB shelf at f0 = 1681.974 Hz
71
59
  // From BS.1770-4, Annex 1, Table 1
72
- local(db, K, Vh, Vb, a0);
73
60
  db = 3.99984385397; // ~4 dB
74
61
  K = tan($pi * 1681.974 / srate);
75
62
  Vh = exp(db / 20 * log(10));
@@ -82,7 +69,6 @@ slider2:track_slot=0<0,127,1>Track Slot
82
69
  hs_a2 = (1 - K/0.7071067811865476 + K*K) / a0;
83
70
 
84
71
  // High-pass filter: 38.13547 Hz, Q = 0.5003270373238773
85
- local(K2, a0);
86
72
  K2 = tan($pi * 38.13547 / srate);
87
73
  a0 = K2*K2 + K2/0.5003270373238773 + 1;
88
74
  hp_b0 = 1 / a0;
@@ -93,7 +79,6 @@ slider2:track_slot=0<0,127,1>Track Slot
93
79
  );
94
80
 
95
81
  function reset_measurement() (
96
- local(base);
97
82
  base = floor(slider2 + 0.5) * 8;
98
83
  buf_pos = 0;
99
84
  momentary_sum_l = 0;
@@ -121,7 +106,7 @@ slider2:track_slot=0<0,127,1>Track Slot
121
106
  );
122
107
 
123
108
  @slider
124
- // Re-compute filter coefficients (in case sample rate has changed)
109
+ // Re-compute filter coefficients (handles sample rate changes)
125
110
  needs_init = 1;
126
111
 
127
112
  // Handle reset button
@@ -142,7 +127,6 @@ slider2:track_slot=0<0,127,1>Track Slot
142
127
  );
143
128
 
144
129
  // --- K-weighting: stage 1 (high-shelf) ---
145
- local(wl, wr, kl, kr);
146
130
  wl = spl0;
147
131
  wr = spl1;
148
132
 
@@ -155,7 +139,6 @@ slider2:track_slot=0<0,127,1>Track Slot
155
139
  hs_x2r = hs_x1r; hs_x1r = wr; hs_y2r = hs_y1r; hs_y1r = kr;
156
140
 
157
141
  // --- K-weighting: stage 2 (high-pass) ---
158
- local(fl, fr);
159
142
  fl = hp_b0 * kl + hp_b1 * hp_x1l + hp_b2 * hp_x2l - hp_a1 * hp_y1l - hp_a2 * hp_y2l;
160
143
  hp_x2l = hp_x1l; hp_x1l = kl; hp_y2l = hp_y1l; hp_y1l = fl;
161
144
 
@@ -163,13 +146,10 @@ slider2:track_slot=0<0,127,1>Track Slot
163
146
  hp_x2r = hp_x1r; hp_x1r = kr; hp_y2r = hp_y1r; hp_y1r = fr;
164
147
 
165
148
  // Squared weighted samples
166
- local(sq_l, sq_r);
167
149
  sq_l = fl * fl;
168
150
  sq_r = fr * fr;
169
151
 
170
152
  // --- Ring buffer update ---
171
- // Subtract outgoing samples from running sums
172
- local(old_l, old_r);
173
153
  old_l = buf_l[buf_pos];
174
154
  old_r = buf_r[buf_pos];
175
155
 
@@ -177,12 +157,7 @@ slider2:track_slot=0<0,127,1>Track Slot
177
157
  shortterm_sum_l -= old_l;
178
158
  shortterm_sum_r -= old_r;
179
159
 
180
- // Momentary window: 400ms = srate * 0.4 samples back
181
- // We subtract from momentary only when the sample leaving is within 400ms window
182
- // Simplified: maintain separate momentary accumulators via separate pointers
183
- // For efficiency, use a single ring buffer and recompute momentary from partial sum
184
- // Here we track momentary with a second set of pointers (400ms lag)
185
- local(mom_pos);
160
+ // Momentary window: 400ms lag pointer
186
161
  mom_pos = buf_pos - floor(srate * 0.4);
187
162
  mom_pos < 0 ? mom_pos += buf_size;
188
163
  momentary_sum_l -= buf_l[mom_pos];
@@ -203,18 +178,12 @@ slider2:track_slot=0<0,127,1>Track Slot
203
178
  buf_pos >= buf_size ? buf_pos = 0;
204
179
 
205
180
  // --- Integrated loudness (gated per BS.1770) ---
206
- // Absolute gate: only include 400ms blocks above -70 LUFS
207
- // Simplified: accumulate ungated for speed, apply gate check periodically
181
+ // Simplified: accumulate ungated, apply gate check periodically
208
182
  integrated_sum += sq_l + sq_r;
209
183
  integrated_count += 1;
210
184
 
211
185
  // --- True peak: 4x linear interpolation oversampling ---
212
- // Insert 3 interpolated samples between each real sample using linear interpolation
213
- local(prev_l, prev_r, frac, tp_l, tp_r);
214
-
215
- // We use the previous sample stored in a local state var
216
- // Stored in memory block above buf_r to avoid conflict
217
- // prev_l is at buf_r + buf_size, prev_r is at buf_r + buf_size + 1
186
+ // Previous sample stored above buf_r to avoid conflict
218
187
  prev_l = buf_r[buf_size];
219
188
  prev_r = buf_r[buf_size + 1];
220
189
 
@@ -224,7 +193,6 @@ slider2:track_slot=0<0,127,1>Track Slot
224
193
  // Check interpolated peaks at 1/4, 2/4, 3/4 offsets
225
194
  frac = 0.25;
226
195
  loop(3,
227
- local(interp_l, interp_r);
228
196
  interp_l = abs(prev_l + frac * (spl0 - prev_l));
229
197
  interp_r = abs(prev_r + frac * (spl1 - prev_r));
230
198
  interp_l > tp_l ? tp_l = interp_l;
@@ -243,9 +211,6 @@ slider2:track_slot=0<0,127,1>Track Slot
243
211
 
244
212
  // --- Update gmem every 4096 samples (approx 10x/second at 44.1kHz) ---
245
213
  (sample_count % 4096) == 0 ? (
246
- local(shortterm_mean, momentary_mean, integrated_lufs, shortterm_lufs, momentary_lufs);
247
- local(mom_samples, base);
248
-
249
214
  base = floor(slider2 + 0.5) * 8;
250
215
 
251
216
  mom_samples = floor(srate * 0.4);
@@ -255,8 +220,7 @@ slider2:track_slot=0<0,127,1>Track Slot
255
220
  shortterm_mean = (shortterm_sum_l + shortterm_sum_r) / (buf_size * 2);
256
221
  momentary_mean = (momentary_sum_l + momentary_sum_r) / (mom_samples * 2);
257
222
 
258
- // Integrated mean square (two channels: L + R sum, divided by count*2)
259
- local(integrated_mean);
223
+ // Integrated mean square (two channels)
260
224
  integrated_mean = integrated_count > 0 ? (integrated_sum / (integrated_count * 2)) : 0;
261
225
 
262
226
  // LUFS = -0.691 + 10 * log10(mean_square)
@@ -274,7 +238,6 @@ slider2:track_slot=0<0,127,1>Track Slot
274
238
 
275
239
  integrated_mean > 0 ? (
276
240
  // Apply absolute gate at -70 LUFS
277
- local(ungated_lufs);
278
241
  ungated_lufs = -0.691 + 10 * log10(integrated_mean);
279
242
  ungated_lufs > -70 ? (
280
243
  integrated_lufs = ungated_lufs;