instrument: fix AHD node-tracking/tie-break/live-latch defects and close staged-envelope-curve test gaps
This commit is contained in:
@@ -21,7 +21,7 @@ reasampler_test(bank_sync LINK bank_sync)
|
||||
# the format already keeps on independent version axes (see component_state_io.h).
|
||||
reasampler_pure_library(component_state_io
|
||||
SOURCES component_state_io.cpp params_payload.cpp
|
||||
LINK PUBLIC velocity_curve master_gain)
|
||||
LINK PUBLIC velocity_curve master_gain curve_law)
|
||||
# Links only component_state_io, deliberately no sampler_core/pitch_shift: the structural
|
||||
# proof the codec is engine-free, which is what keeps engine object code out of the extension.
|
||||
reasampler_test(component_state_io LINK component_state_io)
|
||||
@@ -29,7 +29,7 @@ reasampler_test(component_state_io LINK component_state_io)
|
||||
# The mapping's product is plain SampleData, so the voice engine is not a dependency.
|
||||
reasampler_pure_library(sample_map
|
||||
SOURCES sample_map.cpp
|
||||
LINK PUBLIC bank_book wav_codec velocity_curve peaks)
|
||||
LINK PUBLIC bank_book wav_codec velocity_curve peaks curve_law)
|
||||
# Links only sample_map + component_state_io: the same plain-data-boundary proof, spanning
|
||||
# both halves of the mapping/codec split where the frozen-format assertions live.
|
||||
reasampler_test(sample_map LINK sample_map component_state_io)
|
||||
|
||||
@@ -17,15 +17,13 @@ namespace reasampler::instrument::map {
|
||||
|
||||
using engine::masterGainMaxLinear;
|
||||
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); }
|
||||
|
||||
// Apply a payload read to the state: the adoption rule (a retired payload's first zone
|
||||
// supersedes the envelope's selection id) lives here, once.
|
||||
void applyPayload(ComponentState& out, PayloadRead read) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -203,8 +203,8 @@ DecodedPcm decodeChannels(const std::vector<AudioSample>& interleaved,
|
||||
}
|
||||
|
||||
PlayParams resolvePlay(const PlaySeconds& stored, int sampleRate) {
|
||||
// seconds -> frames at the LIVE rate; source-timeline quantities (trigger %-length +
|
||||
// fades) carry through untouched, already frames/fractions.
|
||||
// seconds -> frames at the LIVE rate; the source-timeline quantity (trigger %-length)
|
||||
// carries through untouched, already a fraction.
|
||||
assert(sampleRate > 0 && "resolvePlay: sampleRate must be > 0 (programming error)");
|
||||
const double sr = sampleRate > 0 ? static_cast<double>(sampleRate) : 1.0; // 1.0 avoids div-by-zero; assert fires first
|
||||
const auto secToFrames = [sr](double sec) {
|
||||
|
||||
Reference in New Issue
Block a user