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:
@@ -0,0 +1,88 @@
|
||||
#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 <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<CellRect> 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
|
||||
Reference in New Issue
Block a user