Merge pS-w1-t4-kbd: keyboard_strip isNaturalKey predicate (S-VIEW-7 core)

This commit is contained in:
2026-07-27 13:37:32 -04:00
3 changed files with 70 additions and 1 deletions
+23
View File
@@ -102,6 +102,29 @@ ZoneBarHit zoneBarAtPoint(const StripLayout& layout, const int* lows, const int*
return ZoneBarHit{}; // on the band but on no bar
}
bool isNaturalKey(int note) {
// Clamp to the valid MIDI range before indexing.
const int n = note < 0 ? 0 : (note > kStripKeyCount - 1 ? kStripKeyCount - 1 : note);
// The 12-semitone pattern of natural (white) keys within an octave, starting at C:
// positions 0(C) 2(D) 4(E) 5(F) 7(G) 9(A) 11(B) are natural;
// positions 1(C#) 3(D#) 6(F#) 8(G#) 10(A#) are accidental.
static constexpr bool kNatural[12] = {
true, // 0 C
false, // 1 C#
true, // 2 D
false, // 3 D#
true, // 4 E
true, // 5 F
false, // 6 F#
true, // 7 G
false, // 8 G#
true, // 9 A
false, // 10 A#
true, // 11 B
};
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());
+10
View File
@@ -118,4 +118,14 @@ ZoneBarHit zoneBarAtPoint(const StripLayout& layout, const int* lows, const int*
// key centre. Returns startNote unchanged for dxPixels==0.
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels);
// Returns true when `note` (0..127) is a NATURAL (white) key in standard 12-tone equal
// temperament; false when it is an ACCIDENTAL (black) key. Notes out of the [0,127]
// range are clamped to [0,127] before classification (i.e. this never throws/UBs on a
// bad input). The 12 semitone positions within an octave:
// Natural (white): 0(C) 2(D) 4(E) 5(F) 7(G) 9(A) 11(B)
// Accidental (black): 1(C#) 3(D#) 6(F#) 8(G#) 10(A#)
// Used by the shell to overlay the two-tone bright/dark piano-key pattern over the
// pastel spectral fill (S-VIEW-7). Pure — no layout required, no host types.
bool isNaturalKey(int note);
} // namespace reasampler::vst
+37 -1
View File
@@ -11,7 +11,8 @@
// classifying low-edge / high-edge / body and the narrow-bar midpoint split (low wins the
// tie); zoneBarAtPoint first-match on overlap + null-list rejection; resolveDragNote rounding
// to the nearest key at the key centre, clamping to [0,127], and the zero-delta / zero-width
// no-ops.
// no-ops; isNaturalKey across a full octave (C4..B4), at boundary notes 0 and 127, and with
// out-of-range inputs that clamp to [0,127].
#include "../src/vst/keyboard_strip.h"
@@ -179,6 +180,38 @@ static void testResolveDragClampsAndNoOps() {
CHECK(resolveDragNote(Z, 60, 500) == 60);
}
// --- isNaturalKey -------------------------------------------------------------
static void testIsNaturalKeyFullOctave() {
// Semitone positions 0..11 starting at C4 (MIDI 60):
// C=60(nat) C#=61(acc) D=62(nat) D#=63(acc) E=64(nat) F=65(nat)
// F#=66(acc) G=67(nat) G#=68(acc) A=69(nat) A#=70(acc) B=71(nat)
const bool expected[12] = {
true, false, true, false, true, true,
false, true, false, true, false, true,
};
for (int i = 0; i < 12; ++i) {
CHECK(isNaturalKey(60 + i) == expected[i]);
}
}
static void testIsNaturalKeyBoundaryNotes() {
// Note 0 is C (natural); note 127 is G (natural); note 1 is C# (accidental).
CHECK(isNaturalKey(0) == true); // C0 — natural
CHECK(isNaturalKey(1) == false); // C#0 — accidental
CHECK(isNaturalKey(127) == true); // G9 — natural (127 % 12 == 7)
CHECK(isNaturalKey(126) == false); // F#9 — accidental (126 % 12 == 6)
}
static void testIsNaturalKeyOutOfRangeClamped() {
// Values outside [0,127] clamp to [0,127]; must not crash/UB.
// note -1 clamps to 0 (C, natural); note 128 clamps to 127 (G, natural).
CHECK(isNaturalKey(-1) == true);
CHECK(isNaturalKey(128) == true);
CHECK(isNaturalKey(-100) == true);
CHECK(isNaturalKey(200) == true);
}
static void testResolveDragProportionalNonDivisibleWidth() {
// THE REVIEW FINDING: 544px / 128 = 4.25 (non-integer). Old uniform-keyW math used
// keyW = 4 (floor), accumulating ~7 keys of drift at the far end. The proportional fix
@@ -221,6 +254,9 @@ int main() {
testResolveDragRoundsToNearestKey();
testResolveDragClampsAndNoOps();
testResolveDragProportionalNonDivisibleWidth();
testIsNaturalKeyFullOctave();
testIsNaturalKeyBoundaryNotes();
testIsNaturalKeyOutOfRangeClamped();
if (g_fail == 0) std::printf("keyboard_strip: all tests passed\n");
return g_fail != 0;