@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.
- package/README.md +281 -0
- package/claude-rules/architecture.md +39 -0
- package/claude-rules/development.md +54 -0
- package/claude-rules/lua-bridge.md +50 -0
- package/claude-rules/testing.md +42 -0
- package/claude-skills/learn-plugin.md +123 -0
- package/knowledge/genres/_template.md +109 -0
- package/knowledge/genres/electronic.md +112 -0
- package/knowledge/genres/hip-hop.md +111 -0
- package/knowledge/genres/metal.md +136 -0
- package/knowledge/genres/orchestral.md +132 -0
- package/knowledge/genres/pop.md +108 -0
- package/knowledge/genres/rock.md +117 -0
- package/knowledge/plugins/_template.md +82 -0
- package/knowledge/plugins/fabfilter/pro-c-2.md +117 -0
- package/knowledge/plugins/fabfilter/pro-l-2.md +95 -0
- package/knowledge/plugins/fabfilter/pro-q-3.md +112 -0
- package/knowledge/plugins/neural-dsp/helix-native.md +104 -0
- package/knowledge/plugins/stock-reaper/js-1175-compressor.md +94 -0
- package/knowledge/plugins/stock-reaper/rea-comp.md +100 -0
- package/knowledge/plugins/stock-reaper/rea-delay.md +95 -0
- package/knowledge/plugins/stock-reaper/rea-eq.md +103 -0
- package/knowledge/plugins/stock-reaper/rea-gate.md +99 -0
- package/knowledge/plugins/stock-reaper/rea-limit.md +75 -0
- package/knowledge/plugins/stock-reaper/rea-verb.md +76 -0
- package/knowledge/reference/common-mistakes.md +307 -0
- package/knowledge/reference/compression.md +176 -0
- package/knowledge/reference/frequencies.md +154 -0
- package/knowledge/reference/metering.md +166 -0
- package/knowledge/workflows/drum-bus.md +211 -0
- package/knowledge/workflows/gain-staging.md +165 -0
- package/knowledge/workflows/low-end.md +261 -0
- package/knowledge/workflows/master-bus.md +204 -0
- package/knowledge/workflows/vocal-chain.md +246 -0
- package/main.js +755 -0
- package/package.json +44 -0
- package/reaper/install.sh +50 -0
- package/reaper/mcp_analyzer.jsfx +167 -0
- package/reaper/mcp_bridge.lua +1105 -0
- package/reaper/mcp_correlation_meter.jsfx +148 -0
- package/reaper/mcp_crest_factor.jsfx +108 -0
- 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
|