@mthines/reaper-mcp 0.13.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/main.js +275 -1
- package/package.json +1 -1
- package/reaper/mcp_bridge.lua +222 -9
- package/reaper/mcp_correlation_meter.jsfx +0 -4
- package/reaper/mcp_crest_factor.jsfx +0 -3
- package/reaper/mcp_lufs_meter.jsfx +9 -46
package/main.js
CHANGED
|
@@ -1875,6 +1875,270 @@ function registerEnvelopeTools(server) {
|
|
|
1875
1875
|
);
|
|
1876
1876
|
}
|
|
1877
1877
|
|
|
1878
|
+
// apps/reaper-mcp-server/src/tools/batch.ts
|
|
1879
|
+
import { z as z15 } from "zod/v4";
|
|
1880
|
+
function registerBatchTools(server) {
|
|
1881
|
+
server.tool(
|
|
1882
|
+
"set_multiple_track_properties",
|
|
1883
|
+
"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.",
|
|
1884
|
+
{
|
|
1885
|
+
tracks: z15.array(z15.object({
|
|
1886
|
+
trackIndex: z15.coerce.number().int().min(0).describe("0-based track index"),
|
|
1887
|
+
volume: z15.coerce.number().optional().describe("Volume in dB (0 = unity gain)"),
|
|
1888
|
+
pan: z15.coerce.number().min(-1).max(1).optional().describe("Pan position -1.0 (left) to 1.0 (right)"),
|
|
1889
|
+
mute: z15.coerce.number().int().min(0).max(1).optional().describe("Mute state (0=unmuted, 1=muted)"),
|
|
1890
|
+
solo: z15.coerce.number().int().min(0).max(1).optional().describe("Solo state (0=unsolo, 1=solo)"),
|
|
1891
|
+
recordArm: z15.coerce.number().int().min(0).max(1).optional().describe("Record arm state (0=unarmed, 1=armed)"),
|
|
1892
|
+
phase: z15.coerce.number().int().min(0).max(1).optional().describe("Phase inversion (0=normal, 1=inverted)"),
|
|
1893
|
+
input: z15.coerce.number().int().optional().describe("REAPER input index (-1 = no input)")
|
|
1894
|
+
})).describe("Array of track property updates to apply")
|
|
1895
|
+
},
|
|
1896
|
+
async ({ tracks }) => {
|
|
1897
|
+
const res = await sendCommand("set_multiple_track_properties", { tracks });
|
|
1898
|
+
if (!res.success) {
|
|
1899
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
|
|
1900
|
+
}
|
|
1901
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
1902
|
+
}
|
|
1903
|
+
);
|
|
1904
|
+
server.tool(
|
|
1905
|
+
"setup_fx_chain",
|
|
1906
|
+
"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.",
|
|
1907
|
+
{
|
|
1908
|
+
trackIndex: z15.coerce.number().int().min(0).describe("0-based track index"),
|
|
1909
|
+
plugins: z15.array(z15.object({
|
|
1910
|
+
fxName: z15.string().describe('FX plugin name (partial match supported, e.g. "ReaEQ", "VST: Pro-Q 3")'),
|
|
1911
|
+
enabled: z15.coerce.number().int().min(0).max(1).optional().describe("Initial enabled state (1=enabled default, 0=bypassed)"),
|
|
1912
|
+
parameters: z15.array(z15.object({
|
|
1913
|
+
index: z15.coerce.number().int().min(0).describe("Parameter index"),
|
|
1914
|
+
value: z15.coerce.number().min(0).max(1).describe("Normalized parameter value 0.0\u20131.0")
|
|
1915
|
+
})).optional().describe("Initial parameter values to set after adding the plugin")
|
|
1916
|
+
})).describe("Array of FX plugins to add, in order")
|
|
1917
|
+
},
|
|
1918
|
+
async ({ trackIndex, plugins }) => {
|
|
1919
|
+
const res = await sendCommand("setup_fx_chain", { trackIndex, plugins });
|
|
1920
|
+
if (!res.success) {
|
|
1921
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
|
|
1922
|
+
}
|
|
1923
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
1924
|
+
}
|
|
1925
|
+
);
|
|
1926
|
+
server.tool(
|
|
1927
|
+
"set_multiple_fx_parameters",
|
|
1928
|
+
"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.",
|
|
1929
|
+
{
|
|
1930
|
+
updates: z15.array(z15.object({
|
|
1931
|
+
trackIndex: z15.coerce.number().int().min(0).describe("0-based track index"),
|
|
1932
|
+
fxIndex: z15.coerce.number().int().min(0).describe("0-based FX index in the chain"),
|
|
1933
|
+
paramIndex: z15.coerce.number().int().min(0).describe("0-based parameter index"),
|
|
1934
|
+
value: z15.coerce.number().min(0).max(1).describe("Normalized parameter value 0.0\u20131.0")
|
|
1935
|
+
})).describe("Array of FX parameter updates to apply")
|
|
1936
|
+
},
|
|
1937
|
+
async ({ updates }) => {
|
|
1938
|
+
const res = await sendCommand("set_multiple_fx_parameters", { updates });
|
|
1939
|
+
if (!res.success) {
|
|
1940
|
+
return { content: [{ type: "text", text: `Error: ${res.error}` }], isError: true };
|
|
1941
|
+
}
|
|
1942
|
+
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
1943
|
+
}
|
|
1944
|
+
);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// apps/reaper-mcp-server/src/tools/categories.ts
|
|
1948
|
+
import { z as z16 } from "zod/v4";
|
|
1949
|
+
var TOOL_CATEGORIES = {
|
|
1950
|
+
project: {
|
|
1951
|
+
name: "project",
|
|
1952
|
+
description: "Project-level information: name, tempo, time signature, sample rate, transport state",
|
|
1953
|
+
tools: ["get_project_info"]
|
|
1954
|
+
},
|
|
1955
|
+
tracks: {
|
|
1956
|
+
name: "tracks",
|
|
1957
|
+
description: "Track management: list, inspect, and set properties (volume, pan, mute, solo, record arm, phase, input). Includes batch set for multiple tracks at once.",
|
|
1958
|
+
tools: ["list_tracks", "get_track_properties", "set_track_property", "set_multiple_track_properties"]
|
|
1959
|
+
},
|
|
1960
|
+
fx: {
|
|
1961
|
+
name: "fx",
|
|
1962
|
+
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.",
|
|
1963
|
+
tools: ["add_fx", "remove_fx", "get_fx_parameters", "set_fx_parameter", "set_fx_enabled", "set_fx_offline", "setup_fx_chain", "set_multiple_fx_parameters"]
|
|
1964
|
+
},
|
|
1965
|
+
transport: {
|
|
1966
|
+
name: "transport",
|
|
1967
|
+
description: "Transport control: play, stop, record, get transport state, set cursor position",
|
|
1968
|
+
tools: ["play", "stop", "record", "get_transport_state", "set_cursor_position"]
|
|
1969
|
+
},
|
|
1970
|
+
midi: {
|
|
1971
|
+
name: "midi",
|
|
1972
|
+
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.",
|
|
1973
|
+
tools: [
|
|
1974
|
+
"create_midi_item",
|
|
1975
|
+
"list_midi_items",
|
|
1976
|
+
"get_midi_notes",
|
|
1977
|
+
"analyze_midi",
|
|
1978
|
+
"insert_midi_note",
|
|
1979
|
+
"insert_midi_notes",
|
|
1980
|
+
"edit_midi_note",
|
|
1981
|
+
"edit_midi_notes",
|
|
1982
|
+
"delete_midi_note",
|
|
1983
|
+
"get_midi_cc",
|
|
1984
|
+
"insert_midi_cc",
|
|
1985
|
+
"delete_midi_cc",
|
|
1986
|
+
"get_midi_item_properties",
|
|
1987
|
+
"set_midi_item_properties"
|
|
1988
|
+
]
|
|
1989
|
+
},
|
|
1990
|
+
media: {
|
|
1991
|
+
name: "media",
|
|
1992
|
+
description: "Media item editing: list, inspect, set properties, split, delete, move, trim, stretch markers. Includes batch set for multiple items at once.",
|
|
1993
|
+
tools: [
|
|
1994
|
+
"list_media_items",
|
|
1995
|
+
"get_media_item_properties",
|
|
1996
|
+
"set_media_item_properties",
|
|
1997
|
+
"set_media_items_properties",
|
|
1998
|
+
"split_media_item",
|
|
1999
|
+
"delete_media_item",
|
|
2000
|
+
"move_media_item",
|
|
2001
|
+
"trim_media_item",
|
|
2002
|
+
"add_stretch_marker",
|
|
2003
|
+
"get_stretch_markers",
|
|
2004
|
+
"delete_stretch_marker"
|
|
2005
|
+
]
|
|
2006
|
+
},
|
|
2007
|
+
selection: {
|
|
2008
|
+
name: "selection",
|
|
2009
|
+
description: "Selection and navigation: get selected tracks, get/set time selection range",
|
|
2010
|
+
tools: ["get_selected_tracks", "get_time_selection", "set_time_selection"]
|
|
2011
|
+
},
|
|
2012
|
+
markers: {
|
|
2013
|
+
name: "markers",
|
|
2014
|
+
description: "Markers and regions: list, add, delete markers and regions with optional names and colors",
|
|
2015
|
+
tools: ["list_markers", "list_regions", "add_marker", "add_region", "delete_marker", "delete_region"]
|
|
2016
|
+
},
|
|
2017
|
+
tempo: {
|
|
2018
|
+
name: "tempo",
|
|
2019
|
+
description: "Tempo map: get all tempo and time signature changes with positions, BPM, and linear/step flags",
|
|
2020
|
+
tools: ["get_tempo_map"]
|
|
2021
|
+
},
|
|
2022
|
+
envelopes: {
|
|
2023
|
+
name: "envelopes",
|
|
2024
|
+
description: "Automation envelopes: create, inspect, insert/delete points, clear, batch insert, set properties. Supports volume, pan, mute, width, trim volume, and FX parameter envelopes.",
|
|
2025
|
+
tools: [
|
|
2026
|
+
"create_track_envelope",
|
|
2027
|
+
"get_track_envelopes",
|
|
2028
|
+
"get_envelope_points",
|
|
2029
|
+
"insert_envelope_point",
|
|
2030
|
+
"insert_envelope_points",
|
|
2031
|
+
"delete_envelope_point",
|
|
2032
|
+
"clear_envelope",
|
|
2033
|
+
"remove_envelope_points",
|
|
2034
|
+
"set_envelope_properties"
|
|
2035
|
+
]
|
|
2036
|
+
},
|
|
2037
|
+
analysis: {
|
|
2038
|
+
name: "analysis",
|
|
2039
|
+
description: "Audio metering and analysis: peak/RMS meters, FFT spectrum, LUFS loudness, stereo correlation, crest factor",
|
|
2040
|
+
tools: ["read_track_meters", "read_track_spectrum", "read_track_lufs", "read_track_correlation", "read_track_crest"]
|
|
2041
|
+
},
|
|
2042
|
+
discovery: {
|
|
2043
|
+
name: "discovery",
|
|
2044
|
+
description: "FX discovery and presets: list all installed plugins, fuzzy search by name, list and load presets",
|
|
2045
|
+
tools: ["list_available_fx", "search_fx", "get_fx_preset_list", "set_fx_preset"]
|
|
2046
|
+
},
|
|
2047
|
+
snapshots: {
|
|
2048
|
+
name: "snapshots",
|
|
2049
|
+
description: "Mixer state snapshots: save, restore, and list named snapshots of volumes, pans, FX, and mutes",
|
|
2050
|
+
tools: ["snapshot_save", "snapshot_restore", "snapshot_list"]
|
|
2051
|
+
},
|
|
2052
|
+
routing: {
|
|
2053
|
+
name: "routing",
|
|
2054
|
+
description: "Track routing: inspect sends, receives, parent/folder relationships for a track",
|
|
2055
|
+
tools: ["get_track_routing"]
|
|
2056
|
+
}
|
|
2057
|
+
};
|
|
2058
|
+
function registerCategoryTools(server) {
|
|
2059
|
+
server.tool(
|
|
2060
|
+
"list_tool_categories",
|
|
2061
|
+
"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.",
|
|
2062
|
+
{},
|
|
2063
|
+
async () => {
|
|
2064
|
+
const categories = Object.values(TOOL_CATEGORIES).map((cat) => ({
|
|
2065
|
+
name: cat.name,
|
|
2066
|
+
description: cat.description,
|
|
2067
|
+
toolCount: cat.tools.length,
|
|
2068
|
+
tools: cat.tools
|
|
2069
|
+
}));
|
|
2070
|
+
return {
|
|
2071
|
+
content: [{
|
|
2072
|
+
type: "text",
|
|
2073
|
+
text: JSON.stringify({ categories, totalTools: categories.reduce((sum, c) => sum + c.toolCount, 0) }, null, 2)
|
|
2074
|
+
}]
|
|
2075
|
+
};
|
|
2076
|
+
}
|
|
2077
|
+
);
|
|
2078
|
+
server.tool(
|
|
2079
|
+
"enable_tool_category",
|
|
2080
|
+
"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.",
|
|
2081
|
+
{
|
|
2082
|
+
category: z16.string().describe('Category name (e.g. "tracks", "fx", "midi", "media", "transport", "markers", "envelopes", "analysis", "discovery")')
|
|
2083
|
+
},
|
|
2084
|
+
async ({ category }) => {
|
|
2085
|
+
const cat = TOOL_CATEGORIES[category];
|
|
2086
|
+
if (!cat) {
|
|
2087
|
+
const available = Object.keys(TOOL_CATEGORIES).join(", ");
|
|
2088
|
+
return {
|
|
2089
|
+
content: [{
|
|
2090
|
+
type: "text",
|
|
2091
|
+
text: `Error: Unknown category "${category}". Available categories: ${available}`
|
|
2092
|
+
}],
|
|
2093
|
+
isError: true
|
|
2094
|
+
};
|
|
2095
|
+
}
|
|
2096
|
+
return {
|
|
2097
|
+
content: [{
|
|
2098
|
+
type: "text",
|
|
2099
|
+
text: JSON.stringify({
|
|
2100
|
+
category: cat.name,
|
|
2101
|
+
description: cat.description,
|
|
2102
|
+
toolCount: cat.tools.length,
|
|
2103
|
+
tools: cat.tools,
|
|
2104
|
+
status: "ready"
|
|
2105
|
+
}, null, 2)
|
|
2106
|
+
}]
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
);
|
|
2110
|
+
server.tool(
|
|
2111
|
+
"disable_tool_category",
|
|
2112
|
+
"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.",
|
|
2113
|
+
{
|
|
2114
|
+
category: z16.string().describe('Category name (e.g. "tracks", "fx", "midi", "media", "transport", "markers", "envelopes", "analysis", "discovery")')
|
|
2115
|
+
},
|
|
2116
|
+
async ({ category }) => {
|
|
2117
|
+
const cat = TOOL_CATEGORIES[category];
|
|
2118
|
+
if (!cat) {
|
|
2119
|
+
const available = Object.keys(TOOL_CATEGORIES).join(", ");
|
|
2120
|
+
return {
|
|
2121
|
+
content: [{
|
|
2122
|
+
type: "text",
|
|
2123
|
+
text: `Error: Unknown category "${category}". Available categories: ${available}`
|
|
2124
|
+
}],
|
|
2125
|
+
isError: true
|
|
2126
|
+
};
|
|
2127
|
+
}
|
|
2128
|
+
return {
|
|
2129
|
+
content: [{
|
|
2130
|
+
type: "text",
|
|
2131
|
+
text: JSON.stringify({
|
|
2132
|
+
category: cat.name,
|
|
2133
|
+
status: "disabled",
|
|
2134
|
+
note: "Tools remain available if needed. Use enable_tool_category to re-activate."
|
|
2135
|
+
}, null, 2)
|
|
2136
|
+
}]
|
|
2137
|
+
};
|
|
2138
|
+
}
|
|
2139
|
+
);
|
|
2140
|
+
}
|
|
2141
|
+
|
|
1878
2142
|
// apps/reaper-mcp-server/src/server.ts
|
|
1879
2143
|
function instrumentToolHandlers(server) {
|
|
1880
2144
|
const originalTool = server.tool.bind(server);
|
|
@@ -1932,6 +2196,8 @@ function createServer() {
|
|
|
1932
2196
|
registerMarkerTools(server);
|
|
1933
2197
|
registerTempoTools(server);
|
|
1934
2198
|
registerEnvelopeTools(server);
|
|
2199
|
+
registerBatchTools(server);
|
|
2200
|
+
registerCategoryTools(server);
|
|
1935
2201
|
return server;
|
|
1936
2202
|
}
|
|
1937
2203
|
|
|
@@ -2091,7 +2357,15 @@ var MCP_TOOL_NAMES = [
|
|
|
2091
2357
|
"create_track_envelope",
|
|
2092
2358
|
"set_envelope_properties",
|
|
2093
2359
|
"clear_envelope",
|
|
2094
|
-
"remove_envelope_points"
|
|
2360
|
+
"remove_envelope_points",
|
|
2361
|
+
// composite batch tools
|
|
2362
|
+
"set_multiple_track_properties",
|
|
2363
|
+
"setup_fx_chain",
|
|
2364
|
+
"set_multiple_fx_parameters",
|
|
2365
|
+
// progressive discovery
|
|
2366
|
+
"list_tool_categories",
|
|
2367
|
+
"enable_tool_category",
|
|
2368
|
+
"disable_tool_category"
|
|
2095
2369
|
];
|
|
2096
2370
|
function ensureClaudeSettings(settingsPath) {
|
|
2097
2371
|
const allowList = MCP_TOOL_NAMES.map((t) => `mcp__reaper__${t}`);
|
package/package.json
CHANGED
package/reaper/mcp_bridge.lua
CHANGED
|
@@ -1000,8 +1000,25 @@ function handlers.snapshot_restore(params)
|
|
|
1000
1000
|
|
|
1001
1001
|
local restored = 0
|
|
1002
1002
|
|
|
1003
|
-
|
|
1004
|
-
|
|
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
|
|
1244
|
-
|
|
1245
|
-
reaper.
|
|
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
|
-
--
|
|
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)
|
|
@@ -2352,6 +2374,196 @@ function handlers.set_fx_offline(params)
|
|
|
2352
2374
|
return { trackIndex = params.trackIndex, fxIndex = params.fxIndex, offline = params.offline == 1 }
|
|
2353
2375
|
end
|
|
2354
2376
|
|
|
2377
|
+
-- =============================================================================
|
|
2378
|
+
-- Composite batch tools
|
|
2379
|
+
-- =============================================================================
|
|
2380
|
+
|
|
2381
|
+
function handlers.set_multiple_track_properties(params)
|
|
2382
|
+
local tracks_input = params.tracks
|
|
2383
|
+
if not tracks_input then return nil, "tracks array required" end
|
|
2384
|
+
|
|
2385
|
+
local tracks = nil
|
|
2386
|
+
if type(tracks_input) == "string" then
|
|
2387
|
+
tracks = json_decode(tracks_input)
|
|
2388
|
+
elseif type(tracks_input) == "table" then
|
|
2389
|
+
tracks = tracks_input
|
|
2390
|
+
end
|
|
2391
|
+
if not tracks then return nil, "Failed to parse tracks array" end
|
|
2392
|
+
|
|
2393
|
+
local updated = 0
|
|
2394
|
+
local errors = {}
|
|
2395
|
+
|
|
2396
|
+
reaper.Undo_BeginBlock()
|
|
2397
|
+
|
|
2398
|
+
for _, t in ipairs(tracks) do
|
|
2399
|
+
local track_idx = t.trackIndex
|
|
2400
|
+
if track_idx == nil then
|
|
2401
|
+
errors[#errors + 1] = "track entry missing trackIndex"
|
|
2402
|
+
else
|
|
2403
|
+
local track = reaper.GetTrack(0, track_idx)
|
|
2404
|
+
if not track then
|
|
2405
|
+
errors[#errors + 1] = "Track " .. track_idx .. " not found"
|
|
2406
|
+
else
|
|
2407
|
+
if t.volume ~= nil then
|
|
2408
|
+
reaper.SetMediaTrackInfo_Value(track, "D_VOL", from_db(t.volume))
|
|
2409
|
+
end
|
|
2410
|
+
if t.pan ~= nil then
|
|
2411
|
+
reaper.SetMediaTrackInfo_Value(track, "D_PAN", t.pan)
|
|
2412
|
+
end
|
|
2413
|
+
if t.mute ~= nil then
|
|
2414
|
+
reaper.SetMediaTrackInfo_Value(track, "B_MUTE", t.mute)
|
|
2415
|
+
end
|
|
2416
|
+
if t.solo ~= nil then
|
|
2417
|
+
reaper.SetMediaTrackInfo_Value(track, "I_SOLO", t.solo)
|
|
2418
|
+
end
|
|
2419
|
+
if t.recordArm ~= nil then
|
|
2420
|
+
reaper.SetMediaTrackInfo_Value(track, "I_RECARM", t.recordArm)
|
|
2421
|
+
end
|
|
2422
|
+
if t.phase ~= nil then
|
|
2423
|
+
reaper.SetMediaTrackInfo_Value(track, "B_PHASE", t.phase)
|
|
2424
|
+
end
|
|
2425
|
+
if t.input ~= nil then
|
|
2426
|
+
reaper.SetMediaTrackInfo_Value(track, "I_RECINPUT", t.input)
|
|
2427
|
+
end
|
|
2428
|
+
updated = updated + 1
|
|
2429
|
+
end
|
|
2430
|
+
end
|
|
2431
|
+
end
|
|
2432
|
+
|
|
2433
|
+
reaper.Undo_EndBlock("MCP: Batch set " .. updated .. " track properties", -1)
|
|
2434
|
+
|
|
2435
|
+
local result = { success = true, updated = updated, total = #tracks }
|
|
2436
|
+
if #errors > 0 then result.errors = errors end
|
|
2437
|
+
return result
|
|
2438
|
+
end
|
|
2439
|
+
|
|
2440
|
+
function handlers.setup_fx_chain(params)
|
|
2441
|
+
local track_idx = params.trackIndex
|
|
2442
|
+
if track_idx == nil then return nil, "trackIndex required" end
|
|
2443
|
+
|
|
2444
|
+
local plugins_input = params.plugins
|
|
2445
|
+
if not plugins_input then return nil, "plugins array required" end
|
|
2446
|
+
|
|
2447
|
+
local plugins = nil
|
|
2448
|
+
if type(plugins_input) == "string" then
|
|
2449
|
+
plugins = json_decode(plugins_input)
|
|
2450
|
+
elseif type(plugins_input) == "table" then
|
|
2451
|
+
plugins = plugins_input
|
|
2452
|
+
end
|
|
2453
|
+
if not plugins then return nil, "Failed to parse plugins array" end
|
|
2454
|
+
|
|
2455
|
+
local track = reaper.GetTrack(0, track_idx)
|
|
2456
|
+
if not track then return nil, "Track " .. track_idx .. " not found" end
|
|
2457
|
+
|
|
2458
|
+
local added = 0
|
|
2459
|
+
local errors = {}
|
|
2460
|
+
local added_plugins = {}
|
|
2461
|
+
|
|
2462
|
+
reaper.Undo_BeginBlock()
|
|
2463
|
+
|
|
2464
|
+
for i, plugin in ipairs(plugins) do
|
|
2465
|
+
local fx_name = plugin.fxName
|
|
2466
|
+
if not fx_name or fx_name == "" then
|
|
2467
|
+
errors[#errors + 1] = "plugin[" .. (i - 1) .. "] missing fxName"
|
|
2468
|
+
else
|
|
2469
|
+
local fx_idx = reaper.TrackFX_AddByName(track, fx_name, false, -1)
|
|
2470
|
+
if fx_idx < 0 then
|
|
2471
|
+
errors[#errors + 1] = "FX not found: " .. fx_name
|
|
2472
|
+
else
|
|
2473
|
+
-- Set enabled state if specified (default is enabled)
|
|
2474
|
+
if plugin.enabled ~= nil then
|
|
2475
|
+
reaper.TrackFX_SetEnabled(track, fx_idx, plugin.enabled == 1)
|
|
2476
|
+
end
|
|
2477
|
+
|
|
2478
|
+
-- Set initial parameters if provided
|
|
2479
|
+
local param_errors = {}
|
|
2480
|
+
if plugin.parameters and type(plugin.parameters) == "table" then
|
|
2481
|
+
for _, p in ipairs(plugin.parameters) do
|
|
2482
|
+
if p.index ~= nil and p.value ~= nil then
|
|
2483
|
+
local ok = reaper.TrackFX_SetParam(track, fx_idx, p.index, p.value)
|
|
2484
|
+
if not ok then
|
|
2485
|
+
param_errors[#param_errors + 1] = "Failed to set param " .. p.index
|
|
2486
|
+
end
|
|
2487
|
+
end
|
|
2488
|
+
end
|
|
2489
|
+
end
|
|
2490
|
+
|
|
2491
|
+
local _, actual_name = reaper.TrackFX_GetFXName(track, fx_idx)
|
|
2492
|
+
added_plugins[#added_plugins + 1] = {
|
|
2493
|
+
pluginIndex = i - 1,
|
|
2494
|
+
fxName = actual_name or fx_name,
|
|
2495
|
+
fxIndex = fx_idx,
|
|
2496
|
+
}
|
|
2497
|
+
if #param_errors > 0 then
|
|
2498
|
+
for _, pe in ipairs(param_errors) do
|
|
2499
|
+
errors[#errors + 1] = fx_name .. ": " .. pe
|
|
2500
|
+
end
|
|
2501
|
+
end
|
|
2502
|
+
added = added + 1
|
|
2503
|
+
end
|
|
2504
|
+
end
|
|
2505
|
+
end
|
|
2506
|
+
|
|
2507
|
+
reaper.Undo_EndBlock("MCP: Setup FX chain (" .. added .. " plugins) on track " .. track_idx, -1)
|
|
2508
|
+
|
|
2509
|
+
local result = {
|
|
2510
|
+
success = true,
|
|
2511
|
+
trackIndex = track_idx,
|
|
2512
|
+
added = added,
|
|
2513
|
+
total = #plugins,
|
|
2514
|
+
plugins = added_plugins,
|
|
2515
|
+
}
|
|
2516
|
+
if #errors > 0 then result.errors = errors end
|
|
2517
|
+
return result
|
|
2518
|
+
end
|
|
2519
|
+
|
|
2520
|
+
function handlers.set_multiple_fx_parameters(params)
|
|
2521
|
+
local updates_input = params.updates
|
|
2522
|
+
if not updates_input then return nil, "updates array required" end
|
|
2523
|
+
|
|
2524
|
+
local updates = nil
|
|
2525
|
+
if type(updates_input) == "string" then
|
|
2526
|
+
updates = json_decode(updates_input)
|
|
2527
|
+
elseif type(updates_input) == "table" then
|
|
2528
|
+
updates = updates_input
|
|
2529
|
+
end
|
|
2530
|
+
if not updates then return nil, "Failed to parse updates array" end
|
|
2531
|
+
|
|
2532
|
+
local updated = 0
|
|
2533
|
+
local errors = {}
|
|
2534
|
+
|
|
2535
|
+
reaper.Undo_BeginBlock()
|
|
2536
|
+
|
|
2537
|
+
for _, u in ipairs(updates) do
|
|
2538
|
+
local track_idx = u.trackIndex
|
|
2539
|
+
local fx_idx = u.fxIndex
|
|
2540
|
+
local param_idx = u.paramIndex
|
|
2541
|
+
local value = u.value
|
|
2542
|
+
|
|
2543
|
+
if track_idx == nil or fx_idx == nil or param_idx == nil or value == nil then
|
|
2544
|
+
errors[#errors + 1] = "update entry missing trackIndex, fxIndex, paramIndex, or value"
|
|
2545
|
+
else
|
|
2546
|
+
local track = reaper.GetTrack(0, track_idx)
|
|
2547
|
+
if not track then
|
|
2548
|
+
errors[#errors + 1] = "Track " .. track_idx .. " not found"
|
|
2549
|
+
else
|
|
2550
|
+
local ok = reaper.TrackFX_SetParam(track, fx_idx, param_idx, value)
|
|
2551
|
+
if not ok then
|
|
2552
|
+
errors[#errors + 1] = "Failed to set track " .. track_idx .. " fx " .. fx_idx .. " param " .. param_idx
|
|
2553
|
+
else
|
|
2554
|
+
updated = updated + 1
|
|
2555
|
+
end
|
|
2556
|
+
end
|
|
2557
|
+
end
|
|
2558
|
+
end
|
|
2559
|
+
|
|
2560
|
+
reaper.Undo_EndBlock("MCP: Batch set " .. updated .. " FX parameters", -1)
|
|
2561
|
+
|
|
2562
|
+
local result = { success = true, updated = updated, total = #updates }
|
|
2563
|
+
if #errors > 0 then result.errors = errors end
|
|
2564
|
+
return result
|
|
2565
|
+
end
|
|
2566
|
+
|
|
2355
2567
|
-- =============================================================================
|
|
2356
2568
|
-- Selection & Navigation
|
|
2357
2569
|
-- =============================================================================
|
|
@@ -2671,8 +2883,9 @@ function handlers.create_track_envelope(params)
|
|
|
2671
2883
|
return nil, "Unknown envelope name: " .. params.envelopeName .. ". Use Volume, Pan, Mute, Width, or Trim Volume"
|
|
2672
2884
|
end
|
|
2673
2885
|
-- Get track chunk, insert envelope chunk if missing
|
|
2886
|
+
-- Use anchored pattern "<VOLENV\n" to avoid matching VOLENV2 (Trim Volume)
|
|
2674
2887
|
local _, chunk = reaper.GetTrackStateChunk(track, "", false)
|
|
2675
|
-
if not chunk:find(chunk_key) then
|
|
2888
|
+
if not chunk:find("<" .. chunk_key .. "\n") then
|
|
2676
2889
|
-- Insert a minimal envelope chunk before the closing >
|
|
2677
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"
|
|
2678
2891
|
-- Use position capture to find the last ">" (closing the <TRACK block).
|
|
@@ -2684,8 +2897,8 @@ function handlers.create_track_envelope(params)
|
|
|
2684
2897
|
reaper.SetTrackStateChunk(track, chunk, false)
|
|
2685
2898
|
else
|
|
2686
2899
|
-- Envelope exists in chunk but may be hidden; make it visible
|
|
2687
|
-
chunk = chunk:gsub("(" .. chunk_key .. "[^\n]*\n)ACT 0", "%1ACT 1")
|
|
2688
|
-
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")
|
|
2689
2902
|
reaper.SetTrackStateChunk(track, chunk, false)
|
|
2690
2903
|
end
|
|
2691
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 (
|
|
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
|
-
//
|
|
38
|
-
// 400ms
|
|
39
|
-
|
|
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 (
|
|
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
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
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;
|