fix(draw_kit): reclaim 4x waveform oversample; relocate waveformColumnCount to component_geometry
kWaveformOversample set to 1 (was 4) — overbinning produced byte-identical pixels because columnMinMax exact partition already makes the draw gap-free. Comments credit columnMinMax, not oversampling.
This commit is contained in:
+6
-6
@@ -13,8 +13,8 @@
|
|||||||
// LICE-drawn named-banks tab-page region below (one tab per named bank, an
|
// LICE-drawn named-banks tab-page region below (one tab per named bank, an
|
||||||
// overflow/scroll strip), and two full-height toggles that collapse the split.
|
// overflow/scroll strip), and two full-height toggles that collapse the split.
|
||||||
// Each region reuses the M5 grid render loop (waveform thumbnails / empty state).
|
// Each region reuses the M5 grid render loop (waveform thumbnails / empty state).
|
||||||
// * per-sample PCM read via PCM_source fed to peaks::computeEnvelope, oversampled
|
// * per-sample PCM read via PCM_source fed to peaks::computeEnvelope, one bin per
|
||||||
// (kWaveformOversample bins per drawn pixel column) for the FA3 gap-free draw.
|
// drawn pixel column; drawWaveform's gap-free render comes from peaks::columnMinMax.
|
||||||
// * an in-memory thumbnail cache keyed by (sample id, draw width, bank generation).
|
// * an in-memory thumbnail cache keyed by (sample id, draw width, bank generation).
|
||||||
// * id-keyed bank management (create / rename / delete / evacuate / activate) and
|
// * id-keyed bank management (create / rename / delete / evacuate / activate) and
|
||||||
// sample move/copy — driven from a tab context menu and a drag — against the B1
|
// sample move/copy — driven from a tab context menu and a drag — against the B1
|
||||||
@@ -1350,10 +1350,10 @@ void drawRegionGrid(LICE_IBitmap* bmp, const RECT& region, bool isBanks,
|
|||||||
// BankIndex insertion order. Selection/focus are keyed by the occupied-ordinal (selection
|
// BankIndex insertion order. Selection/focus are keyed by the occupied-ordinal (selection
|
||||||
// space); a slot maps back to its ordinal via selectionForSlot.
|
// space); a slot maps back to its ordinal via selectionForSlot.
|
||||||
const RegionDisplay disp = regionDisplay(region, isBanks, reg);
|
const RegionDisplay disp = regionDisplay(region, isBanks, reg);
|
||||||
// FA3 anti-alias: thumbnails are computed OVERSAMPLED — kWaveformOversample bins per
|
// FA3 gap-free: request one bin per drawn pixel column; drawWaveform's
|
||||||
// drawn pixel column — and drawWaveform collapses them per column (peaks::columnMinMax)
|
// peaks::columnMinMax exact partition makes every column gap-free — overbinning
|
||||||
// so steep transients render as true full-height spans. computeThumbnail clamps the
|
// produces byte-identical pixels at higher memory/CPU cost. computeThumbnail clamps
|
||||||
// request to the frame count.
|
// the request to the frame count.
|
||||||
const int binWidth = kWaveformOversample *
|
const int binWidth = kWaveformOversample *
|
||||||
waveformColumnCount(KitBox{0, 0, kGrid.cellWidth, kGrid.cellHeight});
|
waveformColumnCount(KitBox{0, 0, kGrid.cellWidth, kGrid.cellHeight});
|
||||||
for (const SlotCellRect& r : disp.slotRects) {
|
for (const SlotCellRect& r : disp.slotRects) {
|
||||||
|
|||||||
@@ -100,4 +100,9 @@ int hitTestListRow(int px, int py, const KitBox& list, int rowHeight, int rowCou
|
|||||||
return row;
|
return row;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int waveformColumnCount(const KitBox& box) {
|
||||||
|
const int w = box.width - 4; // fixed 2px inset each side (matches drawWaveform)
|
||||||
|
return w > 0 ? w : 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
@@ -126,4 +126,14 @@ ListRowBox computeListRow(const KitBox& list, int index, int rowHeight);
|
|||||||
// a phantom row. Half-open bounds match computeListRow so the hit maps to the drawn row.
|
// a phantom row. Half-open bounds match computeListRow so the hit maps to the drawn row.
|
||||||
int hitTestListRow(int px, int py, const KitBox& list, int rowHeight, int rowCount);
|
int hitTestListRow(int px, int py, const KitBox& list, int rowHeight, int rowCount);
|
||||||
|
|
||||||
|
// --- Waveform column count ---------------------------------------------------
|
||||||
|
//
|
||||||
|
// The number of pixel columns drawWaveform renders inside `box` (its fixed 2px side
|
||||||
|
// insets), never negative. Callers pass this count directly as the `binCount` argument to
|
||||||
|
// peaks::computeEnvelope — one bin per column is the correct resolution, and
|
||||||
|
// peaks::columnMinMax's exact partition makes the render gap-free at any bins-to-pixels
|
||||||
|
// ratio. Overbinning does NOT improve render quality (columnMinMax's frame union is
|
||||||
|
// identical whether bins == columns or bins == k*columns) and wastes memory and CPU.
|
||||||
|
int waveformColumnCount(const KitBox& box);
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
+6
-10
@@ -277,11 +277,6 @@ void drawListRow(LICE_IBitmap* bmp, const ListRowBox& row, const char* label,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int waveformColumnCount(const KitBox& box) {
|
|
||||||
const int w = box.width - 4; // fixed 2px inset each side (matches drawWaveform below)
|
|
||||||
return w > 0 ? w : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void drawWaveform(LICE_IBitmap* bmp, const KitBox& box, const Envelope& env) {
|
void drawWaveform(LICE_IBitmap* bmp, const KitBox& box, const Envelope& env) {
|
||||||
if (!bmp || box.empty()) return;
|
if (!bmp || box.empty()) return;
|
||||||
|
|
||||||
@@ -311,11 +306,12 @@ void drawWaveform(LICE_IBitmap* bmp, const KitBox& box, const Envelope& env) {
|
|||||||
if (bins.empty() || innerW <= 0) continue;
|
if (bins.empty() || innerW <= 0) continue;
|
||||||
|
|
||||||
// Render one filled vertical span per pixel column. peaks::columnMinMax merges
|
// Render one filled vertical span per pixel column. peaks::columnMinMax merges
|
||||||
// all bins that project to column `col` under the same partition as
|
// all bins that project to column `col` under the exact same partition as
|
||||||
// computeEnvelope, so every pixel column is covered with no gaps regardless of
|
// computeEnvelope used to build the envelope, so every pixel column is covered
|
||||||
// the bins-to-pixels ratio — callers oversample (kWaveformOversample bins per
|
// with no gaps regardless of the bins-to-pixels ratio. With one bin per column
|
||||||
// column) so each span shows true extremes. Same dB display compression
|
// (kWaveformOversample == 1) each span covers the true min/max of exactly the
|
||||||
// everywhere (bank_grid, pure).
|
// frames that fall in that column. Same dB display compression everywhere
|
||||||
|
// (bank_grid, pure).
|
||||||
for (int col = 0; col < innerW; ++col) {
|
for (int col = 0; col < innerW; ++col) {
|
||||||
const MinMax mm = columnMinMax(bins, innerW, col);
|
const MinMax mm = columnMinMax(bins, innerW, col);
|
||||||
const int x = box.x + 2 + col;
|
const int x = box.x + 2 + col;
|
||||||
|
|||||||
+17
-18
@@ -115,29 +115,28 @@ void drawSlider(LICE_IBitmap* bmp, const SliderGeometry& geom, InteractionState
|
|||||||
void drawListRow(LICE_IBitmap* bmp, const ListRowBox& row, const char* label,
|
void drawListRow(LICE_IBitmap* bmp, const ListRowBox& row, const char* label,
|
||||||
int thumbWidth, InteractionState state);
|
int thumbWidth, InteractionState state);
|
||||||
|
|
||||||
// The number of pixel columns drawWaveform renders inside `box` (its fixed 2px side
|
// waveformColumnCount — declared in component_geometry.h (already included above). Returns
|
||||||
// insets), never negative. Callers size the envelope they compute from this: request
|
// the drawable column count inside `box` (box.width minus the fixed 2px insets each side).
|
||||||
// `kWaveformOversample * waveformColumnCount(box)` bins (clamped to the frame count) and
|
// Callers pass this value directly as the `binCount` argument to peaks::computeEnvelope;
|
||||||
// drawWaveform collapses them per column — the anti-aliasing lever. Requesting fewer bins
|
// overbinning (more bins than columns) costs memory and CPU without changing a rendered
|
||||||
// than columns still renders gap-free (the enclosing bin fills each column) but at the
|
// pixel — peaks::columnMinMax's exact partition already makes the draw gap-free.
|
||||||
// envelope's coarser resolution.
|
|
||||||
int waveformColumnCount(const KitBox& box);
|
|
||||||
|
|
||||||
// The house oversampling factor for waveform envelopes: bins requested per drawn pixel
|
// Multiplier kept at 1 (no oversampling). kWaveformOversample is present only so existing
|
||||||
// column. Each display column then shows the true min/max of ~4 bins (via
|
// call sites `kWaveformOversample * waveformColumnCount(box)` compile unchanged; a value of
|
||||||
// peaks::columnMinMax), so steep transients render as accurate full-height spans instead
|
// 1 means they request exactly one bin per column, which is correct. The gap-free render
|
||||||
// of aliased single-bin dots.
|
// comes from peaks::columnMinMax's exact partition, NOT from extra bins.
|
||||||
inline constexpr int kWaveformOversample = 4;
|
inline constexpr int kWaveformOversample = 1;
|
||||||
|
|
||||||
// A waveform envelope drawn as a min/max plot over the bg/panel surface: a midline per
|
// A waveform envelope drawn as a min/max plot over the bg/panel surface: a midline per
|
||||||
// channel and one accent vertical span PER PIXEL COLUMN, each column covering the true
|
// channel and one accent vertical span PER PIXEL COLUMN, each column covering the true
|
||||||
// extremes of every bin that projects to it (peaks::columnMinMax — gap-free at any
|
// extremes of every bin that projects to it (peaks::columnMinMax — gap-free at any
|
||||||
// bins-to-pixels ratio). The ONE waveform shape in the system: the dock-panel thumbnail,
|
// bins-to-pixels ratio because columnMinMax partitions bins exactly as computeEnvelope
|
||||||
// the browser cards, and the editor hero all render through this. `box` is the draw
|
// does, so every pixel column is always covered). The ONE waveform shape in the system:
|
||||||
// region; `env` is the per-channel min/max envelope from peaks::computeEnvelope, ideally
|
// the dock-panel thumbnail, the browser cards, and the editor hero all render through
|
||||||
// oversampled (see waveformColumnCount / kWaveformOversample above). An empty env draws
|
// this. `box` is the draw region; `env` is the per-channel min/max envelope from
|
||||||
// just the midline. The caller fills the surface first (or passes a box already filled);
|
// peaks::computeEnvelope, sized to waveformColumnCount(box) bins (clamped to frame count).
|
||||||
// this draws only the wave + midline.
|
// An empty env draws just the midline. The caller fills the surface first (or passes a
|
||||||
|
// box already filled); this draws only the wave + midline.
|
||||||
void drawWaveform(LICE_IBitmap* bmp, const KitBox& box, const Envelope& env);
|
void drawWaveform(LICE_IBitmap* bmp, const KitBox& box, const Envelope& env);
|
||||||
|
|
||||||
} // namespace reasampler
|
} // namespace reasampler
|
||||||
|
|||||||
@@ -121,9 +121,9 @@ std::string sampleLabel(const std::vector<SampleChoice>& samples, const std::str
|
|||||||
return "?";
|
return "?";
|
||||||
}
|
}
|
||||||
|
|
||||||
// The bin count a card's thumbnail is computed at: kWaveformOversample bins per drawn
|
// The bin count a card's thumbnail is computed at: one bin per drawn pixel column — the
|
||||||
// thumbnail pixel column (FA3 anti-alias) — drawWaveform collapses them per column via
|
// gap-free render comes from peaks::columnMinMax's exact partition, not from extra bins.
|
||||||
// peaks::columnMinMax. thumbnailFor clamps the request to the decoded frame count.
|
// thumbnailFor clamps the request to the decoded frame count.
|
||||||
int thumbBins(const BrowserLayout& layout) {
|
int thumbBins(const BrowserLayout& layout) {
|
||||||
return (std::max)(1, kWaveformOversample *
|
return (std::max)(1, kWaveformOversample *
|
||||||
waveformColumnCount(toKitBox(cardThumbnailRect(layout, 0))));
|
waveformColumnCount(toKitBox(cardThumbnailRect(layout, 0))));
|
||||||
@@ -1011,9 +1011,9 @@ void ReaSamplerEditor::paintSample(LICE_IBitmap* bmp, int w, int h) {
|
|||||||
const Rect waveArea = bands.hero;
|
const Rect waveArea = bands.hero;
|
||||||
fillSurface(bmp, toKitBox(waveArea), Role::BgBase, InteractionState::Rest);
|
fillSurface(bmp, toKitBox(waveArea), Role::BgBase, InteractionState::Rest);
|
||||||
if (frames > 0 && waveArea.width() > 0) {
|
if (frames > 0 && waveArea.width() > 0) {
|
||||||
// FA3 anti-alias: request kWaveformOversample bins per drawn pixel column (clamped
|
// FA3 gap-free: one bin per drawn pixel column (kWaveformOversample == 1, so this
|
||||||
// to the frame count) — drawWaveform collapses them per column via
|
// multiplies by 1). The gap-free draw comes from peaks::columnMinMax's exact
|
||||||
// peaks::columnMinMax into a gap-free true min/max envelope.
|
// partition — extra bins produce no visible change. Clamped to frame count below.
|
||||||
const std::int64_t wantBins =
|
const std::int64_t wantBins =
|
||||||
static_cast<std::int64_t>((std::max)(1, waveformColumnCount(toKitBox(waveArea)))) *
|
static_cast<std::int64_t>((std::max)(1, waveformColumnCount(toKitBox(waveArea)))) *
|
||||||
kWaveformOversample;
|
kWaveformOversample;
|
||||||
|
|||||||
@@ -173,6 +173,22 @@ static void testListRowLayoutHitAgreement() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- waveformColumnCount -----------------------------------------------------
|
||||||
|
|
||||||
|
static void testWaveformColumnCount() {
|
||||||
|
// Normal box: width minus the two 2px side insets.
|
||||||
|
CHECK(waveformColumnCount(KitBox{0, 0, 100, 40}) == 96);
|
||||||
|
CHECK(waveformColumnCount(KitBox{10, 5, 50, 20}) == 46);
|
||||||
|
// Minimum: a 5-wide box yields exactly 1 drawable column.
|
||||||
|
CHECK(waveformColumnCount(KitBox{0, 0, 5, 10}) == 1);
|
||||||
|
// Too narrow to have any drawable columns: 4 or fewer columns returns 0.
|
||||||
|
CHECK(waveformColumnCount(KitBox{0, 0, 4, 10}) == 0);
|
||||||
|
CHECK(waveformColumnCount(KitBox{0, 0, 1, 10}) == 0);
|
||||||
|
// Degenerate (empty) box returns 0 — graceful suppression.
|
||||||
|
CHECK(waveformColumnCount(KitBox{}) == 0);
|
||||||
|
CHECK(waveformColumnCount(KitBox{0, 0, 0, 40}) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
testHitTestBoxHalfOpen();
|
testHitTestBoxHalfOpen();
|
||||||
testButtonBoxInset();
|
testButtonBoxInset();
|
||||||
@@ -190,6 +206,7 @@ int main() {
|
|||||||
testListRowHitTest();
|
testListRowHitTest();
|
||||||
testListRowHitTestBoundedByCount();
|
testListRowHitTestBoundedByCount();
|
||||||
testListRowLayoutHitAgreement();
|
testListRowLayoutHitAgreement();
|
||||||
|
testWaveformColumnCount();
|
||||||
|
|
||||||
if (g_fail == 0) std::printf("component_geometry: all tests passed\n");
|
if (g_fail == 0) std::printf("component_geometry: all tests passed\n");
|
||||||
else std::printf("component_geometry: %d CHECK(s) FAILED\n", g_fail);
|
else std::printf("component_geometry: %d CHECK(s) FAILED\n", g_fail);
|
||||||
|
|||||||
Reference in New Issue
Block a user