Bake window: derived note lengths carry exact durations, not ladder rungs — a long take is no longer cut at 384 beats
Hold keeps its picker. Also: one home for the %-fold, duration-ordered Hold travel, and a corrupt tail degrades to absent rather than fabricating one.
This commit is contained in:
@@ -85,6 +85,47 @@ OffsetAmount redenominate(OffsetAmount amount, Denomination to, Tempo tempo);
|
||||
OffsetAmount withMsView(OffsetAmount amount, double ms, Tempo tempo);
|
||||
OffsetAmount withBeatsView(OffsetAmount amount, double beats, Tempo tempo);
|
||||
|
||||
// The largest exact note length, in seconds — the ms ceiling restated in the unit an exact
|
||||
// length is entered in, so a length and an offset cap at the same instant.
|
||||
inline constexpr double kMaxLengthSeconds = msToSeconds(kMaxConvertibleMagnitude);
|
||||
|
||||
class NoteLength;
|
||||
|
||||
// The two doors. A DERIVED length is exact: `lengthOfSeconds` is what every computed duration
|
||||
// takes, and rounding one onto the ladder is what truncates a source longer than the ladder's
|
||||
// top rung. A PICKED length is a `Division`, because a picker's rungs are the point. Both
|
||||
// clamp: a negative or NaN duration names no length and becomes zero, a magnitude past
|
||||
// kMaxLengthSeconds clamps to it, and `makeDivision` already holds the rung's own domain.
|
||||
NoteLength lengthOfDivision(Division division);
|
||||
NoteLength lengthOfSeconds(double seconds);
|
||||
|
||||
// One length, in whichever denomination its door established. There is no accessor per
|
||||
// denomination: `noteLengthSeconds` resolves both, so no reader can pick the wrong one.
|
||||
class NoteLength {
|
||||
public:
|
||||
NoteLength() = default; // a quarter note — Division's own default
|
||||
|
||||
private:
|
||||
NoteLength(Division division, double seconds, bool exact)
|
||||
: division_(division), seconds_(seconds), exact_(exact) {}
|
||||
friend NoteLength lengthOfDivision(Division division);
|
||||
friend NoteLength lengthOfSeconds(double seconds);
|
||||
friend double noteLengthSeconds(NoteLength length, Tempo tempo);
|
||||
friend bool operator==(NoteLength a, NoteLength b);
|
||||
|
||||
Division division_{};
|
||||
double seconds_ = 0.0;
|
||||
bool exact_ = false;
|
||||
};
|
||||
|
||||
static_assert(!std::is_constructible_v<NoteLength, Division>,
|
||||
"lengthOfDivision must be the only way to give a NoteLength a rung");
|
||||
|
||||
bool operator==(NoteLength a, NoteLength b);
|
||||
bool operator!=(NoteLength a, NoteLength b);
|
||||
|
||||
double noteLengthSeconds(NoteLength length, Tempo tempo);
|
||||
|
||||
// Two types rather than one carrying an anchor field: the anchor is then unswappable at
|
||||
// compile time. Sign is uniform — positive is later in time — so a capture that opens before
|
||||
// the note is a negative start offset, and a negative end offset truncates before release.
|
||||
@@ -114,7 +155,7 @@ static_assert(!std::is_convertible_v<OffsetAmount, StartOffset>,
|
||||
"the anchor constructor must stay explicit");
|
||||
|
||||
struct NoteProgram {
|
||||
Division length{};
|
||||
NoteLength length{};
|
||||
StartOffset start{};
|
||||
EndOffset end{};
|
||||
Velocity velocity{};
|
||||
|
||||
Reference in New Issue
Block a user