86 lines
3.1 KiB
C++
86 lines
3.1 KiB
C++
// embed_strip.cpp — see embed_strip.h. Pure math; no host types.
|
|
|
|
#include "core/instrument/ui/embed_strip.h"
|
|
|
|
#include <algorithm>
|
|
|
|
namespace reasampler::instrument::ui {
|
|
|
|
namespace {
|
|
|
|
// Clamp a MIDI note to [0, kEmbedKeyCount-1].
|
|
int clampNote(int n) {
|
|
if (n < 0) return 0;
|
|
if (n > kEmbedKeyCount - 1) return kEmbedKeyCount - 1;
|
|
return n;
|
|
}
|
|
|
|
// Map a key boundary in [0, kEmbedKeyCount] to an x pixel inside a band of the given
|
|
// left/width. keyEdge is a boundary (0..128), so keyEdge==128 maps to the band's right.
|
|
// Integer math, floored — a zone's left uses floor(low) and its right uses floor(high+1),
|
|
// which tiles adjacent zones without a seam.
|
|
int keyEdgeToX(int bandLeft, int bandWidth, int keyEdge) {
|
|
if (keyEdge <= 0) return bandLeft;
|
|
if (keyEdge >= kEmbedKeyCount) return bandLeft + bandWidth;
|
|
return bandLeft + (keyEdge * bandWidth) / kEmbedKeyCount;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
EmbedLayout layoutEmbed(int w, int h) {
|
|
const int cw = std::max(0, w);
|
|
const int ch = std::max(0, h);
|
|
|
|
EmbedLayout out;
|
|
|
|
// The level band takes a fixed height at the bottom, but never so much that the keymap
|
|
// above it falls below its minimum (or that the band exceeds the area). On a very short
|
|
// area the band yields to the keymap entirely.
|
|
int bandH = std::min(kEmbedLevelBandHeight, ch);
|
|
if (ch - bandH < kEmbedKeymapMinHeight) {
|
|
bandH = std::max(0, ch - kEmbedKeymapMinHeight);
|
|
}
|
|
const int keymapBottom = ch - bandH;
|
|
|
|
out.keymap = Rect::ltrb(0, 0, cw, keymapBottom);
|
|
out.levelBand = Rect::ltrb(0, keymapBottom, cw, ch);
|
|
return out;
|
|
}
|
|
|
|
Rect zoneSegmentRect(const EmbedLayout& layout, int lowNote, int highNote) {
|
|
const Rect& band = layout.keymap;
|
|
const int bandWidth = std::max(0, band.width);
|
|
|
|
int lo = clampNote(lowNote);
|
|
int hi = clampNote(highNote);
|
|
if (lo > hi) lo = hi; // defensive: a malformed zone collapses rather than inverts
|
|
|
|
const int leftX = keyEdgeToX(band.x, bandWidth, lo);
|
|
const int rightX = keyEdgeToX(band.x, bandWidth, hi + 1);
|
|
return Rect::ltrb(leftX, band.y, std::max(leftX, rightX), band.bottom());
|
|
}
|
|
|
|
int zoneAtPoint(const EmbedLayout& layout, const EmbedZone* zones, int zoneCount, int x,
|
|
int y) {
|
|
if (zoneCount <= 0 || zones == nullptr) return -1;
|
|
if (!contains(layout.keymap, x, y)) return -1;
|
|
// First covering zone in draw order wins (first-match, mirroring the core's resolve).
|
|
for (int i = 0; i < zoneCount; ++i) {
|
|
const Rect r = zoneSegmentRect(layout, zones[i].lowNote, zones[i].highNote);
|
|
if (contains(r, x, y)) return i;
|
|
}
|
|
return -1; // on the band but on an uncovered key
|
|
}
|
|
|
|
Rect levelFillRect(const EmbedLayout& layout, double level) {
|
|
const Rect& band = layout.levelBand;
|
|
if (band.width <= 0 || band.height <= 0) return Rect{};
|
|
double l = level;
|
|
if (l < 0.0) l = 0.0;
|
|
if (l > 1.0) l = 1.0;
|
|
const int fillW = static_cast<int>(l * band.width);
|
|
if (fillW <= 0) return Rect{};
|
|
return Rect::ltrb(band.x, band.y, band.x + fillW, band.bottom());
|
|
}
|
|
|
|
} // namespace reasampler::instrument::ui
|