Ω-W1-T3: run the master meter on its own 60 FPS timer, and make a meter frame cost one rect

This commit is contained in:
2026-08-03 12:39:36 -04:00
parent 0eb2c67875
commit 4320bfdb08
7 changed files with 248 additions and 88 deletions
+34 -2
View File
@@ -31,6 +31,15 @@ constexpr const wchar_t* kChildClassName = L"ReaSampler9000VstEditor";
// a per-window SetTimer id (any nonzero).
constexpr UINT_PTR kSyncTimerId = 1;
constexpr UINT kSyncTimerIntervalMs = 500;
// The meter's own clock on the same child window, because the bus meter is the one surface
// whose value changes every block. Raising the poll above to frame rate instead is the REJECTED
// alternative: its body costs a bridge read plus a bank parse, and its two tick-counted banners
// (the bake message, the drop hint) are calibrated in ticks, so they would silently shorten by
// the same factor. 16 ms is 60 FPS; what a frame costs is the meter's bar field, not the client
// area — the whole-client invalidate is what made this rate unaffordable before.
constexpr UINT_PTR kMeterTimerId = 2;
constexpr UINT kMeterTimerIntervalMs = 16;
} // namespace
#endif
@@ -66,6 +75,17 @@ void ReaSamplerEditor::invalidate() {
if (childHwnd_) InvalidateRect(childHwnd_, nullptr, FALSE);
}
void ReaSamplerEditor::invalidateMeter() {
if (!childHwnd_) return;
const Rect& f = meterRects_.field;
if (f.empty()) { // nothing cached (see meterRects_) — the whole face has to answer
invalidate();
return;
}
RECT r{f.x, f.y, f.right(), f.bottom()};
InvalidateRect(childHwnd_, &r, FALSE);
}
void ReaSamplerEditor::attachedToParent() {
HWND parent = static_cast<HWND>(systemWindow);
if (!parent) return;
@@ -111,6 +131,9 @@ void ReaSamplerEditor::attachedToParent() {
// created here, killed in removedFromParent — so an instance whose editor is closed
// does not poll.
SetTimer(childHwnd_, kSyncTimerId, kSyncTimerIntervalMs, nullptr);
// Bound to the same window for the same reason: an instance with no editor open runs
// neither clock, and the meter's accumulators simply pile up until one opens.
SetTimer(childHwnd_, kMeterTimerId, kMeterTimerIntervalMs, nullptr);
// Poll once immediately so a pending assignment (an ingest fired while this editor was
// closed) or a bank change applies the instant the editor opens, rather than waiting up
// to one timer interval. refreshFromBank above already primed the view; this folds in
@@ -127,9 +150,11 @@ void ReaSamplerEditor::removedFromParent() {
if (processor_) processor_->endParamGesture();
if (childHwnd_) {
KillTimer(childHwnd_, kSyncTimerId); // stop the poll before the window goes away
KillTimer(childHwnd_, kMeterTimerId);
DestroyWindow(childHwnd_);
childHwnd_ = nullptr;
}
releaseBackBuffer(); // a client-area bitmap outlives nothing here
}
tresult PLUGIN_API ReaSamplerEditor::onSize(ViewRect* newSize) {
@@ -137,6 +162,10 @@ tresult PLUGIN_API ReaSamplerEditor::onSize(ViewRect* newSize) {
if (childHwnd_ && newSize) {
MoveWindow(childHwnd_, 0, 0, newSize->getWidth(), newSize->getHeight(), TRUE);
thumbCache_.clear(); // thumbnails are width-bound; a resize invalidates them
ensureBackBuffer(newSize->getWidth(), newSize->getHeight());
// The cached meter rects name the OLD layout; drop them so the meter tick takes the
// whole-client path until the resize's own full paint resolves them again.
meterRects_ = {};
}
return res;
}
@@ -149,7 +178,7 @@ LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam,
case WM_PAINT: {
PAINTSTRUCT ps{};
HDC hdc = BeginPaint(hwnd, &ps);
if (self) self->paint(hdc);
if (self) self->paint(hdc, ps.rcPaint);
EndPaint(hwnd, &ps);
return 0;
}
@@ -293,7 +322,10 @@ LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam,
return 0;
}
case WM_TIMER:
if (self && wParam == kSyncTimerId) self->onSyncTimer();
if (self) {
if (wParam == kSyncTimerId) self->onSyncTimer();
else if (wParam == kMeterTimerId) self->onMeterTimer();
}
return 0;
case WM_ERASEBKGND:
return 1; // fully repaint in WM_PAINT; skip the flicker-inducing erase