note: close every value type's domain at construction, so resolveNote is finite for every constructible input
Division and OffsetAmount get single normalizing doors and private constructors; fromBpm validates by running the conversions rather than their reciprocal. Readers drop their re-clamps and default labels.
This commit is contained in:
@@ -6,6 +6,7 @@
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
|
||||
namespace reasampler::instrument::note {
|
||||
|
||||
@@ -24,19 +25,43 @@ inline constexpr int kRungCount = kMaxQuarterExponent - kMinQuarterExponent + 1;
|
||||
inline constexpr int kModifierCount = 3;
|
||||
inline constexpr int kDivisionCount = kRungCount * kModifierCount;
|
||||
|
||||
struct Division {
|
||||
std::int8_t quarterExponent = 0; // 1/4
|
||||
DivisionModifier modifier = DivisionModifier::Straight;
|
||||
// The longest programmable note — the dotted top rung — so a caller composing this ladder
|
||||
// with the tempo conversions can check the two domains against each other at compile time.
|
||||
inline constexpr double kMaxDivisionBeats = (1 << kMaxQuarterExponent) * 1.5;
|
||||
|
||||
class Division;
|
||||
|
||||
// Off-ladder inputs clamp rather than reject: the only ways to reach one are a corrupt
|
||||
// persisted record or a picker bug, and the nearest legal length beats a nonsense duration.
|
||||
// An unnamed modifier byte has no nearest rung to fall to, so it takes the field's default.
|
||||
Division makeDivision(int quarterExponent, DivisionModifier modifier);
|
||||
|
||||
// In-domain by construction — `makeDivision` is the only door and it clamps BOTH fields, so
|
||||
// every reader below trusts the stored pair instead of re-clamping it, and equality compares
|
||||
// the two fields raw without disagreeing with any of them.
|
||||
class Division {
|
||||
public:
|
||||
Division() = default; // 1/4 straight
|
||||
|
||||
constexpr std::int8_t quarterExponent() const { return quarterExponent_; }
|
||||
constexpr DivisionModifier modifier() const { return modifier_; }
|
||||
|
||||
private:
|
||||
Division(std::int8_t quarterExponent, DivisionModifier modifier)
|
||||
: quarterExponent_(quarterExponent), modifier_(modifier) {}
|
||||
friend Division makeDivision(int quarterExponent, DivisionModifier modifier);
|
||||
|
||||
std::int8_t quarterExponent_ = 0;
|
||||
DivisionModifier modifier_ = DivisionModifier::Straight;
|
||||
};
|
||||
|
||||
static_assert(!std::is_constructible_v<Division, int, DivisionModifier>,
|
||||
"makeDivision must be the only way to give a Division a rung");
|
||||
|
||||
bool operator==(Division a, Division b);
|
||||
bool operator!=(Division a, Division b);
|
||||
|
||||
// Off-ladder exponents clamp rather than reject: the only ways to reach one are a corrupt
|
||||
// persisted record or a picker bug, and the nearest legal length beats a nonsense duration.
|
||||
Division makeDivision(int quarterExponent, DivisionModifier modifier);
|
||||
|
||||
// Length in beats (quarter notes). Always > 0.
|
||||
// Length in beats (quarter notes). Always > 0, and never above kMaxDivisionBeats.
|
||||
double divisionBeats(Division d);
|
||||
|
||||
// Picker order: shortest rung first, straight/dotted/triplet within each rung. Index is
|
||||
|
||||
Reference in New Issue
Block a user