Merge pS-w1-t4-kbd: keyboard_strip isNaturalKey predicate (S-VIEW-7 core)
This commit is contained in:
@@ -102,6 +102,29 @@ ZoneBarHit zoneBarAtPoint(const StripLayout& layout, const int* lows, const int*
|
|||||||
return ZoneBarHit{}; // on the band but on no bar
|
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) {
|
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels) {
|
||||||
if (dxPixels == 0) return clampNote(startNote);
|
if (dxPixels == 0) return clampNote(startNote);
|
||||||
const int bandWidth = std::max(0, layout.keys.width());
|
const int bandWidth = std::max(0, layout.keys.width());
|
||||||
|
|||||||
@@ -118,4 +118,14 @@ ZoneBarHit zoneBarAtPoint(const StripLayout& layout, const int* lows, const int*
|
|||||||
// key centre. Returns startNote unchanged for dxPixels==0.
|
// key centre. Returns startNote unchanged for dxPixels==0.
|
||||||
int resolveDragNote(const StripLayout& layout, int startNote, int dxPixels);
|
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
|
} // namespace reasampler::vst
|
||||||
|
|||||||
@@ -11,7 +11,8 @@
|
|||||||
// classifying low-edge / high-edge / body and the narrow-bar midpoint split (low wins the
|
// 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
|
// 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
|
// 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"
|
#include "../src/vst/keyboard_strip.h"
|
||||||
|
|
||||||
@@ -179,6 +180,38 @@ static void testResolveDragClampsAndNoOps() {
|
|||||||
CHECK(resolveDragNote(Z, 60, 500) == 60);
|
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() {
|
static void testResolveDragProportionalNonDivisibleWidth() {
|
||||||
// THE REVIEW FINDING: 544px / 128 = 4.25 (non-integer). Old uniform-keyW math used
|
// 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
|
// keyW = 4 (floor), accumulating ~7 keys of drift at the far end. The proportional fix
|
||||||
@@ -221,6 +254,9 @@ int main() {
|
|||||||
testResolveDragRoundsToNearestKey();
|
testResolveDragRoundsToNearestKey();
|
||||||
testResolveDragClampsAndNoOps();
|
testResolveDragClampsAndNoOps();
|
||||||
testResolveDragProportionalNonDivisibleWidth();
|
testResolveDragProportionalNonDivisibleWidth();
|
||||||
|
testIsNaturalKeyFullOctave();
|
||||||
|
testIsNaturalKeyBoundaryNotes();
|
||||||
|
testIsNaturalKeyOutOfRangeClamped();
|
||||||
|
|
||||||
if (g_fail == 0) std::printf("keyboard_strip: all tests passed\n");
|
if (g_fail == 0) std::printf("keyboard_strip: all tests passed\n");
|
||||||
return g_fail != 0;
|
return g_fail != 0;
|
||||||
|
|||||||
Reference in New Issue
Block a user