@mthines/reaper-mcp 0.1.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.
Files changed (42) hide show
  1. package/README.md +281 -0
  2. package/claude-rules/architecture.md +39 -0
  3. package/claude-rules/development.md +54 -0
  4. package/claude-rules/lua-bridge.md +50 -0
  5. package/claude-rules/testing.md +42 -0
  6. package/claude-skills/learn-plugin.md +123 -0
  7. package/knowledge/genres/_template.md +109 -0
  8. package/knowledge/genres/electronic.md +112 -0
  9. package/knowledge/genres/hip-hop.md +111 -0
  10. package/knowledge/genres/metal.md +136 -0
  11. package/knowledge/genres/orchestral.md +132 -0
  12. package/knowledge/genres/pop.md +108 -0
  13. package/knowledge/genres/rock.md +117 -0
  14. package/knowledge/plugins/_template.md +82 -0
  15. package/knowledge/plugins/fabfilter/pro-c-2.md +117 -0
  16. package/knowledge/plugins/fabfilter/pro-l-2.md +95 -0
  17. package/knowledge/plugins/fabfilter/pro-q-3.md +112 -0
  18. package/knowledge/plugins/neural-dsp/helix-native.md +104 -0
  19. package/knowledge/plugins/stock-reaper/js-1175-compressor.md +94 -0
  20. package/knowledge/plugins/stock-reaper/rea-comp.md +100 -0
  21. package/knowledge/plugins/stock-reaper/rea-delay.md +95 -0
  22. package/knowledge/plugins/stock-reaper/rea-eq.md +103 -0
  23. package/knowledge/plugins/stock-reaper/rea-gate.md +99 -0
  24. package/knowledge/plugins/stock-reaper/rea-limit.md +75 -0
  25. package/knowledge/plugins/stock-reaper/rea-verb.md +76 -0
  26. package/knowledge/reference/common-mistakes.md +307 -0
  27. package/knowledge/reference/compression.md +176 -0
  28. package/knowledge/reference/frequencies.md +154 -0
  29. package/knowledge/reference/metering.md +166 -0
  30. package/knowledge/workflows/drum-bus.md +211 -0
  31. package/knowledge/workflows/gain-staging.md +165 -0
  32. package/knowledge/workflows/low-end.md +261 -0
  33. package/knowledge/workflows/master-bus.md +204 -0
  34. package/knowledge/workflows/vocal-chain.md +246 -0
  35. package/main.js +755 -0
  36. package/package.json +44 -0
  37. package/reaper/install.sh +50 -0
  38. package/reaper/mcp_analyzer.jsfx +167 -0
  39. package/reaper/mcp_bridge.lua +1105 -0
  40. package/reaper/mcp_correlation_meter.jsfx +148 -0
  41. package/reaper/mcp_crest_factor.jsfx +108 -0
  42. package/reaper/mcp_lufs_meter.jsfx +301 -0
