@mthines/reaper-mcp 0.19.0 → 0.70.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -570
- package/bridge-ui/mcp_bridge.html +129 -0
- package/bridge-ui/mcp_bridge.webmanifest +16 -0
- package/bridge-ui/mcp_bridge_icon_192.png +0 -0
- package/bridge-ui/mcp_bridge_icon_512.png +0 -0
- package/bridge-ui/mcp_bridge_sw.js +35 -0
- package/claude/agents/plugin-researcher.md +188 -0
- package/claude/knowledge/reference/aesthetics-scoring.md +48 -0
- package/{knowledge → claude/knowledge}/reference/common-mistakes.md +1 -1
- package/claude/knowledge/reference/lorekit-setup.md +36 -0
- package/claude/knowledge/reference/memory-protocol.md +377 -0
- package/{knowledge → claude/knowledge}/reference/metering.md +5 -5
- package/claude/knowledge/reference/perceived-loudness.md +182 -0
- package/{knowledge → claude/knowledge}/workflows/editing.md +4 -0
- package/{knowledge → claude/knowledge}/workflows/gain-staging.md +21 -23
- package/{knowledge → claude/knowledge}/workflows/learn-plugin.md +15 -48
- package/{knowledge → claude/knowledge}/workflows/master-bus.md +13 -1
- package/claude/knowledge/workflows/section-levels.md +138 -0
- package/{knowledge → claude/knowledge}/workflows/session-prep.md +4 -0
- package/claude/knowledge/workflows/transient-lighting.md +52 -0
- package/claude/skills/critique/SKILL.md +131 -0
- package/claude/skills/editing/SKILL.md +281 -0
- package/claude/skills/learn-plugin/SKILL.md +210 -0
- package/claude/skills/mastering/SKILL.md +151 -0
- package/claude/skills/mix-memory/SKILL.md +140 -0
- package/claude/skills/mixer/SKILL.md +253 -0
- package/claude/skills/new-extension/SKILL.md +324 -0
- package/main.js +7843 -1940
- package/package.json +17 -15
- package/reaper/CLAUDE.md +22 -4
- package/reaper/mcp_actions_lib.lua +375 -0
- package/reaper/mcp_analyzer.jsfx +27 -12
- package/reaper/mcp_arrange_lib.lua +949 -0
- package/reaper/mcp_bridge.lua +2321 -758
- package/reaper/mcp_edit_views_lib.lua +1378 -0
- package/reaper/mcp_envelopes_lib.lua +748 -0
- package/reaper/mcp_extensions_lib.lua +55 -0
- package/reaper/mcp_fxstate_lib.lua +144 -0
- package/reaper/mcp_gain_trace.jsfx +126 -0
- package/reaper/mcp_items_lib.lua +877 -0
- package/reaper/mcp_lufs_meter.jsfx +16 -13
- package/reaper/mcp_midi_lib.lua +555 -0
- package/reaper/mcp_mixing_lib.lua +1021 -0
- package/reaper/mcp_plan_lib.lua +801 -0
- package/reaper/mcp_prefs_lib.lua +127 -0
- package/reaper/mcp_project_lib.lua +1088 -0
- package/reaper/mcp_recording_lib.lua +595 -0
- package/reaper/mcp_render_lib.lua +492 -0
- package/reaper/mcp_snapshot_lib.lua +547 -120
- package/reaper/mcp_snapshot_next.lua +10 -4
- package/reaper/mcp_snapshot_prev.lua +11 -4
- package/reaper/mcp_snapshot_quick_save.lua +7 -32
- package/reaper/mcp_snapshots_ui_lib.lua +258 -0
- package/reaper/mcp_tracks_lib.lua +409 -0
- package/sidecar/analysis.py +661 -0
- package/sidecar/chords.py +96 -0
- package/sidecar/mixcheck.py +295 -0
- package/sidecar/requirements-chords.txt +6 -0
- package/sidecar/requirements-separation.txt +3 -0
- package/sidecar/requirements.txt +17 -0
- package/sidecar/separation.py +142 -0
- package/sidecar/server.py +622 -0
- package/sidecar/tests/test_analysis.py +249 -0
- package/sidecar/tests/test_chords.py +117 -0
- package/sidecar/tests/test_mixcheck.py +163 -0
- package/sidecar/tests/test_separation.py +73 -0
- package/sidecar/tests/test_server.py +464 -0
- package/sidecar/tests/test_verify.py +243 -0
- package/sidecar/tests/test_voice_render.py +268 -0
- package/sidecar/tests/test_voice_sketch.py +140 -0
- package/sidecar/verify.py +224 -0
- package/sidecar/voice_render.py +295 -0
- package/sidecar/voice_sketch.py +280 -0
- package/claude-agents/critique.md +0 -147
- package/claude-agents/editor.md +0 -184
- package/claude-agents/levels.md +0 -107
- package/claude-agents/mastering.md +0 -129
- package/claude-agents/mixer.md +0 -248
- package/claude-agents/preflight.md +0 -149
- package/claude-agents/producer.md +0 -159
- package/claude-agents/setup.md +0 -150
- package/claude-agents/stems.md +0 -135
- package/claude-rules/architecture.md +0 -39
- package/claude-rules/development.md +0 -54
- package/claude-rules/lua-bridge.md +0 -50
- package/claude-rules/testing.md +0 -42
- package/claude-skills/learn-plugin.md +0 -123
- package/knowledge/CLAUDE.md +0 -71
- package/knowledge/plugins/_template.md +0 -82
- package/knowledge/plugins/fabfilter/pro-c-2.md +0 -117
- package/knowledge/plugins/fabfilter/pro-l-2.md +0 -95
- package/knowledge/plugins/fabfilter/pro-q-3.md +0 -112
- package/knowledge/plugins/neural-dsp/helix-native.md +0 -104
- package/knowledge/plugins/stock-reaper/js-1175-compressor.md +0 -94
- package/knowledge/plugins/stock-reaper/rea-comp.md +0 -100
- package/knowledge/plugins/stock-reaper/rea-delay.md +0 -95
- package/knowledge/plugins/stock-reaper/rea-eq.md +0 -103
- package/knowledge/plugins/stock-reaper/rea-gate.md +0 -99
- package/knowledge/plugins/stock-reaper/rea-limit.md +0 -75
- package/knowledge/plugins/stock-reaper/rea-verb.md +0 -76
- package/knowledge/reference/perceived-loudness.md +0 -122
- package/reaper/mcp_snapshot_manager.lua +0 -1001
- /package/{knowledge → claude/knowledge}/genres/_template.md +0 -0
- /package/{knowledge → claude/knowledge}/genres/electronic.md +0 -0
- /package/{knowledge → claude/knowledge}/genres/hip-hop.md +0 -0
- /package/{knowledge → claude/knowledge}/genres/metal.md +0 -0
- /package/{knowledge → claude/knowledge}/genres/orchestral.md +0 -0
- /package/{knowledge → claude/knowledge}/genres/pop.md +0 -0
- /package/{knowledge → claude/knowledge}/genres/rock.md +0 -0
- /package/{knowledge → claude/knowledge}/reference/compression.md +0 -0
- /package/{knowledge → claude/knowledge}/reference/frequencies.md +0 -0
- /package/{knowledge → claude/knowledge}/workflows/delivery.md +0 -0
- /package/{knowledge → claude/knowledge}/workflows/drum-bus.md +0 -0
- /package/{knowledge → claude/knowledge}/workflows/low-end.md +0 -0
- /package/{knowledge → claude/knowledge}/workflows/stem-prep.md +0 -0
- /package/{knowledge → claude/knowledge}/workflows/vocal-chain.md +0 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
-- mcp_extensions_lib.lua — run a Lua command that an extension ships (extension_call).
|
|
2
|
+
--
|
|
3
|
+
-- An extension's handlers live in <bridge folder>/extensions/<name>/handlers.lua (the server copies the file
|
|
4
|
+
-- there when it loads the extension) and return a table of command functions:
|
|
5
|
+
--
|
|
6
|
+
-- return { my_command = function(params, host) ... return data end } -- or: return nil, "refusal"
|
|
7
|
+
--
|
|
8
|
+
-- `host` = { reaper, plan, fxstate, json_decode }. Extensions are trusted code the user installed; this lib
|
|
9
|
+
-- only makes sure a command can name nothing outside its own file. Loaded by mcp_bridge.lua with dofile.
|
|
10
|
+
|
|
11
|
+
local M = {}
|
|
12
|
+
|
|
13
|
+
local cache = {} -- extension name -> { src, mod }: reloaded when the file's text changes
|
|
14
|
+
|
|
15
|
+
function M.call(api, params, ctx)
|
|
16
|
+
local name, command = params.extension, params.command
|
|
17
|
+
if type(name) ~= "string" or not name:match("^[a-z][a-z0-9%-]+$") or #name > 32 then
|
|
18
|
+
return nil, "invalid extension name"
|
|
19
|
+
end
|
|
20
|
+
if type(command) ~= "string" or not command:match("^[%a_][%w_]*$") then
|
|
21
|
+
return nil, "invalid command name"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
local path = ctx.dir .. name .. "/handlers.lua"
|
|
25
|
+
local src = ctx.read(path)
|
|
26
|
+
if not src then
|
|
27
|
+
return nil, "extension '" .. name .. "' has no Lua handlers installed; its manifest needs a \"lua\" file and the server must have loaded it"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
local entry = cache[name]
|
|
31
|
+
if not entry or entry.src ~= src then
|
|
32
|
+
local chunk, cerr = load(src, "@" .. path, "t")
|
|
33
|
+
if not chunk then return nil, "extension '" .. name .. "': handlers.lua does not compile: " .. tostring(cerr) end
|
|
34
|
+
local ok, mod = pcall(chunk)
|
|
35
|
+
if not ok then return nil, "extension '" .. name .. "': handlers.lua failed to load: " .. tostring(mod) end
|
|
36
|
+
if type(mod) ~= "table" then return nil, "extension '" .. name .. "': handlers.lua must return a table of commands" end
|
|
37
|
+
entry = { src = src, mod = mod }
|
|
38
|
+
cache[name] = entry
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
local fn = entry.mod[command]
|
|
42
|
+
if type(fn) ~= "function" then return nil, "extension '" .. name .. "' has no Lua command '" .. command .. "'" end
|
|
43
|
+
|
|
44
|
+
local args = {}
|
|
45
|
+
if type(params.paramsJson) == "string" and params.paramsJson ~= "" then
|
|
46
|
+
local decoded, derr = ctx.plan.json_decode(params.paramsJson)
|
|
47
|
+
if derr then return nil, "paramsJson is not valid JSON: " .. tostring(derr) end
|
|
48
|
+
args = type(decoded) == "table" and decoded or {}
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
-- A crash inside the handler is the bridge's "Handler error:", a system fault: it is not caught here.
|
|
52
|
+
return fn(args, { reaper = api, plan = ctx.plan, fxstate = ctx.fxstate, json_decode = ctx.plan.json_decode })
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
return M
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
-- mcp_fxstate_lib.lua — read and replace one VST3 plugin's saved state in a track chunk.
|
|
2
|
+
--
|
|
3
|
+
-- Pure logic: every function takes the ReaScript API as `api` and `ctx` = { plan, dir, read, write }, so it
|
|
4
|
+
-- runs under a fake API in unit tests. Loaded by mcp_bridge.lua with dofile.
|
|
5
|
+
--
|
|
6
|
+
-- The state is megabytes, so it never travels inside the command JSON: the caller names a file inside the
|
|
7
|
+
-- bridge's data folder and this lib reads or writes it (line 1: base64 header, line 2: base64 state).
|
|
8
|
+
--
|
|
9
|
+
-- Why a new FXID on write: with the same FXID REAPER keeps the loaded instance and ignores the new state
|
|
10
|
+
-- (verified on Vital, 2026-11-04). The VST3 length fields are the caller's job (libs/extension-api).
|
|
11
|
+
|
|
12
|
+
local M = {}
|
|
13
|
+
|
|
14
|
+
local WRAP = 280
|
|
15
|
+
local FX_TAGS = { VST = true, JS = true, AU = true, CLAP = true, DX = true, VIDEO_EFFECT = true }
|
|
16
|
+
|
|
17
|
+
local function split_lines(chunk)
|
|
18
|
+
local lines = {}
|
|
19
|
+
for line in (chunk .. "\n"):gmatch("(.-)\r?\n") do lines[#lines + 1] = line end
|
|
20
|
+
if lines[#lines] == "" then lines[#lines] = nil end
|
|
21
|
+
return lines
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
local function trim(s) return s:match("^%s*(.-)%s*$") end
|
|
25
|
+
|
|
26
|
+
local function norm(path) return (tostring(path):gsub("\\", "/")) end
|
|
27
|
+
|
|
28
|
+
--- A file this lib may touch: inside the bridge folder, named fxstate_<id>.in/.out, no "..".
|
|
29
|
+
local function allowed_file(path, dir)
|
|
30
|
+
if type(path) ~= "string" or type(dir) ~= "string" then return false end
|
|
31
|
+
local p, d = norm(path), norm(dir)
|
|
32
|
+
if d:sub(-1) ~= "/" then d = d .. "/" end
|
|
33
|
+
if p:sub(1, #d) ~= d or p:find("..", 1, true) then return false end
|
|
34
|
+
return p:sub(#d + 1):match("^fxstate_[%w%-]+%.%a+$") ~= nil
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
--- The FX of a track chunk in chain order: { open, close, tag, header, name, fxid_line } (1-based line numbers).
|
|
38
|
+
--- Returns list, lines, chain_end or nil, err.
|
|
39
|
+
local function list_fx(chunk, plan)
|
|
40
|
+
local lines = split_lines(chunk)
|
|
41
|
+
local cs, ce = plan.find_block(lines, "FXCHAIN")
|
|
42
|
+
if not cs then return nil, "the track has no FX chain" end
|
|
43
|
+
local fx, current, d = {}, nil, 1
|
|
44
|
+
for i = cs + 1, ce - 1 do
|
|
45
|
+
local t = trim(lines[i])
|
|
46
|
+
if t:sub(1, 1) == "<" then
|
|
47
|
+
if d == 1 then
|
|
48
|
+
local tag = t:match("^<(%u[%u_%d]*)")
|
|
49
|
+
if tag == "CONTAINER" then
|
|
50
|
+
return nil, "FX containers are not supported yet (this track's chain has one)"
|
|
51
|
+
end
|
|
52
|
+
current = nil
|
|
53
|
+
if FX_TAGS[tag] then
|
|
54
|
+
current = { open = i, tag = tag, header = t, name = t:match('^<%u[%u_%d]*%s+"([^"]*)"') or "" }
|
|
55
|
+
fx[#fx + 1] = current
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
d = d + 1
|
|
59
|
+
elseif t == ">" then
|
|
60
|
+
d = d - 1
|
|
61
|
+
if d == 1 and current and not current.close then current.close = i end
|
|
62
|
+
elseif d == 1 and t:match("^FXID%s") and #fx > 0 and not fx[#fx].fxid_line then
|
|
63
|
+
fx[#fx].fxid_line = i
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
return fx, lines, ce
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
local function locate(api, params, ctx)
|
|
70
|
+
local track_ref = params.trackIndex
|
|
71
|
+
if track_ref == nil then return nil, "trackIndex is required" end
|
|
72
|
+
local fx_index = tonumber(params.fxIndex)
|
|
73
|
+
if not fx_index or fx_index < 0 or fx_index ~= math.floor(fx_index) then
|
|
74
|
+
return nil, "fxIndex must be a whole number, 0 or more"
|
|
75
|
+
end
|
|
76
|
+
local r, err = ctx.plan.resolve_track(api, track_ref, {})
|
|
77
|
+
if not r then return nil, err end
|
|
78
|
+
local ok, chunk = api.GetTrackStateChunk(r.track, "", false)
|
|
79
|
+
if not ok or not chunk or chunk == "" then return nil, "could not read the track's state" end
|
|
80
|
+
local fx, lines = list_fx(chunk, ctx.plan)
|
|
81
|
+
if not fx then return nil, lines end
|
|
82
|
+
local target = fx[fx_index + 1]
|
|
83
|
+
if not target then return nil, "FX " .. fx_index .. " not found (the track has " .. #fx .. " FX)" end
|
|
84
|
+
if target.tag ~= "VST" or not target.name:match("^VST3") then
|
|
85
|
+
return nil, "FX " .. fx_index .. " (" .. target.name .. ") is not a VST3 plugin; only VST3 state is supported"
|
|
86
|
+
end
|
|
87
|
+
if not target.close then return nil, "FX " .. fx_index .. " has an unreadable state block" end
|
|
88
|
+
return { track = r.track, fx = target, lines = lines, index = fx_index }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
--- Read one VST3's state into params.outFile. Returns { name, format }.
|
|
92
|
+
function M.get_fx_state(api, params, ctx)
|
|
93
|
+
if not allowed_file(params.outFile, ctx.dir) then return nil, "outFile must be an fxstate_* file in the bridge folder" end
|
|
94
|
+
local loc, err = locate(api, params, ctx)
|
|
95
|
+
if not loc then return nil, err end
|
|
96
|
+
local data = {}
|
|
97
|
+
for i = loc.fx.open + 1, loc.fx.close - 1 do
|
|
98
|
+
local t = trim(loc.lines[i])
|
|
99
|
+
if t ~= "" then data[#data + 1] = t end
|
|
100
|
+
end
|
|
101
|
+
if #data < 2 then return nil, "FX " .. loc.index .. " has no saved state to read" end
|
|
102
|
+
local ok, werr = ctx.write(params.outFile, data[1] .. "\n" .. table.concat(data, "", 2) .. "\n")
|
|
103
|
+
if not ok then return nil, "could not write " .. tostring(params.outFile) .. ": " .. tostring(werr) end
|
|
104
|
+
return { name = loc.fx.name, format = "vst3" }
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
--- Replace one VST3's state with the file params.inFile and give it a new FXID so REAPER reloads it.
|
|
108
|
+
function M.set_fx_state(api, params, ctx)
|
|
109
|
+
if not allowed_file(params.inFile, ctx.dir) then return nil, "inFile must be an fxstate_* file in the bridge folder" end
|
|
110
|
+
local text = ctx.read(params.inFile)
|
|
111
|
+
if not text then return nil, "could not read " .. tostring(params.inFile) end
|
|
112
|
+
local header, state = text:match("^([^\n]+)\n([^\n]+)\n?")
|
|
113
|
+
local b64 = "^[A-Za-z0-9+/]+=?=?$"
|
|
114
|
+
if not header or not state or not header:match(b64) or not state:match(b64) then
|
|
115
|
+
return nil, "inFile must hold two base64 lines: the header, then the state"
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
local loc, err = locate(api, params, ctx)
|
|
119
|
+
if not loc then return nil, err end
|
|
120
|
+
if params.expectName and not loc.fx.name:find(tostring(params.expectName), 1, true) then
|
|
121
|
+
return nil, "FX " .. loc.index .. " is '" .. loc.fx.name .. "', not '" .. tostring(params.expectName) .. "'; nothing changed"
|
|
122
|
+
end
|
|
123
|
+
if not loc.fx.fxid_line then return nil, "FX " .. loc.index .. " has no FXID to renew; nothing changed" end
|
|
124
|
+
|
|
125
|
+
local out = {}
|
|
126
|
+
for i = 1, loc.fx.open do out[#out + 1] = loc.lines[i] end
|
|
127
|
+
out[#out + 1] = header
|
|
128
|
+
for at = 1, #state, WRAP do out[#out + 1] = state:sub(at, at + WRAP - 1) end
|
|
129
|
+
for i = loc.fx.close, #loc.lines do
|
|
130
|
+
if i == loc.fx.fxid_line then
|
|
131
|
+
out[#out + 1] = loc.lines[i]:match("^(%s*)") .. "FXID " .. api.genGuid("")
|
|
132
|
+
else
|
|
133
|
+
out[#out + 1] = loc.lines[i]
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
api.Undo_BeginBlock()
|
|
138
|
+
local ok = api.SetTrackStateChunk(loc.track, table.concat(out, "\n") .. "\n", false)
|
|
139
|
+
api.Undo_EndBlock("MCP: Set FX state (" .. loc.fx.name .. ")", -1)
|
|
140
|
+
if not ok then return nil, "REAPER refused the new track state" end
|
|
141
|
+
return { name = loc.fx.name, fxIndex = loc.index }
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
return M
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
desc:MCP Gain Trace (pre/post ducking meter)
|
|
2
|
+
// Measures how much a chain of processors moves the level over time, live during playback.
|
|
3
|
+
// Put one instance in mode "pre" at the start of a chain and one in mode "post" at the end (same slot).
|
|
4
|
+
// Every 5 ms the post instance compares its 30 ms RMS with the pre RMS (post - pre, dB), at the
|
|
5
|
+
// lag where they line up best (plugin latency in between), and reports percentiles relative to the
|
|
6
|
+
// median gain: p5 / p1 = how deep the dips go, std = how much the gain moves.
|
|
7
|
+
// Stats reset when playback starts or Reset changes. Audio passes through unmodified.
|
|
8
|
+
// Everything runs in @sample. EEL2 has no scientific-notation literals (1e9 fails to compile).
|
|
9
|
+
|
|
10
|
+
options:gmem=MCPGainTrace
|
|
11
|
+
|
|
12
|
+
slider1:0<0,1,1{pre,post}>Mode
|
|
13
|
+
slider2:0<0,7,1>Slot
|
|
14
|
+
slider3:-45<-80,-20,1>Gate (dBFS, pre)
|
|
15
|
+
slider4:0<-40,10,0.01>p5 (dB, rel. median)
|
|
16
|
+
slider5:0<-40,10,0.01>p1 (dB, rel. median)
|
|
17
|
+
slider6:0<0,20,0.01>Std (dB)
|
|
18
|
+
slider7:0<-40,40,0.01>Median gain (dB)
|
|
19
|
+
slider8:0<0,100000,0.1>Gated time (s)
|
|
20
|
+
slider9:0<0,100,0.1>Lag (ms)
|
|
21
|
+
slider10:0<0,1,1>Reset
|
|
22
|
+
|
|
23
|
+
in_pin:left input
|
|
24
|
+
in_pin:right input
|
|
25
|
+
out_pin:left output
|
|
26
|
+
out_pin:right output
|
|
27
|
+
|
|
28
|
+
@init
|
|
29
|
+
RING = 4096; // windows kept per slot
|
|
30
|
+
LAGS = 16; // lags tried, in windows
|
|
31
|
+
BINS = 600; // histogram bins: -40..+20 dB, 0.1 dB
|
|
32
|
+
BINLO = -40;
|
|
33
|
+
hist = 0; // LAGS * BINS
|
|
34
|
+
sums = hist + LAGS * BINS; // per lag: n, sum, sumsq
|
|
35
|
+
ph = sums + LAGS * 3; // post window energies, ring of 64
|
|
36
|
+
AVG = 6; // gain is compared over AVG windows (30 ms RMS), so waveform phase doesn't count
|
|
37
|
+
win = floor(srate * 0.005);
|
|
38
|
+
acc = 0; total = 0; seen = -1;
|
|
39
|
+
was_playing = 0;
|
|
40
|
+
last_reset = slider10;
|
|
41
|
+
nwin = 0;
|
|
42
|
+
|
|
43
|
+
function reset_stats() local(i) (
|
|
44
|
+
i = 0; loop(LAGS * BINS + LAGS * 3 + 64, hist[i] = 0; i += 1);
|
|
45
|
+
slider4 = slider5 = slider6 = slider7 = slider8 = slider9 = 0;
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
function update_stats() local(l, s, sd, best, bstd, n, h, c, b, q1, q5, q50) (
|
|
49
|
+
best = -1; bstd = 1000000000; l = 0;
|
|
50
|
+
loop(LAGS,
|
|
51
|
+
s = sums + l * 3;
|
|
52
|
+
s[0] > 20 ? (
|
|
53
|
+
sd = sqrt(max(0, s[2] / s[0] - sqr(s[1] / s[0])));
|
|
54
|
+
sd < bstd ? ( bstd = sd; best = l; );
|
|
55
|
+
);
|
|
56
|
+
l += 1;
|
|
57
|
+
);
|
|
58
|
+
best >= 0 ? (
|
|
59
|
+
s = sums + best * 3; n = s[0]; h = hist + best * BINS;
|
|
60
|
+
c = 0; b = 0; q1 = q5 = q50 = -1;
|
|
61
|
+
loop(BINS,
|
|
62
|
+
c += h[b];
|
|
63
|
+
q1 < 0 && c >= n * 0.01 ? q1 = b;
|
|
64
|
+
q5 < 0 && c >= n * 0.05 ? q5 = b;
|
|
65
|
+
q50 < 0 && c >= n * 0.5 ? q50 = b;
|
|
66
|
+
b += 1;
|
|
67
|
+
);
|
|
68
|
+
slider4 = (q5 - q50) / 10;
|
|
69
|
+
slider5 = (q1 - q50) / 10;
|
|
70
|
+
slider6 = bstd;
|
|
71
|
+
slider7 = BINLO + (q50 + 0.5) / 10;
|
|
72
|
+
slider8 = n * win / srate;
|
|
73
|
+
slider9 = best * win / srate * 1000;
|
|
74
|
+
);
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
reset_stats();
|
|
78
|
+
|
|
79
|
+
@slider
|
|
80
|
+
slider10 != last_reset ? ( last_reset = slider10; reset_stats(); );
|
|
81
|
+
|
|
82
|
+
@sample
|
|
83
|
+
base = 16 + slider2 * (RING + 16);
|
|
84
|
+
playing = play_state & 1;
|
|
85
|
+
slider1 == 1 && playing && !was_playing ? reset_stats();
|
|
86
|
+
was_playing = playing;
|
|
87
|
+
|
|
88
|
+
slider1 == 0 ? (
|
|
89
|
+
total += 1;
|
|
90
|
+
gmem[base - 16] = total;
|
|
91
|
+
) : (
|
|
92
|
+
// pre ran its whole block before this one: when its count moves, this block starts at count - samplesblock
|
|
93
|
+
gmem[base - 16] != seen ? ( seen = gmem[base - 16]; total = seen - samplesblock; acc = 0; );
|
|
94
|
+
total += 1;
|
|
95
|
+
);
|
|
96
|
+
acc += spl0 * spl0 + spl1 * spl1;
|
|
97
|
+
|
|
98
|
+
total % win == 0 ? (
|
|
99
|
+
k = total / win - 1;
|
|
100
|
+
e = acc / win;
|
|
101
|
+
slider1 == 0 ? (
|
|
102
|
+
gmem[base + (k % RING)] = e;
|
|
103
|
+
) : playing ? (
|
|
104
|
+
ph[k % 64] = e;
|
|
105
|
+
E = 0; j = 0; loop(AVG, E += ph[(k - j + 64) % 64]; j += 1);
|
|
106
|
+
E /= AVG;
|
|
107
|
+
l = 0;
|
|
108
|
+
loop(LAGS,
|
|
109
|
+
kk = k - l;
|
|
110
|
+
p = 0; j = 0;
|
|
111
|
+
loop(AVG, p += (kk - j >= 0 ? gmem[base + ((kk - j) % RING)] : 0); j += 1);
|
|
112
|
+
p /= AVG;
|
|
113
|
+
p > 0 && E > 0 && 10 * log10(p) > slider3 ? (
|
|
114
|
+
g = 10 * log10(E / p);
|
|
115
|
+
b = min(BINS - 1, max(0, floor((g - BINLO) * 10)));
|
|
116
|
+
hist[l * BINS + b] += 1;
|
|
117
|
+
s = sums + l * 3;
|
|
118
|
+
s[0] += 1; s[1] += g; s[2] += g * g;
|
|
119
|
+
);
|
|
120
|
+
l += 1;
|
|
121
|
+
);
|
|
122
|
+
nwin += 1;
|
|
123
|
+
nwin % 100 == 0 ? update_stats();
|
|
124
|
+
);
|
|
125
|
+
acc = 0;
|
|
126
|
+
);
|