// keyboard_strip.cpp — see keyboard_strip.h. Pure math; no host types. #include "keyboard_strip.h" #include namespace reasampler::vst { namespace { int clampNote(int n) { if (n < 0) return 0; if (n > kStripKeyCount - 1) return kStripKeyCount - 1; return n; } // Map a key BOUNDARY in [0, kStripKeyCount] to an x pixel inside a band of the given // left/width. keyEdge is a boundary (0..128): 0 -> band left, 128 -> band right. Integer // math, floored — key N's left is keyEdgeToX(N) and its right is keyEdgeToX(N+1), tiling // adjacent keys/zones without a seam (mirror of embed_strip::keyEdgeToX). int keyEdgeToX(int bandLeft, int bandWidth, int keyEdge) { if (keyEdge <= 0) return bandLeft; if (keyEdge >= kStripKeyCount) return bandLeft + bandWidth; return bandLeft + (keyEdge * bandWidth) / kStripKeyCount; } } // namespace StripLayout layoutStrip(int w, int h) { const int cw = std::max(0, w); const int ch = std::max(0, h); StripLayout out; out.keys = Rect{0, 0, cw, ch}; return out; } int keyLeftX(const StripLayout& layout, int note) { const Rect& band = layout.keys; const int bandWidth = std::max(0, band.width()); // note is a KEY here (0..127); its left edge is boundary `note`. Callers pass note+1 to // get a key's right edge, and 128 maps to the band right. const int edge = note < 0 ? 0 : (note > kStripKeyCount ? kStripKeyCount : note); return keyEdgeToX(band.left, bandWidth, edge); } Rect keyRect(const StripLayout& layout, int note) { const int n = clampNote(note); const int leftX = keyLeftX(layout, n); const int rightX = keyLeftX(layout, n + 1); return Rect{leftX, layout.keys.top, std::max(leftX, rightX), layout.keys.bottom}; } Rect rootMarkerRect(const StripLayout& layout, int rootNote) { return keyRect(layout, rootNote); } int keyAtPoint(const StripLayout& layout, int x, int y) { const Rect& band = layout.keys; if (!contains(band, x, y)) return -1; const int bandWidth = std::max(0, band.width()); if (bandWidth <= 0) return -1; // Invert keyEdgeToX: the key whose half-open [leftX, rightX) contains x. Floor-divide // the pixel offset back to a key; clamp defensively (a point on band.right-1 maps to 127). const int offset = x - band.left; int note = (offset * kStripKeyCount) / bandWidth; return clampNote(note); } Rect zoneBarRect(const StripLayout& layout, int lowNote, int highNote) { int lo = clampNote(lowNote); int hi = clampNote(highNote); if (lo > hi) lo = hi; // defensive: a malformed zone collapses rather than inverts const int leftX = keyLeftX(layout, lo); const int rightX = keyLeftX(layout, hi + 1); return Rect{leftX, layout.keys.top, std::max(leftX, rightX), layout.keys.bottom}; } ZoneGrab zoneGrabAt(const StripLayout& layout, int lowNote, int highNote, int x, int y) { const Rect bar = zoneBarRect(layout, lowNote, highNote); if (!contains(bar, x, y)) return ZoneGrab::kNone; const int barW = bar.width(); // A narrow bar (< 2*edge) has no body: split at the midpoint, LOW edge wins the tie so // a click exactly on the midpoint resizes low (deterministic). if (barW < 2 * kStripEdgeGrabWidth) { const int mid = bar.left + barW / 2; return x <= mid ? ZoneGrab::kLowEdge : ZoneGrab::kHighEdge; } if (x < bar.left + kStripEdgeGrabWidth) return ZoneGrab::kLowEdge; if (x >= bar.right - kStripEdgeGrabWidth) return ZoneGrab::kHighEdge; return ZoneGrab::kBody; } ZoneBarHit zoneBarAtPoint(const StripLayout& layout, const int* lows, const int* highs, int count, int x, int y) { if (count <= 0 || lows == nullptr || highs == nullptr) return ZoneBarHit{}; if (!contains(layout.keys, x, y)) return ZoneBarHit{}; for (int i = 0; i < count; ++i) { const ZoneGrab g = zoneGrabAt(layout, lows[i], highs[i], x, y); if (g != ZoneGrab::kNone) return ZoneBarHit{i, g}; } return ZoneBarHit{}; // on the band but on no bar } int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels) { if (dxPixels == 0) return clampNote(startNote); const int bandWidth = std::max(0, layout.keys.width()); if (bandWidth <= 0) return clampNote(startNote); // zero-width -> no motion // Key width in pixels (>= 1 via the max). Round the delta to the nearest key so a // half-key drag flips at the key centre: add/subtract half a key before the divide. const int keyW = std::max(1, bandWidth / kStripKeyCount); const int half = keyW / 2; const int shift = dxPixels >= 0 ? (dxPixels + half) / keyW : -((-dxPixels + half) / keyW); return clampNote(startNote + shift); } } // namespace reasampler::vst