fix: harden note-program model against corrupt tempo/division/denomination records
Reject subnormal BPM that overflows to NaN, normalize Division equality, pin a single out-of-range-denomination interpretation across all readers, flag collapsed capture windows, add offset off-view editors and structural static_asserts, collapse duplicated CLAUDE.md facts.
This commit is contained in:
@@ -111,16 +111,19 @@ static void testNamedExamples() {
|
||||
// --- Picker order -------------------------------------------------------------
|
||||
|
||||
static void testPickerOrderIsShortestFirst() {
|
||||
// Straight lengths ascend across rungs; within a rung the order is straight, dotted,
|
||||
// triplet (so the index is not itself sorted by duration — only the rungs are).
|
||||
// Straight lengths ascend across rungs; within EVERY rung (not just rung 0) the order is
|
||||
// straight, dotted, triplet — so the index is not itself sorted by duration.
|
||||
for (int rung = 1; rung < kRungCount; ++rung) {
|
||||
const double prev = divisionBeats(divisionAt((rung - 1) * kModifierCount));
|
||||
const double here = divisionBeats(divisionAt(rung * kModifierCount));
|
||||
CHECK(here > prev);
|
||||
}
|
||||
CHECK(divisionAt(0) == makeDivision(kMinQuarterExponent, DivisionModifier::Straight));
|
||||
CHECK(divisionAt(1) == makeDivision(kMinQuarterExponent, DivisionModifier::Dotted));
|
||||
CHECK(divisionAt(2) == makeDivision(kMinQuarterExponent, DivisionModifier::Triplet));
|
||||
for (int rung = 0; rung < kRungCount; ++rung) {
|
||||
const int e = kMinQuarterExponent + rung;
|
||||
CHECK(divisionAt(rung * kModifierCount + 0) == makeDivision(e, DivisionModifier::Straight));
|
||||
CHECK(divisionAt(rung * kModifierCount + 1) == makeDivision(e, DivisionModifier::Dotted));
|
||||
CHECK(divisionAt(rung * kModifierCount + 2) == makeDivision(e, DivisionModifier::Triplet));
|
||||
}
|
||||
}
|
||||
|
||||
static void testIndexRoundTripsOverTheWholeSet() {
|
||||
@@ -151,6 +154,16 @@ static void testOffLadderExponentClampsToTheNearestRung() {
|
||||
CHECK(almostEqual(divisionBeats(corrupt), 256.0));
|
||||
}
|
||||
|
||||
static void testEqualityNormalizesOffLadderExponentsLikeEveryOtherReader() {
|
||||
// divisionBeats/divisionIndex/divisionLabel all re-clamp through makeDivision; equality
|
||||
// must too, or a corrupt persisted value reads as a spurious diff on every reload.
|
||||
Division corrupt;
|
||||
corrupt.quarterExponent = 120;
|
||||
corrupt.modifier = DivisionModifier::Straight;
|
||||
CHECK(corrupt == makeDivision(kMaxQuarterExponent, DivisionModifier::Straight));
|
||||
CHECK(corrupt != makeDivision(kMaxQuarterExponent, DivisionModifier::Dotted));
|
||||
}
|
||||
|
||||
static void testOutOfRangeIndexClampsIntoTheSet() {
|
||||
CHECK(divisionAt(-1) == divisionAt(0));
|
||||
CHECK(divisionAt(kDivisionCount) == divisionAt(kDivisionCount - 1));
|
||||
@@ -169,6 +182,7 @@ int main() {
|
||||
testEverySetMemberIsDistinct();
|
||||
|
||||
testOffLadderExponentClampsToTheNearestRung();
|
||||
testEqualityNormalizesOffLadderExponentsLikeEveryOtherReader();
|
||||
testOutOfRangeIndexClampsIntoTheSet();
|
||||
|
||||
if (g_fail == 0) std::printf("musical_division: all tests passed\n");
|
||||
|
||||
Reference in New Issue
Block a user