diff --git a/src/shell/panel/panel_input.cpp b/src/shell/panel/panel_input.cpp index 7eb5b79..5941b87 100644 --- a/src/shell/panel/panel_input.cpp +++ b/src/shell/panel/panel_input.cpp @@ -135,8 +135,9 @@ void enumerateLiveGuids(ReaProject* proj, std::set& allGuids, } // One detection tick: REAPER exposes no "item/track added" callback, so this diffs live -// GUIDs against the baseline and auto-tags the new ones into the active mode. Runs every -// timer tick regardless of panel open/close. READ-ONLY on the project; mutates only the +// GUIDs against the baseline and auto-tags the new ones into the active mode. Called every +// timer tick regardless of panel open/close, but the enumeration itself is throttled to +// kDetectIntervalMs (see the gate below). READ-ONLY on the project; mutates only the // in-memory membership index — deliberately OUTSIDE any Undo block (auto-tag is a // background metadata update, not a destructive edit; an Undo block here would flood // REAPER's history with an entry per tick that sees new content). @@ -146,6 +147,14 @@ void enumerateLiveGuids(ReaProject* proj, std::set& allGuids, bool detectNewContent() { if (!g_panel.session) return false; + // Throttles the enumeration (O(T+I) REAPER calls + allocations) to kDetectIntervalMs, + // independent of how often the caller ticks. A skipped tick leaves reloadPending/the + // baseline untouched, so the guards below still run before the NEXT diff whenever this + // gate next opens — only the diff's cadence changes, not its correctness. + const unsigned int now = GetTickCount(); + if (now - g_panel.lastDetectTick < kDetectIntervalMs) return false; + g_panel.lastDetectTick = now; + ReaProject* proj = EnumProjects(-1, nullptr, 0); // A project (re)load re-arms the first-poll guard so we never diff across two @@ -538,17 +547,19 @@ void unregisterAccel() { namespace reasampler { void bankPanelNotifyProjectLoaded() { - // Arms the new-content detector to re-baseline on its next tick so the just-loaded - // project's pre-existing content is the baseline (nothing new) rather than diffed - // against the previous project and mass-tagged. A flag, not an inline reset, because - // detectNewContent owns the baseline and runs later in the SAME OnTimer tick. + // Arms the new-content detector to re-baseline on its next ENUMERATING tick (the flag + // persists across any throttled/skipped ticks in between) so the just-loaded project's + // pre-existing content is the baseline (nothing new) rather than diffed against the + // previous project and mass-tagged. A flag, not an inline reset, because detectNewContent + // owns the baseline and drains this before its own diff. panel::g_panel.reloadPending = true; } void bankPanelRefresh() { - // New-content auto-tag detection runs EVERY tick regardless of panel open/close: - // tracks/items are created in the arrange view, not the panel. READ-ONLY on the - // project; only mutates the in-memory membership index. + // New-content auto-tag detection is CALLED every tick regardless of panel open/close + // (tracks/items are created in the arrange view, not the panel), but the enumeration + // it drives is throttled — see kDetectIntervalMs. READ-ONLY on the project; only + // mutates the in-memory membership index. const bool tagged = panel::detectNewContent(); // Lane minting runs ONLY when detection just tagged new content — a track can only diff --git a/src/shell/panel/panel_state.h b/src/shell/panel/panel_state.h index 7abebf8..9d046ba 100644 --- a/src/shell/panel/panel_state.h +++ b/src/shell/panel/panel_state.h @@ -206,6 +206,13 @@ inline constexpr unsigned int kTooltipDelayMs = 500; inline constexpr int kTooltipCharPx = 7; inline constexpr int kTooltipTextH = 14; +// New-content detection's enumeration cadence — matches the VST3 side's own +// kSyncTimerIntervalMs (editor_platform.cpp), this codebase's established interval for a +// background poll nothing visible depends on. Auto-tag is an invisible metadata update +// (see detectNewContent), so a 15x cadence cut (30/s -> 2/s) costs latency no one watches +// for, not correctness. +inline constexpr unsigned int kDetectIntervalMs = 500; + // Client area top to bottom: top toolbar | split body | bottom toolbar | footer. // 26, not 24: Font::RegionTitle's line box (19px em + Segoe UI's leading) is ~25px, and // DT_VCENTER clips to the rect. @@ -345,12 +352,18 @@ struct PanelState { bool previewActive = false; bool previewInited = false; // guards double init / deinit - // Each timer tick diffs the live track+item GUID set against the previous tick to - // auto-tag new content. GuidBaseline self-arms on first observe() so pre-existing - // content is never mass-tagged. Project-load re-arm is driven by persist's load - // signal, not a ReaProject* compare — a recycled address previously mis-tagged tracks. + // Each enumerating tick (kDetectIntervalMs-throttled) diffs the live track+item GUID + // set against the prior enumeration to auto-tag new content. GuidBaseline self-arms on + // first observe() so pre-existing content is never mass-tagged. Project-load re-arm is + // driven by persist's load signal, not a ReaProject* compare — a recycled address + // previously mis-tagged tracks. GuidBaseline contentBaseline; bool reloadPending = false; // set by bankPanelNotifyProjectLoaded; drained next detect tick + + // Throttles enumerateLiveGuids to kDetectIntervalMs regardless of how often + // bankPanelRefresh itself is called (the OnTimer poll, ~30/s, plus a handful of + // post-capture/ingest/bake call sites). 0 forces the very first tick through. + unsigned int lastDetectTick = 0; }; // Defined in panel_window.cpp (the lifecycle owner).