#pragma once // bank_grid — the REAPER-free layout math and cache-key logic behind the docked // bank_panel (M5, Wave A). The panel shell (bank_panel.cpp) owns the SWELL window, // LICE drawing, and PCM reads; ALL of that is REAPER-bound and DAW-verified. What // is NOT DAW-bound — how N sample cells tile a panel of a given pixel size, and // the key that identifies a cached thumbnail — lives here so it is unit-tested // outside the DAW (CLAUDE.md §load-bearing split). // // PURE MODULE: NO REAPER types, NO SWELL, NO vendor/ includes. Standard library // only. Builds and unit-tests without REAPER. #include #include #include #include namespace reasampler { // A single cell's pixel rectangle within the panel, top-left origin (SWELL/LICE // convention). (x, y) is the top-left corner; width/height are the cell extents. // These are the draw bounds for one sample's thumbnail; the panel draws its // waveform envelope inside this rect (minus any internal padding it applies). struct CellRect { int x = 0; int y = 0; int width = 0; int height = 0; bool operator==(const CellRect& o) const { return x == o.x && y == o.y && width == o.width && height == o.height; } }; // Fixed inputs that shape the grid. All in pixels. cellWidth/cellHeight are the // TARGET cell size; the layout fits as many whole columns as the panel width // allows (>= 1) and wraps to as many rows as N requires. gap is the pixel spacing // between adjacent cells (and the outer margin), so cells never touch. struct GridSpec { int cellWidth = 120; int cellHeight = 72; int gap = 8; }; // Computes the number of columns that fit in a panel of the given pixel width for // the spec. Always >= 1 (a panel narrower than one cell still shows one column, // clipped by the window). Pure arithmetic — the panel passes its live client // width here and to computeCellRects. int columnsForWidth(int panelWidth, const GridSpec& spec); // Tiles `itemCount` cells left-to-right, top-to-bottom into a panel of the given // pixel width, honoring the spec's cell size and gap. Returns exactly itemCount // rects in item order (rect i is sample i). A partial last row is left-aligned // and simply shorter — no centering, no stretching. itemCount == 0 -> empty. // panelWidth is used only to derive the column count; the returned rects may // extend below any fixed viewport height (the panel scrolls/clips in Wave B). std::vector computeCellRects(int itemCount, int panelWidth, const GridSpec& spec); // The total pixel height the grid occupies for itemCount cells at the given panel // width and spec (top margin + rows*cellHeight + inter-row gaps + bottom margin). // 0 when itemCount == 0. The panel uses this to know its full content height // (scroll extent in Wave B; for Wave A it sizes the empty-vs-populated decision). int contentHeight(int itemCount, int panelWidth, const GridSpec& spec); // Identifies one cached thumbnail. A cached envelope is valid only while the // sample's identity, the draw width it was computed at, and the bank generation // it was computed under all match. Width is part of the key because the envelope // has exactly `width` bins per channel (peaks::computeEnvelope is width-driven); // a resized panel needs a fresh envelope. Generation lets the panel invalidate // every entry when the bank changes (capture / project load) without diffing. struct ThumbnailKey { std::string sampleId; int width = 0; std::uint64_t generation = 0; bool operator==(const ThumbnailKey& o) const { return sampleId == o.sampleId && width == o.width && generation == o.generation; } }; // A stable string form of the key, suitable as a map key. Deterministic: the same // key always yields the same string, distinct keys always differ (the sampleId is // length-prefixed so an id containing the delimiter cannot collide with another). std::string thumbnailKeyString(const ThumbnailKey& key); } // namespace reasampler