Rebuild the chrome band: full-width piano strip with uniform key widths, note tooltips, one toolbar font
This commit is contained in:
@@ -8,18 +8,29 @@ namespace reasampler::instrument::ui {
|
||||
|
||||
namespace {
|
||||
|
||||
// Pitch class of each natural, and the count of naturals strictly below each pitch class.
|
||||
constexpr int kNaturalPitchClass[7] = {0, 2, 4, 5, 7, 9, 11};
|
||||
constexpr int kNaturalsBelowPc[12] = {0, 1, 1, 2, 2, 3, 4, 4, 5, 5, 6, 6};
|
||||
|
||||
int clampNote(int n) {
|
||||
if (n < 0) return 0;
|
||||
if (n > kStripKeyCount - 1) return kStripKeyCount - 1;
|
||||
return n;
|
||||
}
|
||||
|
||||
// Maps a key boundary (0..128) to an x pixel. Key N's left is keyEdgeToX(N), right is
|
||||
// keyEdgeToX(N+1) — tiles adjacent keys without a seam. Mirrors 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;
|
||||
// The MIDI note of white key `index` (0..74).
|
||||
int whiteNoteAt(int index) {
|
||||
const int i = index < 0 ? 0 : (index > kStripWhiteKeyCount - 1 ? kStripWhiteKeyCount - 1
|
||||
: index);
|
||||
return clampNote((i / 7) * 12 + kNaturalPitchClass[i % 7]);
|
||||
}
|
||||
|
||||
// The black key straddling white-key boundary `b`, or -1 where the scale has none (E-F and
|
||||
// B-C are adjacent naturals).
|
||||
int blackNoteAtBoundary(int b) {
|
||||
if (b <= 0 || b >= kStripWhiteKeyCount) return -1;
|
||||
const int below = whiteNoteAt(b) - 1;
|
||||
return (below >= 0 && !isNaturalKey(below)) ? below : -1;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -28,42 +39,22 @@ 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::ltrb(0, 0, cw, ch);
|
||||
out.band = Rect::ltrb(0, 0, cw, ch);
|
||||
if (ch <= 0) return out;
|
||||
|
||||
out.whiteWidth = cw / kStripWhiteKeyCount;
|
||||
if (out.whiteWidth <= 0) return out; // narrower than one pixel per white key
|
||||
|
||||
const int keysW = out.whiteWidth * kStripWhiteKeyCount;
|
||||
const int left = (cw - keysW) / 2; // residue split evenly into the two end margins
|
||||
out.keys = Rect::ltrb(left, 0, left + keysW, ch);
|
||||
out.blackWidth = std::max(1, (out.whiteWidth * 3) / 5);
|
||||
out.blackHeight = std::max(1, (ch * 3) / 5);
|
||||
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 (0..127); callers pass note+1 to get its right edge, 128 -> band right.
|
||||
const int edge = note < 0 ? 0 : (note > kStripKeyCount ? kStripKeyCount : note);
|
||||
return keyEdgeToX(band.x, 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::ltrb(leftX, layout.keys.y, 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;
|
||||
// Inverts keyEdgeToX: the key whose half-open [leftX, rightX) contains x.
|
||||
const int offset = x - band.x;
|
||||
int note = (offset * kStripKeyCount) / bandWidth;
|
||||
return clampNote(note);
|
||||
}
|
||||
|
||||
bool isNaturalKey(int note) {
|
||||
const int n = note < 0 ? 0 : (note > kStripKeyCount - 1 ? kStripKeyCount - 1 : note);
|
||||
const int n = clampNote(note);
|
||||
static constexpr bool kNatural[12] = {
|
||||
true, // 0 C
|
||||
false, // 1 C#
|
||||
@@ -81,20 +72,56 @@ bool isNaturalKey(int note) {
|
||||
return kNatural[n % 12];
|
||||
}
|
||||
|
||||
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
|
||||
// Same linear mapping as keyAtPoint/keyEdgeToX (exact rational), not a truncated-integer
|
||||
// bandWidth/128 key width — that drifted at the far end of the strip.
|
||||
const int half = bandWidth / 2;
|
||||
int shift;
|
||||
if (dxPixels > 0) {
|
||||
shift = (dxPixels * kStripKeyCount + half) / bandWidth;
|
||||
} else {
|
||||
shift = -(((-dxPixels) * kStripKeyCount + half) / bandWidth);
|
||||
int whiteIndexOf(int note) {
|
||||
const int n = clampNote(note);
|
||||
return (n / 12) * 7 + kNaturalsBelowPc[n % 12];
|
||||
}
|
||||
|
||||
Rect keyRect(const StripLayout& layout, int note) {
|
||||
if (layout.keys.empty()) return Rect{};
|
||||
const int n = clampNote(note);
|
||||
const int wi = whiteIndexOf(n);
|
||||
if (isNaturalKey(n)) {
|
||||
const int left = layout.keys.x + wi * layout.whiteWidth;
|
||||
return Rect::ltrb(left, layout.keys.y, left + layout.whiteWidth, layout.keys.bottom());
|
||||
}
|
||||
return clampNote(startNote + shift);
|
||||
const int centre = layout.keys.x + wi * layout.whiteWidth;
|
||||
const int left = centre - layout.blackWidth / 2;
|
||||
return Rect::ltrb(left, layout.keys.y, left + layout.blackWidth,
|
||||
layout.keys.y + layout.blackHeight);
|
||||
}
|
||||
|
||||
Rect rootMarkerRect(const StripLayout& layout, int rootNote) {
|
||||
return keyRect(layout, rootNote);
|
||||
}
|
||||
|
||||
int keyAtPoint(const StripLayout& layout, int x, int y) {
|
||||
if (!contains(layout.keys, x, y)) return -1;
|
||||
const int offset = x - layout.keys.x;
|
||||
const int wi = std::min(offset / layout.whiteWidth, kStripWhiteKeyCount - 1);
|
||||
if (y < layout.keys.y + layout.blackHeight) {
|
||||
// Only the two boundaries flanking this white key can carry an overlapping black.
|
||||
const int candidates[2] = {wi, wi + 1};
|
||||
for (const int b : candidates) {
|
||||
const int note = blackNoteAtBoundary(b);
|
||||
if (note >= 0 && contains(keyRect(layout, note), x, y)) return note;
|
||||
}
|
||||
}
|
||||
return whiteNoteAt(wi);
|
||||
}
|
||||
|
||||
int resolveDragNote(const StripLayout& layout, int x, int y) {
|
||||
if (layout.keys.empty()) return -1;
|
||||
const int cx = std::clamp(x, layout.keys.x, layout.keys.right() - 1);
|
||||
const int cy = std::clamp(y, layout.keys.y, layout.keys.bottom() - 1);
|
||||
return keyAtPoint(layout, cx, cy);
|
||||
}
|
||||
|
||||
std::string noteName(int note) {
|
||||
static constexpr const char* kNames[12] = {"C", "C#", "D", "D#", "E", "F",
|
||||
"F#", "G", "G#", "A", "A#", "B"};
|
||||
const int n = clampNote(note);
|
||||
return std::string(kNames[n % 12]) + std::to_string(n / 12 - 1);
|
||||
}
|
||||
|
||||
} // namespace reasampler::instrument::ui
|
||||
|
||||
Reference in New Issue
Block a user