S5 Tier-1: zoned keymap editor + performance-map playback/persistence

Zone editor in the IPlugView LICE surface, zoned resolution built off-thread into the
LoadedInstrument keymap with Tier-0 fallback, performance map in VST3 component state
with v1 back-compat. Pure resolve/build/serialize + geometry with CTest coverage.
This commit is contained in:
2026-07-26 17:28:15 -04:00
parent 6ae843345c
commit 2356958930
10 changed files with 1063 additions and 63 deletions
+86
View File
@@ -75,4 +75,90 @@ int sampleRowHitTest(const EditorLayout& layout, int rowCount, int x, int y) {
return index;
}
// --- Keymap editor -----------------------------------------------------------
KeymapEditorLayout layoutKeymapEditor(int w, int h) {
KeymapEditorLayout out;
out.base = layoutEditor(w, h);
const Rect& canvas = out.base.canvas;
// Split the canvas vertically: the left column is the bank-sample list, the right
// column (1/kZonePanelFraction of the width) is the zone panel. Guard tiny widths so
// the split point never crosses the canvas edges.
const int canvasW = std::max(0, canvas.width());
const int splitW = canvasW / kZonePanelFraction; // width of the zone panel
const int splitX = std::max(canvas.left, canvas.right - splitW);
out.sampleList = Rect{canvas.left, canvas.top, splitX, canvas.bottom};
out.zonePanel = Rect{splitX, canvas.top, canvas.right, canvas.bottom};
// "Add Zone" button spans the top of the zone panel, clamped to its height.
const int addH = std::min(kAddZoneHeight, std::max(0, out.zonePanel.height()));
out.addZoneButton =
Rect{out.zonePanel.left, out.zonePanel.top, out.zonePanel.right,
out.zonePanel.top + addH};
// Zone rows stack below the button.
out.zoneRowArea = Rect{out.zonePanel.left, out.addZoneButton.bottom,
out.zonePanel.right, out.zonePanel.bottom};
return out;
}
Rect keymapSampleRowRect(const KeymapEditorLayout& layout, int index) {
if (index < 0) return Rect{};
const int top = layout.sampleList.top + index * kSampleRowHeight;
return Rect{layout.sampleList.left, top, layout.sampleList.right,
top + kSampleRowHeight};
}
int keymapSampleRowHitTest(const KeymapEditorLayout& layout, int rowCount, int x, int y) {
if (rowCount <= 0) return -1;
const Rect& list = layout.sampleList;
if (x < list.left || x >= list.right) return -1;
if (y < list.top || y >= list.bottom) return -1;
const int index = (y - list.top) / kSampleRowHeight;
if (index < 0 || index >= rowCount) return -1;
const Rect r = keymapSampleRowRect(layout, index);
if (y >= r.bottom) return -1;
return index;
}
Rect zoneRowRect(const KeymapEditorLayout& layout, int index) {
if (index < 0) return Rect{};
const int top = layout.zoneRowArea.top + index * kZoneRowHeight;
return Rect{layout.zoneRowArea.left, top, layout.zoneRowArea.right,
top + kZoneRowHeight};
}
ZoneHit zoneHitTest(const KeymapEditorLayout& layout, int zoneCount, int x, int y) {
if (zoneCount <= 0) return ZoneHit{};
const Rect& area = layout.zoneRowArea;
if (x < area.left || x >= area.right) return ZoneHit{};
if (y < area.top || y >= area.bottom) return ZoneHit{};
const int index = (y - area.top) / kZoneRowHeight;
if (index < 0 || index >= zoneCount) return ZoneHit{};
const Rect row = zoneRowRect(layout, index);
if (y >= row.bottom) return ZoneHit{};
// Seven mini-buttons pinned to the right edge, right-to-left:
// delete, root+, root-, high+, high-, low+, low-
// Each is kZoneCtrlWidth wide. A click left of the leftmost is the label ("select").
// The fields laid out LEFT-TO-RIGHT in slot order 0..6.
const ZoneField fields[7] = {
ZoneField::kLowDown, ZoneField::kLowUp, ZoneField::kHighDown,
ZoneField::kHighUp, ZoneField::kRootDown, ZoneField::kRootUp,
ZoneField::kDelete,
};
const int slots = 7;
const int ctrlBlockLeft = row.right - slots * kZoneCtrlWidth;
if (x < ctrlBlockLeft) return ZoneHit{index, ZoneField::kZoneNone}; // label -> select
const int slot = (x - ctrlBlockLeft) / kZoneCtrlWidth;
if (slot < 0 || slot >= slots) return ZoneHit{index, ZoneField::kZoneNone};
return ZoneHit{index, fields[slot]};
}
bool addZoneHitTest(const KeymapEditorLayout& layout, int x, int y) {
return contains(layout.addZoneButton, x, y);
}
} // namespace reasampler::vst