feat(bank_panel): docked bank grid with LICE waveform thumbnails (M5 Wave A)

Docked SWELL window toggled by a new action, drawing the current bank as
per-sample min/max thumbnails (PCM_source + peaks) with an in-memory cache.
Pure grid-layout/cache-key math in bank_grid (tested). Renames peaks::Sample
-> AudioSample to avoid colliding with bank_model::Sample.
This commit is contained in:
2026-07-22 21:02:22 -04:00
parent ec211a5620
commit 71a3b26a47
12 changed files with 936 additions and 33 deletions
+65
View File
@@ -0,0 +1,65 @@
// bank_grid — pure implementation. See bank_grid.h. NO REAPER / SWELL / vendor.
#include "bank_grid.h"
namespace reasampler {
int columnsForWidth(int panelWidth, const GridSpec& spec) {
// Layout: [gap][cell][gap][cell]...[cell][gap]. n cells occupy
// gap + n*(cellWidth + gap). Solve for the largest n that fits panelWidth,
// clamped to at least 1 so a too-narrow panel still shows a (clipped) column.
const int cell = spec.cellWidth + spec.gap;
if (cell <= 0) return 1; // degenerate spec — one column, avoid divide-by-zero
const int usable = panelWidth - spec.gap;
if (usable < spec.cellWidth) return 1;
const int cols = usable / cell;
return cols < 1 ? 1 : cols;
}
std::vector<CellRect> computeCellRects(int itemCount,
int panelWidth,
const GridSpec& spec) {
std::vector<CellRect> rects;
if (itemCount <= 0) return rects;
const int cols = columnsForWidth(panelWidth, spec);
rects.reserve(static_cast<std::size_t>(itemCount));
for (int i = 0; i < itemCount; ++i) {
const int col = i % cols;
const int row = i / cols;
CellRect r;
r.x = spec.gap + col * (spec.cellWidth + spec.gap);
r.y = spec.gap + row * (spec.cellHeight + spec.gap);
r.width = spec.cellWidth;
r.height = spec.cellHeight;
rects.push_back(r);
}
return rects;
}
int contentHeight(int itemCount, int panelWidth, const GridSpec& spec) {
if (itemCount <= 0) return 0;
const int cols = columnsForWidth(panelWidth, spec);
// Ceil-divide item count by columns to get the row count (partial last row
// still occupies a full row of height).
const int rows = (itemCount + cols - 1) / cols;
return spec.gap + rows * (spec.cellHeight + spec.gap);
}
std::string thumbnailKeyString(const ThumbnailKey& key) {
// Length-prefix the sampleId so a delimiter byte inside an id cannot forge a
// collision with a different (id, width, generation) triple.
std::string s;
s.reserve(key.sampleId.size() + 32);
s += std::to_string(key.sampleId.size());
s += ':';
s += key.sampleId;
s += '|';
s += std::to_string(key.width);
s += '|';
s += std::to_string(key.generation);
return s;
}
} // namespace reasampler