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:
@@ -1,12 +1,12 @@
|
||||
// note_program — the programmed capture signal: one note length, one velocity, two anchored
|
||||
// offsets, and the one resolver a preview and a bake must share.
|
||||
//
|
||||
// Resolved times are rate-free SECONDS relative to note-on; the caller converts against the
|
||||
// live sample rate.
|
||||
// Resolved times are rate-free seconds (this directory's CLAUDE.md: the standing ruling).
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
#include "core/instrument/note/musical_division.h"
|
||||
#include "core/instrument/note/tempo.h"
|
||||
@@ -52,6 +52,12 @@ double offsetSeconds(OffsetAmount amount, Tempo tempo);
|
||||
// The unit toggle: the same instant restated in the other denomination.
|
||||
OffsetAmount redenominate(OffsetAmount amount, Denomination to, Tempo tempo);
|
||||
|
||||
// Edit the magnitude via its non-stored view without changing which denomination is stored
|
||||
// — a popup's ms and beats fields both stay live no matter which one the offset was entered
|
||||
// in; only `redenominate` changes the stored denomination itself.
|
||||
OffsetAmount withMsView(OffsetAmount amount, double ms, Tempo tempo);
|
||||
OffsetAmount withBeatsView(OffsetAmount amount, double beats, 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.
|
||||
@@ -75,6 +81,11 @@ private:
|
||||
OffsetAmount amount_{};
|
||||
};
|
||||
|
||||
static_assert(!std::is_constructible_v<StartOffset, EndOffset>,
|
||||
"StartOffset and EndOffset must not be interchangeable at compile time");
|
||||
static_assert(!std::is_convertible_v<OffsetAmount, StartOffset>,
|
||||
"the anchor constructor must stay explicit");
|
||||
|
||||
struct NoteProgram {
|
||||
Division length{};
|
||||
StartOffset start{};
|
||||
@@ -89,7 +100,10 @@ struct ResolvedNote {
|
||||
double noteOffSeconds = 0.0; // == the note's sounding length, note-on being 0
|
||||
double captureStartSeconds = 0.0; // negative when the capture opens before the note
|
||||
double captureEndSeconds = 0.0;
|
||||
std::uint8_t velocity = 1;
|
||||
std::uint8_t velocity = 100; // resolveNote always overwrites this; matches Velocity's own default
|
||||
// True when the programmed end offset inverted the window and resolveNote collapsed it
|
||||
// to zero length instead — lets a popup explain an empty window rather than just show one.
|
||||
bool windowCollapsed = false;
|
||||
|
||||
double captureLengthSeconds() const { return captureEndSeconds - captureStartSeconds; }
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user