note: land the programmed capture-signal model — division ladder, tempo resolution, anchored offsets, one record and one resolver
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
// Standalone tests for reasampler::instrument::note::musical_division — no VST3, no REAPER,
|
||||
// no framework. Same fast assert loop as the sibling pure tests.
|
||||
//
|
||||
// Covers: the beat length of all 39 divisions against a literal rung table (NOT the module's
|
||||
// own exponent formula); the 1/64 and 64/1 extremes; the four named example divisions; the
|
||||
// label notation; picker order and index round-trip; off-ladder clamping.
|
||||
|
||||
#include "../src/core/instrument/note/musical_division.h"
|
||||
|
||||
#include <cstdio>
|
||||
|
||||
using namespace reasampler::instrument::note;
|
||||
|
||||
static int g_fail = 0;
|
||||
#define CHECK(cond) do { if(!(cond)) { \
|
||||
std::printf("FAIL line %d: %s\n", __LINE__, #cond); ++g_fail; } } while(0)
|
||||
|
||||
static bool almostEqual(double a, double b) {
|
||||
const double d = a - b;
|
||||
return (d < 0 ? -d : d) < 1e-12;
|
||||
}
|
||||
|
||||
// Length in beats (quarter notes) of each straight rung, written out rather than computed,
|
||||
// so a broken exponent formula cannot agree with its own mistake.
|
||||
static const double kStraightBeats[kRungCount] = {
|
||||
0.0625, // 1/64
|
||||
0.125, // 1/32
|
||||
0.25, // 1/16
|
||||
0.5, // 1/8
|
||||
1.0, // 1/4
|
||||
2.0, // 1/2
|
||||
4.0, // 1/1
|
||||
8.0, // 2/1
|
||||
16.0, // 4/1
|
||||
32.0, // 8/1
|
||||
64.0, // 16/1
|
||||
128.0, // 32/1
|
||||
256.0, // 64/1
|
||||
};
|
||||
|
||||
static const char* const kStraightLabels[kRungCount] = {
|
||||
"1/64", "1/32", "1/16", "1/8", "1/4", "1/2", "1/1",
|
||||
"2/1", "4/1", "8/1", "16/1", "32/1", "64/1",
|
||||
};
|
||||
|
||||
// --- The ladder ---------------------------------------------------------------
|
||||
|
||||
static void testLadderSpansSixtyfourthToSixtyFourWhole() {
|
||||
CHECK(kRungCount == 13);
|
||||
CHECK(kDivisionCount == 39);
|
||||
CHECK(divisionLabel(divisionAt(0)) == "1/64");
|
||||
CHECK(divisionLabel(divisionAt(kDivisionCount - 1)) == "64/1t");
|
||||
}
|
||||
|
||||
static void testEveryStraightRungHasItsWrittenBeatLength() {
|
||||
for (int rung = 0; rung < kRungCount; ++rung) {
|
||||
const Division d = makeDivision(kMinQuarterExponent + rung, DivisionModifier::Straight);
|
||||
CHECK(almostEqual(divisionBeats(d), kStraightBeats[rung]));
|
||||
CHECK(divisionLabel(d) == kStraightLabels[rung]);
|
||||
}
|
||||
}
|
||||
|
||||
static void testDottedIsHalfAgainAndTripletIsTwoThirds() {
|
||||
for (int rung = 0; rung < kRungCount; ++rung) {
|
||||
const int e = kMinQuarterExponent + rung;
|
||||
CHECK(almostEqual(divisionBeats(makeDivision(e, DivisionModifier::Dotted)),
|
||||
kStraightBeats[rung] * 1.5));
|
||||
CHECK(almostEqual(divisionBeats(makeDivision(e, DivisionModifier::Triplet)),
|
||||
kStraightBeats[rung] * 2.0 / 3.0));
|
||||
}
|
||||
}
|
||||
|
||||
static void testExtremes() {
|
||||
// 1/64 straight is the shortest rung; 64/1 straight is the longest.
|
||||
CHECK(almostEqual(divisionBeats(makeDivision(kMinQuarterExponent, DivisionModifier::Straight)),
|
||||
0.0625));
|
||||
CHECK(almostEqual(divisionBeats(makeDivision(kMaxQuarterExponent, DivisionModifier::Straight)),
|
||||
256.0));
|
||||
// The dotted 64/1 is the single longest programmable note.
|
||||
CHECK(almostEqual(divisionBeats(makeDivision(kMaxQuarterExponent, DivisionModifier::Dotted)),
|
||||
384.0));
|
||||
// The 1/64 triplet is the shortest.
|
||||
CHECK(almostEqual(divisionBeats(makeDivision(kMinQuarterExponent, DivisionModifier::Triplet)),
|
||||
0.0625 * 2.0 / 3.0));
|
||||
}
|
||||
|
||||
// --- The four named examples --------------------------------------------------
|
||||
|
||||
static void testNamedExamples() {
|
||||
// 1/8. — an eighth is half a beat, dotted is three quarters of one.
|
||||
const Division dottedEighth = makeDivision(-1, DivisionModifier::Dotted);
|
||||
CHECK(almostEqual(divisionBeats(dottedEighth), 0.75));
|
||||
CHECK(divisionLabel(dottedEighth) == "1/8.");
|
||||
|
||||
// 1/4t — a quarter is one beat, the triplet is two thirds of one.
|
||||
const Division quarterTriplet = makeDivision(0, DivisionModifier::Triplet);
|
||||
CHECK(almostEqual(divisionBeats(quarterTriplet), 2.0 / 3.0));
|
||||
CHECK(divisionLabel(quarterTriplet) == "1/4t");
|
||||
|
||||
// 1/16 — a quarter of a beat.
|
||||
const Division sixteenth = makeDivision(-2, DivisionModifier::Straight);
|
||||
CHECK(almostEqual(divisionBeats(sixteenth), 0.25));
|
||||
CHECK(divisionLabel(sixteenth) == "1/16");
|
||||
|
||||
// 4/1 — four whole notes, sixteen beats.
|
||||
const Division fourWhole = makeDivision(4, DivisionModifier::Straight);
|
||||
CHECK(almostEqual(divisionBeats(fourWhole), 16.0));
|
||||
CHECK(divisionLabel(fourWhole) == "4/1");
|
||||
}
|
||||
|
||||
// --- 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).
|
||||
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));
|
||||
}
|
||||
|
||||
static void testIndexRoundTripsOverTheWholeSet() {
|
||||
for (int i = 0; i < kDivisionCount; ++i) {
|
||||
CHECK(divisionIndex(divisionAt(i)) == i);
|
||||
}
|
||||
}
|
||||
|
||||
static void testEverySetMemberIsDistinct() {
|
||||
// No two indices name the same division, so the picker offers 39 real choices.
|
||||
for (int i = 0; i < kDivisionCount; ++i) {
|
||||
for (int j = i + 1; j < kDivisionCount; ++j) {
|
||||
CHECK(divisionAt(i) != divisionAt(j));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// --- Clamping -----------------------------------------------------------------
|
||||
|
||||
static void testOffLadderExponentClampsToTheNearestRung() {
|
||||
CHECK(makeDivision(-99, DivisionModifier::Straight)
|
||||
== makeDivision(kMinQuarterExponent, DivisionModifier::Straight));
|
||||
CHECK(makeDivision(99, DivisionModifier::Triplet)
|
||||
== makeDivision(kMaxQuarterExponent, DivisionModifier::Triplet));
|
||||
// A record carrying an off-ladder exponent still resolves to a real length.
|
||||
Division corrupt;
|
||||
corrupt.quarterExponent = 120;
|
||||
CHECK(almostEqual(divisionBeats(corrupt), 256.0));
|
||||
}
|
||||
|
||||
static void testOutOfRangeIndexClampsIntoTheSet() {
|
||||
CHECK(divisionAt(-1) == divisionAt(0));
|
||||
CHECK(divisionAt(kDivisionCount) == divisionAt(kDivisionCount - 1));
|
||||
}
|
||||
|
||||
int main() {
|
||||
testLadderSpansSixtyfourthToSixtyFourWhole();
|
||||
testEveryStraightRungHasItsWrittenBeatLength();
|
||||
testDottedIsHalfAgainAndTripletIsTwoThirds();
|
||||
testExtremes();
|
||||
|
||||
testNamedExamples();
|
||||
|
||||
testPickerOrderIsShortestFirst();
|
||||
testIndexRoundTripsOverTheWholeSet();
|
||||
testEverySetMemberIsDistinct();
|
||||
|
||||
testOffLadderExponentClampsToTheNearestRung();
|
||||
testOutOfRangeIndexClampsIntoTheSet();
|
||||
|
||||
if (g_fail == 0) std::printf("musical_division: all tests passed\n");
|
||||
else std::printf("musical_division: %d FAILED\n", g_fail);
|
||||
return g_fail == 0 ? 0 : 1;
|
||||
}
|
||||
Reference in New Issue
Block a user