Close pass four: undetented host curve read, LiveValues guard, ordering assert, static-lane fix, docs
Points toPlain's exponent arm at the undetented curve map so host reads match the editor; adds a sizeof guard plus field-poison test for LiveValues::operator==; skips the model write when an automation value hasn't moved; corrects five stale doc citations.
This commit is contained in:
@@ -70,6 +70,13 @@ struct LiveValues {
|
||||
// The seqlock copies the block as raw bytes, which is only defensible for a plain value type.
|
||||
static_assert(std::is_trivially_copyable_v<LiveValues>,
|
||||
"the live block is copied under a seqlock — it must stay a plain value");
|
||||
// Guards operator== against silent staleness: a member added to the struct above changes this
|
||||
// size, so the assert fails at the new member's own commit instead of leaving a live control
|
||||
// that never reaches a sounding voice with no compiler or test signal. Confirmed 352 bytes,
|
||||
// MSVC 19.44 x64, Release (`SizeProbe<sizeof(LiveValues)>`, an incomplete-template size probe
|
||||
// whose error message reports the value). Bump the literal AND operator== together.
|
||||
static_assert(sizeof(LiveValues) == 352,
|
||||
"a member was added or removed — extend operator== in live_params.cpp to match");
|
||||
|
||||
// FIELD-wise equality, and it must never be "simplified" into a memcmp. LiveValues carries
|
||||
// padding, and nothing gives that padding a determinate value across a copy: NRVO is optional
|
||||
|
||||
@@ -88,21 +88,16 @@ no longer exist.
|
||||
- **Round-trip exactness at arbitrary values is NOT a property here and must not be asserted.**
|
||||
No log map satisfies `toNormalized(toPlain(n)) == n` in double, and demanding it would rule
|
||||
out the taper the range needs. Exactness is required at the defaults; monotonicity everywhere.
|
||||
- **A curve exponent inside the knob detent but not exactly neutral READS BACK as `1.00` on the
|
||||
host, while the stored value keeps its true exponent.** The detent lives in `curve_law`'s
|
||||
norm↔exponent map, and `toPlain` is that map — so an off-detent near-neutral exponent (an
|
||||
overlay knot drag can set one) displays as `1.00` in the host. The editor's own label reads the
|
||||
stored field and shows the true value.
|
||||
**The WRITE path does NOT have this loss, and that is deliberate.** A host write goes through
|
||||
`hostStoredFromNorm`, which skips the detent: the detent is a DRAG affordance — a drag grid
|
||||
- **Both the host's read (`toPlain`) and write (`hostStoredFromNorm`) paths for a curve exponent
|
||||
skip `curve_law`'s knob detent, and that is deliberate** (Daniel, 2026-08-02: continuous ranges
|
||||
stay continuous at the host boundary). The detent is a DRAG affordance only — a drag grid
|
||||
delivers `start - dy/128` and lands on the identity only by luck, so a band wider than one drag
|
||||
step snaps to it — and a lane has no grid. `curveFromKnobNorm` already answers exactly `1.0` at
|
||||
norm `0.5`, so skipping the detent costs nothing in reachability from the host, and applying it
|
||||
would flatten a knot-drawn exponent to `1.0` on any lane pass. `test_param_live`'s
|
||||
`testTheHostSkipsTheCurveDetentAndNothingElse` pins both halves: the host map is the editor's
|
||||
everywhere else, and differs exactly inside the band.
|
||||
**What remains is the DISPLAY divergence above**, which this module already documents as
|
||||
structural and which no change to the frozen `toPlain`/`toNormalized` pair was made to chase.
|
||||
step snaps to it — and a host lane has no grid. `curveFromKnobNorm` already answers exactly
|
||||
`1.0` at norm `0.5`, so skipping the detent costs nothing in reachability from the host. The
|
||||
dial-drag path (`ui::storedFromNorm`/its snap) is the one place the detented map still applies,
|
||||
because that is where the snap earns its place. `test_param_live`'s
|
||||
`testTheHostSkipsTheCurveDetentAndNothingElse` pins the write half; `test_param_format`'s
|
||||
`testAnOffDetentExponentReadsTrueToBothTheHostAndTheEditor` pins the read half.
|
||||
- **Master gain's plain value at norm 0 is `-inf`**, which is outside the declared −60…+24 range
|
||||
on purpose — norm 0 is true silence, not the floor. The formatter prints `-inf` there. The
|
||||
editor additionally SUPPRESSES its unit suffix at that one value (`editor_controls`, the
|
||||
|
||||
@@ -40,9 +40,10 @@ void mergeAutomation(engine::LiveValues& block, AutomationSlot* slots, std::size
|
||||
// Whether a point of `normalized` for a slot in this state actually moves the block. False for a
|
||||
// point equal to a hold that is still standing — the ordinary read-mode steady state, where a
|
||||
// host delivers one point per block over a flat lane segment. Republishing there would drive
|
||||
// `VoiceEngine::refreshLive` over every sounding voice — a `std::pow`, two envelope φ re-fits and
|
||||
// the filter ramp aims, per voice — for a value that did not move. Once the hold has been
|
||||
// RELEASED the answer is true again, because some other writer may have moved the model since.
|
||||
// `VoiceEngine::applyLiveToActive` over every sounding voice — a `std::pow`, two envelope φ
|
||||
// re-fits and the filter ramp aims, per voice — for a value that did not move. Once the hold has
|
||||
// been RELEASED the answer is true again, because some other writer may have moved the model
|
||||
// since.
|
||||
inline bool automationPointMoves(const AutomationSlot& slot, double normalized) {
|
||||
return !slot.held || slot.norm != normalized;
|
||||
}
|
||||
|
||||
@@ -191,7 +191,9 @@ double toPlain(DeckParam deck, double normalized) {
|
||||
return ui::rateRatioFromNorm(normalized, ui::kRateMinRatio, ui::kRateMaxRatio) *
|
||||
kPercentFullScale;
|
||||
case UnitKind::Dimensionless:
|
||||
return util::curveFromKnobNorm(normalized);
|
||||
// Undetented: a host-facing continuous range stays continuous (Daniel, 2026-08-02) —
|
||||
// the detent is a drag affordance, not part of the value law. See hostStoredFromNorm.
|
||||
return util::curveFromKnobNormUndetented(normalized);
|
||||
case UnitKind::Decibels:
|
||||
case UnitKind::Hertz:
|
||||
break; // handled above
|
||||
|
||||
Reference in New Issue
Block a user