Ω-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
+30 -12
View File
@@ -36,12 +36,25 @@ std::string tickLabel(int db) {
return std::string(buf);
}
// The MASTER column: dB scale in the label gutter, one or two bars, the held peak tick, and
// the latched clip cap. `split` is the RESOLVED lane decision — see master_meter.h.
void paintMeterColumn(LICE_IBitmap* bmp, const Rect& column, const MasterMeterUi& state,
LaneSplit split) {
if (column.width <= 0 || column.height <= 0) return;
const MeterRects m = meterRects(column, split);
// The numeral gutter, left of the bars: static content, so it is drawn on a full paint only —
// re-blending AA text onto itself every meter frame would thicken it.
void drawMeterNumerals(LICE_IBitmap* bmp, const MeterRects& m) {
if (m.field.empty()) return;
for (int db = static_cast<int>(instrument::engine::kMeterTopDb);
db >= static_cast<int>(instrument::engine::kMeterFloorDb);
db -= static_cast<int>(kMeterTickStepDb)) {
if (!meterTickNumeralled(db)) continue;
kitText(bmp, meterNumeralRect(m.labels, meterDbToY(m.field, db)), tickLabel(db).c_str(),
Font::Micro, Role::TextDim, Align::Right);
}
}
// The bar field: the dB rules, one or two bars, the held peak tick and the latched clip cap —
// everything in the column that moves at meter rate. SELF-CONTAINED on purpose: the BgCell fill
// covers every pixel the rest of it then draws, so a meter frame can redraw this rect alone.
// Two bars or one is read off the rects (barB is empty exactly when the split is Single), so
// the lane decision has one representation here rather than two.
void drawMeterField(LICE_IBitmap* bmp, const MeterRects& m, const MasterMeterUi& state) {
// A column narrower than the interior needs yields all-empty rects, which under rect.h's
// contract means suppressed — not a zero-height field to fill, tick twelve times and cap.
if (m.field.empty()) return;
@@ -57,10 +70,6 @@ void paintMeterColumn(LICE_IBitmap* bmp, const Rect& column, const MasterMeterUi
const bool zero = (db == 0);
LICE_FillRect(bmp, m.field.x, y, m.field.width, zero ? 2 : 1,
zero ? toLice(roleColor(Role::TextDim)) : hairline, 1.0f, 0);
if (meterTickNumeralled(db)) {
kitText(bmp, meterNumeralRect(m.labels, y), tickLabel(db).c_str(), Font::Micro,
Role::TextDim, Align::Right);
}
}
// The bars. A single-lane surface shows ONE bar folding both channels per field
@@ -80,7 +89,7 @@ void paintMeterColumn(LICE_IBitmap* bmp, const Rect& column, const MasterMeterUi
LICE_FillRect(bmp, bar.x, hold, bar.width, 2, holdInk, 1.0f, 0);
}
};
if (split == LaneSplit::Single) {
if (m.barB.empty()) {
drawBar(m.barA, meterSingleLaneState(state));
} else {
drawBar(m.barA, state.left);
@@ -96,6 +105,10 @@ void paintMeterColumn(LICE_IBitmap* bmp, const Rect& column, const MasterMeterUi
} // namespace
void ReaSamplerEditor::paintMeterField(LICE_IBitmap* bmp) {
drawMeterField(bmp, meterRects_, masterMeter_);
}
void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
const Rect& deckArea = fl.bands.decks;
if (deckArea.width <= 0 || deckArea.height <= 0) return;
@@ -279,7 +292,12 @@ void ReaSamplerEditor::paintDeck(LICE_IBitmap* bmp, const FaceLayout& fl) {
monoTrigger_ == MonoTrigger::Legato, !isMono);
}
if (g.column.id >= 0) paintMeterColumn(bmp, g.column.box, masterMeter_, meterSplit);
if (g.column.id >= 0) {
// Cached so the meter's own tick repaints the field without re-laying out the deck.
meterRects_ = meterRects(g.column.box, meterSplit);
drawMeterNumerals(bmp, meterRects_);
drawMeterField(bmp, meterRects_, masterMeter_);
}
// The knobs. A dependent group's knobs draw Disabled (not hidden) — stable geometry.
// The predicate is the input side's, so the drawn state and the inert grab agree.