instrument: fix AHD node-tracking/tie-break/live-latch defects and close staged-envelope-curve test gaps

This commit is contained in:
2026-07-31 09:52:17 -04:00
parent d60ab1524a
commit 2fa1405b06
27 changed files with 360 additions and 86 deletions
+9 -5
View File
@@ -14,15 +14,13 @@
namespace reasampler::instrument::map {
using reasampler::wire::ByteReader;
using reasampler::wire::asU64;
using reasampler::wire::bitsToDouble;
using reasampler::wire::doubleToBits;
using reasampler::wire::putLE;
namespace {
// Signed 64-bit values ride the wire as their two's-complement unsigned image.
std::uint64_t asU64(std::int64_t v) { return static_cast<std::uint64_t>(v); }
// Emit the OVERRIDE trio shared by the v2..v7 per-zone record and the v8 single record, so
// the two shapes cannot drift byte-for-byte.
void putOverrides(std::vector<std::uint8_t>& out, const InstrumentParams& p) {
@@ -149,8 +147,14 @@ double readCurveExponent(ByteReader& r) {
}
void readAhd(ByteReader& r, AhdSeconds& a) {
a.attackSeconds = bitsToDouble(r.u64());
a.decaySeconds = bitsToDouble(r.u64());
// attackSeconds/decaySeconds reach resolvePlay's static_cast<std::int64_t> (sample_map.cpp)
// unguarded — UB on NaN, and on a large-enough finite value — so a corrupt/non-finite wire
// value degrades to 0 seconds rather than reaching that cast, the same guard readSecondsPlayTail
// and the v9 filter tail already apply to their own wall-clock fields.
const double attack = bitsToDouble(r.u64());
const double decay = bitsToDouble(r.u64());
a.attackSeconds = std::isfinite(attack) ? attack : 0.0;
a.decaySeconds = std::isfinite(decay) ? decay : 0.0;
const double frac = bitsToDouble(r.u64());
a.holdFraction = std::isfinite(frac) ? frac : 0.0;
a.attackCurve = readCurveExponent(r);