note: close out the model — correct an inert mutation claim, retag four non-discriminating assertions, assert Tempo's closure, fix three doc/test accuracy gaps
No behavior change; verification-record corrections and one static_assert.
This commit is contained in:
@@ -31,14 +31,16 @@ diverge: the capture-signal popup that edits it and the bake that renders it.
|
||||
the ladder ever gained a rung or a modifier.
|
||||
- **An offset stores the denomination it was entered in** — see `OffsetAmount` in
|
||||
`note_program.h` for why.
|
||||
- **Every value type establishes its domain at construction, and nothing downstream can
|
||||
fail.** `Tempo::fromBpm` rejects, alone, because an unusable BPM has no nearest usable one
|
||||
to fall to. `Division`, `OffsetAmount`, and `Velocity` clamp, because an off-ladder rung,
|
||||
an unrepresentable magnitude, and an out-of-range velocity each do. Each has exactly one
|
||||
door (`makeDivision`, `offsetOf`, `Velocity::of`) and a private constructor behind it, so
|
||||
an out-of-domain value cannot be held, only passed in. That is what lets every reader
|
||||
branch without a fallback, equality compare fields raw, and `resolveNote` return finite
|
||||
times for every constructible input with no failure path and no validity flag.
|
||||
- **Every value type establishes its domain at construction, so every field `resolveNote`
|
||||
returns is finite for every constructible program and tempo.** `Tempo::fromBpm` rejects,
|
||||
alone, because an unusable BPM has no nearest usable one to fall to. `Division`,
|
||||
`OffsetAmount`, and `Velocity` clamp, because an off-ladder rung, an unrepresentable
|
||||
magnitude, and an out-of-range velocity each do. Each has exactly one door (`makeDivision`,
|
||||
`offsetOf`, `Velocity::of`); `Division` and `OffsetAmount` block any other path with a
|
||||
private value constructor, `Velocity` with a private member that only `of()` writes —
|
||||
either way an out-of-domain value cannot be held, only passed in. That is what lets every
|
||||
reader branch without a fallback, equality compare fields raw, and `resolveNote` return
|
||||
finite times for every constructible input with no failure path and no validity flag.
|
||||
- **The module will not tell a caller a record is junk, because a junk record cannot exist
|
||||
here.** Corruption is only visible where raw bytes are: a codec sees both the bytes it
|
||||
read and the value construction produced, and reporting the difference is the codec's job.
|
||||
@@ -79,6 +81,9 @@ diverge: the capture-signal popup that edits it and the bake that renders it.
|
||||
Both are legal; `resolveNote` only refuses to invert the window.
|
||||
- **ms <-> beats round-trips are lossless to double precision, not bit-identical.** The
|
||||
conversion is a multiply/divide pair; compare with an epsilon.
|
||||
- **`Division` and `OffsetAmount` are trivially copyable, so a `memcpy` of a wire record
|
||||
bypasses every door.** Decode field-by-field through `makeDivision`/`offsetOf` (the pattern
|
||||
`src/core/wire/bytes.h` already uses) instead — never `memcpy` raw bytes into either type.
|
||||
- **Editing the ms field of a beats-stored offset stores beats, and the ms readout will then
|
||||
move with the tempo.** `withMsView` keeps the stored denomination on purpose, so typing 250
|
||||
into the ms field of a beats offset stores 0.5 beats at 120 BPM. That is the intended
|
||||
|
||||
@@ -64,8 +64,10 @@ double offsetSeconds(OffsetAmount amount, Tempo tempo) {
|
||||
}
|
||||
|
||||
OffsetAmount redenominate(OffsetAmount amount, Denomination to, Tempo tempo) {
|
||||
// Route the requested target through the same door a stored denomination goes through,
|
||||
// so an out-of-enum target lands where a corrupt stored one does.
|
||||
// Defense-in-depth, not a discriminating guard: the branch below already treats any
|
||||
// non-Beats target as Milliseconds, so an unnamed `to` resolves the same way whether or
|
||||
// not it is routed through offsetOf first. Kept because a future third denomination
|
||||
// would make this the one place that still pins it.
|
||||
const Denomination target = offsetOf(0.0, to).denomination();
|
||||
if (amount.denomination() == target) return amount;
|
||||
return target == Denomination::Beats ? offsetFromBeats(offsetBeats(amount, tempo))
|
||||
|
||||
@@ -43,5 +43,7 @@ private:
|
||||
|
||||
static_assert(!std::is_default_constructible_v<Tempo>,
|
||||
"Tempo must not be constructible without a validated BPM");
|
||||
static_assert(!std::is_constructible_v<Tempo, double>,
|
||||
"fromBpm must be the only way to give a Tempo a value");
|
||||
|
||||
} // namespace reasampler::instrument::note
|
||||
|
||||
Reference in New Issue
Block a user