@@ -0,0 +1,148 @@
1
+ desc:MCP Correlation Meter
2
+ // Stereo field correlation and M/S analysis for MCP bridge integration.
3
+ // Measures L/R correlation coefficient and stereo width via M/S decomposition.
4
+ // Writes results to shared memory (gmem) so the Lua bridge can read them.
5
+ //
6
+ // gmem layout:
7
+ // gmem[0] = correlation coefficient (-1.0 = out of phase, 0 = uncorrelated, +1.0 = mono)
8
+ // gmem[1] = stereo width (0.0 = mono, 1.0 = normal stereo, 2.0 = very wide)
9
+ // gmem[2] = mid level (dB)
10
+ // gmem[3] = side level (dB)
11
+
12
+ options:gmem=MCPCorrelationMeter
13
+
14
+ slider1:window_ms=300<50,2000,10>Window (ms)
15
+
16
+ @init
17
+ // Ring buffer for L, R, L*R, L*L, R*R over sliding window
18
+ // Buffer starts at address 0 in JSFX local memory
19
+ buf_l = 0; // L samples squared (L*L)
20
+ buf_r = 16384; // R samples squared (R*R)
21
+ buf_lr = 32768; // L*R cross-correlation samples
22
+ buf_m = 49152; // Mid squared (M*M)
23
+ buf_s = 65536; // Side squared (S*S)
24
+ buf_size = 0; // set from sample rate at runtime
25
+
26
+ buf_pos = 0;
27
+
28
+ // Running sums for window
29
+ sum_ll = 0;
30
+ sum_rr = 0;
31
+ sum_lr = 0;
32
+ sum_mm = 0;
33
+ sum_ss = 0;
34
+
35
+ needs_init = 1;
36
+
37
+ @slider
38
+ needs_init = 1;
39
+
40
+ @sample
41
+ needs_init ? (
42
+ buf_size = ceil(srate * window_ms / 1000);
43
+ buf_size > 16384 ? buf_size = 16384; // cap at ~370ms @ 44.1kHz
44
+ buf_size < 1 ? buf_size = 1;
45
+ // Clear buffers and sums
46
+ memset(buf_l, 0, buf_size);
47
+ memset(buf_r, 0, buf_size);
48
+ memset(buf_lr, 0, buf_size);
49
+ memset(buf_m, 0, buf_size);
50
+ memset(buf_s, 0, buf_size);
51
+ sum_ll = 0; sum_rr = 0; sum_lr = 0;
52
+ sum_mm = 0; sum_ss = 0;
53
+ buf_pos = 0;
54
+ needs_init = 0;
55
+ gmem[0] = 0;
56
+ gmem[1] = 1;
57
+ gmem[2] = -150;
58
+ gmem[3] = -150;
59
+ );
60
+
61
+ local(l, r, m, s);
62
+ l = spl0;
63
+ r = spl1;
64
+
65
+ // M/S decomposition (normalised by 0.5)
66
+ m = (l + r) * 0.5;
67
+ s = (l - r) * 0.5;
68
+
69
+ local(ll, rr, lr, mm, ss);
70
+ ll = l * l;
71
+ rr = r * r;
72
+ lr = l * r;
73
+ mm = m * m;
74
+ ss = s * s;
75
+
76
+ // Subtract outgoing samples from sums
77
+ sum_ll -= buf_l[buf_pos];
78
+ sum_rr -= buf_r[buf_pos];
79
+ sum_lr -= buf_lr[buf_pos];
80
+ sum_mm -= buf_m[buf_pos];
81
+ sum_ss -= buf_s[buf_pos];
82
+
83
+ // Write new samples
84
+ buf_l[buf_pos] = ll;
85
+ buf_r[buf_pos] = rr;
86
+ buf_lr[buf_pos] = lr;
87
+ buf_m[buf_pos] = mm;
88
+ buf_s[buf_pos] = ss;
89
+
90
+ // Add new samples to sums
91
+ sum_ll += ll;
92
+ sum_rr += rr;
93
+ sum_lr += lr;
94
+ sum_mm += mm;
95
+ sum_ss += ss;
96
+
97
+ buf_pos += 1;
98
+ buf_pos >= buf_size ? buf_pos = 0;
99
+
100
+ // Update gmem every 512 samples (~86 Hz at 44.1kHz)
101
+ (buf_pos % 512) == 0 ? (
102
+ local(denom, corr, mid_rms, side_rms, mid_db, side_db, width);
103
+
104
+ // Correlation: sum(L*R) / sqrt(sum(L*L) * sum(R*R))
105
+ denom = sqrt(sum_ll * sum_rr);
106
+ denom > 0.0000001 ? (
107
+ corr = sum_lr / denom;
108
+ // Clamp to [-1, 1] for numerical safety
109
+ corr > 1 ? corr = 1;
110
+ corr < -1 ? corr = -1;
111
+ ) : (
112
+ corr = 0;
113
+ );
114
+
115
+ // Mid and side RMS levels
116
+ mid_rms = sqrt(sum_mm / buf_size);
117
+ side_rms = sqrt(sum_ss / buf_size);
118
+
119
+ mid_rms > 0.000001 ? (
120
+ mid_db = 20 * log10(mid_rms);
121
+ ) : (
122
+ mid_db = -150;
123
+ );
124
+
125
+ side_rms > 0.000001 ? (
126
+ side_db = 20 * log10(side_rms);
127
+ ) : (
128
+ side_db = -150;
129
+ );
130
+
131
+ // Stereo width: ratio of side to mid energy
132
+ // width = 0 when fully mono (no side), 1 for balanced stereo, 2+ for very wide
133
+ sum_mm > 0.0000001 ? (
134
+ width = sqrt(sum_ss / sum_mm);
135
+ // Normalize: a "normal" stereo signal with M=S has width=1
136
+ width = min(width * 2, 2.0);
137
+ ) : (
138
+ // Silence or pure side signal
139
+ sum_ss > 0 ? width = 2.0 : width = 0;
140
+ );
141
+
142
+ gmem[0] = corr;
143
+ gmem[1] = width;
144
+ gmem[2] = mid_db;
145
+ gmem[3] = side_db;
146
+ );
147
+
148
+ // Pass audio through unmodified
@@ -0,0 +1,108 @@
1
+ desc:MCP Crest Factor Meter
2
+ // Dynamic range meter via peak-to-RMS (crest factor) for MCP bridge integration.
3
+ // Measures peak level, RMS level, and their difference (crest factor) in dB.
4
+ // Higher crest factor = more dynamic (less compressed).
5
+ // Writes results to shared memory (gmem) so the Lua bridge can read them.
6
+ //
7
+ // gmem layout:
8
+ // gmem[0] = crest factor (dB) = peak level - RMS level
9
+ // gmem[1] = peak level (dB)
10
+ // gmem[2] = RMS level (dB)
11
+
12
+ options:gmem=MCPCrestFactor
13
+
14
+ slider1:window_ms=1000<100,5000,100>Window (ms)
15
+ slider2:peak_hold_ms=1000<100,10000,100>Peak Hold (ms)
16
+
17
+ @init
18
+ // Ring buffer for squared samples (RMS window)
19
+ buf_sq = 0; // squared mono samples
20
+ buf_size = 0; // set from sample rate at runtime
21
+ buf_pos = 0;
22
+ sum_sq = 0;
23
+
24
+ // Peak hold
25
+ peak_val = 0;
26
+ peak_hold_samples = 0;
27
+ peak_hold_pos = 0;
28
+
29
+ needs_init = 1;
30
+
31
+ @slider
32
+ needs_init = 1;
33
+
34
+ @sample
35
+ needs_init ? (
36
+ buf_size = ceil(srate * window_ms / 1000);
37
+ buf_size > 262144 ? buf_size = 262144;
38
+ buf_size < 1 ? buf_size = 1;
39
+ memset(buf_sq, 0, buf_size);
40
+ sum_sq = 0;
41
+ buf_pos = 0;
42
+ peak_val = 0;
43
+ peak_hold_samples = ceil(srate * peak_hold_ms / 1000);
44
+ peak_hold_pos = 0;
45
+ needs_init = 0;
46
+ gmem[0] = 0;
47
+ gmem[1] = -150;
48
+ gmem[2] = -150;
49
+ );
50
+
51
+ // Mono mix for metering
52
+ local(mono, sq, peak_abs);
53
+ mono = (spl0 + spl1) * 0.5;
54
+ sq = mono * mono;
55
+ peak_abs = abs(mono);
56
+
57
+ // --- RMS ring buffer ---
58
+ sum_sq -= buf_sq[buf_pos];
59
+ buf_sq[buf_pos] = sq;
60
+ sum_sq += sq;
61
+ buf_pos += 1;
62
+ buf_pos >= buf_size ? buf_pos = 0;
63
+
64
+ // --- Peak hold ---
65
+ peak_abs > peak_val ? (
66
+ peak_val = peak_abs;
67
+ peak_hold_pos = 0;
68
+ ) : (
69
+ peak_hold_pos += 1;
70
+ peak_hold_pos >= peak_hold_samples ? (
71
+ // Peak hold expired, decay toward current level
72
+ peak_val *= 0.9999;
73
+ peak_hold_pos = 0;
74
+ );
75
+ );
76
+
77
+ // Update gmem every 512 samples
78
+ (buf_pos % 512) == 0 ? (
79
+ local(rms_val, rms_db, peak_db, crest_db);
80
+
81
+ rms_val = sqrt(sum_sq / buf_size);
82
+
83
+ rms_val > 0.000001 ? (
84
+ rms_db = 20 * log10(rms_val);
85
+ ) : (
86
+ rms_db = -150;
87
+ );
88
+
89
+ peak_val > 0.000001 ? (
90
+ peak_db = 20 * log10(peak_val);
91
+ ) : (
92
+ peak_db = -150;
93
+ );
94
+
95
+ // Crest factor = peak - RMS (both in dB)
96
+ (rms_db > -150 && peak_db > -150) ? (
97
+ crest_db = peak_db - rms_db;
98
+ crest_db < 0 ? crest_db = 0; // should not happen but clamp for safety
99
+ ) : (
100
+ crest_db = 0;
101
+ );
102
+
103
+ gmem[0] = crest_db;
104
+ gmem[1] = peak_db;
105
+ gmem[2] = rms_db;
106
+ );
107
+
108
+ // Pass audio through unmodified
@@ -0,0 +1,301 @@
1
+ desc:MCP LUFS Meter
2
+ // ITU-R BS.1770 loudness meter for MCP bridge integration.
3
+ // Measures integrated, short-term, and momentary LUFS with true peak detection.
4
+ // Writes results to shared memory (gmem) so the Lua bridge can read them.
5
+ //
6
+ // gmem layout:
7
+ // gmem[0] = integrated LUFS (running average since last reset)
8
+ // gmem[1] = short-term LUFS (3-second sliding window)
9
+ // gmem[2] = momentary LUFS (400ms sliding window)
10
+ // gmem[3] = true peak L (dBTP, inter-sample peak via 4x oversampling)
11
+ // gmem[4] = true peak R (dBTP, inter-sample peak via 4x oversampling)
12
+ // gmem[5] = measurement duration (seconds since last reset)
13
+
14
+ options:gmem=MCPLufsMeter
15
+
16
+ slider1:reset_btn=0<0,1,1{Idle,Reset}>Reset Measurement
17
+
18
+ @init
19
+ // K-weighting filter state (two biquad stages per channel)
20
+ // Stage 1: High-shelf pre-filter (+4 dB at 1681 Hz)
21
+ // Stage 2: High-pass RLB filter (100 Hz, -12 dB/oct)
22
+
23
+ // Biquad state: [b0, b1, b2, a1, a2, x1, x2, y1, y2] per stage per channel
24
+ // We store state variables in flat arrays
25
+ hs_x1l = hs_x2l = hs_y1l = hs_y2l = 0;
26
+ hs_x1r = hs_x2r = hs_y1r = hs_y2r = 0;
27
+ hp_x1l = hp_x2l = hp_y1l = hp_y2l = 0;
28
+ hp_x1r = hp_x2r = hp_y1r = hp_y2r = 0;
29
+
30
+ // High-shelf filter coefficients (pre-computed for 48kHz; recomputed @slider)
31
+ // Using bilinear transform of analog prototype per BS.1770-4 Annex 1
32
+ hs_b0 = hs_b1 = hs_b2 = hs_a1 = hs_a2 = 0;
33
+ hp_b0 = hp_b1 = hp_b2 = hp_a1 = hp_a2 = 0;
34
+
35
+ // Circular buffer for loudness blocks (sliding windows)
36
+ // 400ms block = momentary, 3s = short-term
37
+ // BS.1770 uses 100ms overlapping blocks (75% overlap)
38
+ // We approximate with per-sample accumulation into sliding windows
39
+
40
+ // Ring buffer for per-sample squared weighted values
41
+ // momentary: 400ms window
42
+ // short-term: 3s window
43
+ // We store up to 3s of samples in a circular buffer
44
+ buf_size = 0; // computed at runtime from srate
45
+ buf_l = 65536; // start address for left channel ring buffer
46
+ buf_r = 131072; // start address for right channel ring buffer
47
+ buf_pos = 0;
48
+
49
+ // Running sums for windows
50
+ momentary_sum_l = 0;
51
+ momentary_sum_r = 0;
52
+ shortterm_sum_l = 0;
53
+ shortterm_sum_r = 0;
54
+ integrated_sum = 0;
55
+ integrated_count = 0;
56
+
57
+ // True peak hold
58
+ true_peak_l = 0.000001;
59
+ true_peak_r = 0.000001;
60
+
61
+ // Measurement duration
62
+ sample_count = 0;
63
+
64
+ // Flags
65
+ needs_init = 1;
66
+
67
+ function compute_kweight_filters() (
68
+ // High-shelf pre-filter: +4 dB shelf at f0 = 1681.974 Hz
69
+ // From BS.1770-4, Annex 1, Table 1
70
+ local(db, K, Vh, Vb, a0);
71
+ db = 3.99984385397; // ~4 dB
72
+ K = tan($pi * 1681.974 / srate);
73
+ Vh = exp(db / 20 * log(10));
74
+ Vb = Vh ^ 0.4996667741545416;
75
+ a0 = 1 + K/0.7071067811865476 + K*K;
76
+ hs_b0 = (Vh + Vb * K / 0.7071067811865476 + K*K) / a0;
77
+ hs_b1 = 2 * (K*K - Vh) / a0;
78
+ hs_b2 = (Vh - Vb * K / 0.7071067811865476 + K*K) / a0;
79
+ hs_a1 = 2 * (K*K - 1) / a0;
80
+ hs_a2 = (1 - K/0.7071067811865476 + K*K) / a0;
81
+
82
+ // High-pass filter: 38.13547 Hz, Q = 0.5003270373238773
83
+ local(K2, a0);
84
+ K2 = tan($pi * 38.13547 / srate);
85
+ a0 = K2*K2 + K2/0.5003270373238773 + 1;
86
+ hp_b0 = 1 / a0;
87
+ hp_b1 = -2 / a0;
88
+ hp_b2 = 1 / a0;
89
+ hp_a1 = 2 * (K2*K2 - 1) / a0;
90
+ hp_a2 = (K2*K2 - K2/0.5003270373238773 + 1) / a0;
91
+ );
92
+
93
+ function reset_measurement() (
94
+ buf_pos = 0;
95
+ momentary_sum_l = 0;
96
+ momentary_sum_r = 0;
97
+ shortterm_sum_l = 0;
98
+ shortterm_sum_r = 0;
99
+ integrated_sum = 0;
100
+ integrated_count = 0;
101
+ true_peak_l = 0.000001;
102
+ true_peak_r = 0.000001;
103
+ sample_count = 0;
104
+ hs_x1l = hs_x2l = hs_y1l = hs_y2l = 0;
105
+ hs_x1r = hs_x2r = hs_y1r = hs_y2r = 0;
106
+ hp_x1l = hp_x2l = hp_y1l = hp_y2l = 0;
107
+ hp_x1r = hp_x2r = hp_y1r = hp_y2r = 0;
108
+ // Clear ring buffers
109
+ memset(buf_l, 0, buf_size);
110
+ 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;
117
+ );
118
+
119
+ @slider
120
+ // Re-compute filter coefficients (in case sample rate has changed)
121
+ needs_init = 1;
122
+
123
+ // Handle reset button
124
+ slider1 == 1 ? (
125
+ reset_measurement();
126
+ slider1 = 0;
127
+ sliderchange(slider1);
128
+ );
129
+
130
+ @sample
131
+ needs_init ? (
132
+ compute_kweight_filters();
133
+ // Buffer: 3 seconds at current sample rate
134
+ buf_size = ceil(srate * 3.0);
135
+ buf_size > 262144 ? buf_size = 262144; // safety cap (covers up to ~87kHz)
136
+ reset_measurement();
137
+ needs_init = 0;
138
+ );
139
+
140
+ // --- K-weighting: stage 1 (high-shelf) ---
141
+ local(wl, wr, kl, kr);
142
+ wl = spl0;
143
+ wr = spl1;
144
+
145
+ // High-shelf left
146
+ kl = hs_b0 * wl + hs_b1 * hs_x1l + hs_b2 * hs_x2l - hs_a1 * hs_y1l - hs_a2 * hs_y2l;
147
+ hs_x2l = hs_x1l; hs_x1l = wl; hs_y2l = hs_y1l; hs_y1l = kl;
148
+
149
+ // High-shelf right
150
+ kr = hs_b0 * wr + hs_b1 * hs_x1r + hs_b2 * hs_x2r - hs_a1 * hs_y1r - hs_a2 * hs_y2r;
151
+ hs_x2r = hs_x1r; hs_x1r = wr; hs_y2r = hs_y1r; hs_y1r = kr;
152
+
153
+ // --- K-weighting: stage 2 (high-pass) ---
154
+ local(fl, fr);
155
+ fl = hp_b0 * kl + hp_b1 * hp_x1l + hp_b2 * hp_x2l - hp_a1 * hp_y1l - hp_a2 * hp_y2l;
156
+ hp_x2l = hp_x1l; hp_x1l = kl; hp_y2l = hp_y1l; hp_y1l = fl;
157
+
158
+ fr = hp_b0 * kr + hp_b1 * hp_x1r + hp_b2 * hp_x2r - hp_a1 * hp_y1r - hp_a2 * hp_y2r;
159
+ hp_x2r = hp_x1r; hp_x1r = kr; hp_y2r = hp_y1r; hp_y1r = fr;
160
+
161
+ // Squared weighted samples
162
+ local(sq_l, sq_r);
163
+ sq_l = fl * fl;
164
+ sq_r = fr * fr;
165
+
166
+ // --- Ring buffer update ---
167
+ // Subtract outgoing samples from running sums
168
+ local(old_l, old_r);
169
+ old_l = buf_l[buf_pos];
170
+ old_r = buf_r[buf_pos];
171
+
172
+ // Short-term window is full 3s buffer
173
+ shortterm_sum_l -= old_l;
174
+ shortterm_sum_r -= old_r;
175
+
176
+ // Momentary window: 400ms = srate * 0.4 samples back
177
+ // We subtract from momentary only when the sample leaving is within 400ms window
178
+ // Simplified: maintain separate momentary accumulators via separate pointers
179
+ // For efficiency, use a single ring buffer and recompute momentary from partial sum
180
+ // Here we track momentary with a second set of pointers (400ms lag)
181
+ local(mom_pos);
182
+ mom_pos = buf_pos - floor(srate * 0.4);
183
+ mom_pos < 0 ? mom_pos += buf_size;
184
+ momentary_sum_l -= buf_l[mom_pos];
185
+ momentary_sum_r -= buf_r[mom_pos];
186
+
187
+ // Write new values into ring buffer
188
+ buf_l[buf_pos] = sq_l;
189
+ buf_r[buf_pos] = sq_r;
190
+
191
+ // Add new values to sums
192
+ shortterm_sum_l += sq_l;
193
+ shortterm_sum_r += sq_r;
194
+ momentary_sum_l += sq_l;
195
+ momentary_sum_r += sq_r;
196
+
197
+ // Advance ring buffer position
198
+ buf_pos += 1;
199
+ buf_pos >= buf_size ? buf_pos = 0;
200
+
201
+ // --- Integrated loudness (gated per BS.1770) ---
202
+ // Absolute gate: only include 400ms blocks above -70 LUFS
203
+ // Simplified: accumulate ungated for speed, apply gate check periodically
204
+ integrated_sum += sq_l + sq_r;
205
+ integrated_count += 1;
206
+
207
+ // --- True peak: 4x linear interpolation oversampling ---
208
+ // Insert 3 interpolated samples between each real sample using linear interpolation
209
+ local(prev_l, prev_r, frac, tp_l, tp_r);
210
+
211
+ // We use the previous sample stored in a local state var
212
+ // Stored in memory block above buf_r to avoid conflict
213
+ // prev_l is at buf_r + buf_size, prev_r is at buf_r + buf_size + 1
214
+ prev_l = buf_r[buf_size];
215
+ prev_r = buf_r[buf_size + 1];
216
+
217
+ tp_l = abs(spl0);
218
+ tp_r = abs(spl1);
219
+
220
+ // Check interpolated peaks at 1/4, 2/4, 3/4 offsets
221
+ frac = 0.25;
222
+ loop(3,
223
+ local(interp_l, interp_r);
224
+ interp_l = abs(prev_l + frac * (spl0 - prev_l));
225
+ interp_r = abs(prev_r + frac * (spl1 - prev_r));
226
+ interp_l > tp_l ? tp_l = interp_l;
227
+ interp_r > tp_r ? tp_r = interp_r;
228
+ frac += 0.25;
229
+ );
230
+
231
+ tp_l > true_peak_l ? true_peak_l = tp_l;
232
+ tp_r > true_peak_r ? true_peak_r = tp_r;
233
+
234
+ // Store current samples as previous for next iteration
235
+ buf_r[buf_size] = spl0;
236
+ buf_r[buf_size + 1] = spl1;
237
+
238
+ sample_count += 1;
239
+
240
+ // --- Update gmem every 4096 samples (approx 10x/second at 44.1kHz) ---
241
+ (sample_count % 4096) == 0 ? (
242
+ local(shortterm_mean, momentary_mean, integrated_lufs, shortterm_lufs, momentary_lufs);
243
+ local(mom_samples);
244
+
245
+ mom_samples = floor(srate * 0.4);
246
+ mom_samples < 1 ? mom_samples = 1;
247
+
248
+ // Mean square over each window
249
+ shortterm_mean = (shortterm_sum_l + shortterm_sum_r) / (buf_size * 2);
250
+ momentary_mean = (momentary_sum_l + momentary_sum_r) / (mom_samples * 2);
251
+
252
+ // Integrated mean square (two channels: L + R sum, divided by count*2)
253
+ local(integrated_mean);
254
+ integrated_mean = integrated_count > 0 ? (integrated_sum / (integrated_count * 2)) : 0;
255
+
256
+ // LUFS = -0.691 + 10 * log10(mean_square)
257
+ shortterm_mean > 0 ? (
258
+ shortterm_lufs = -0.691 + 10 * log10(shortterm_mean);
259
+ ) : (
260
+ shortterm_lufs = -70;
261
+ );
262
+
263
+ momentary_mean > 0 ? (
264
+ momentary_lufs = -0.691 + 10 * log10(momentary_mean);
265
+ ) : (
266
+ momentary_lufs = -70;
267
+ );
268
+
269
+ integrated_mean > 0 ? (
270
+ // Apply absolute gate at -70 LUFS
271
+ local(ungated_lufs);
272
+ ungated_lufs = -0.691 + 10 * log10(integrated_mean);
273
+ ungated_lufs > -70 ? (
274
+ integrated_lufs = ungated_lufs;
275
+ ) : (
276
+ integrated_lufs = -70;
277
+ );
278
+ ) : (
279
+ integrated_lufs = -70;
280
+ );
281
+
282
+ gmem[0] = integrated_lufs;
283
+ gmem[1] = shortterm_lufs;
284
+ gmem[2] = momentary_lufs;
285
+
286
+ true_peak_l > 0.000001 ? (
287
+ gmem[3] = 20 * log10(true_peak_l);
288
+ ) : (
289
+ gmem[3] = -150;
290
+ );
291
+
292
+ true_peak_r > 0.000001 ? (
293
+ gmem[4] = 20 * log10(true_peak_r);
294
+ ) : (
295
+ gmem[4] = -150;
296
+ );
297
+
298
+ gmem[5] = sample_count / srate;
299
+ );
300
+
301
+ // Pass audio through unmodified