Close derived-bake-window review findings: NaN-guard remaining wire doubles, pin Trigger-span agreement, retire dead quantizer
Guards params_payload.cpp's filter-tail seconds and both keyTrack sites against NaN; pins Voice::start's Trigger-span formula against trigger_seam; retires unused shortestDivisionAtLeast.
This commit is contained in:
@@ -954,25 +954,41 @@ static void testV13HardFlagCountThatStrandsAlignmentLeavesTheHoldAbsentNotFabric
|
||||
|
||||
// Numeric domains are established at the DOOR, not at each consumer. A NaN pitch depth reaches
|
||||
// the bake's pow() and the voice's ratio multiply; a NaN %-length and a NaN stage time reach
|
||||
// narrowing casts that are undefined on one; and a root override outside MIDI range makes the
|
||||
// narrowing casts that are undefined on one; a NaN keyTrack reaches keyTrackedRatio ->
|
||||
// baseRatio_ -> readPos_'s per-sample static_cast<std::int64_t> (voice.h); a NaN v9 filter
|
||||
// env second reaches secToFrames the same way the trigAhd/filter.trigEnv pair already covered
|
||||
// by testNonFiniteAhdSecondsLiftToZero do; and a root override outside MIDI range makes the
|
||||
// bake's render note and the sample's own root disagree, which is a read rate other than 1 and
|
||||
// therefore a window sized in the truncating direction.
|
||||
static void testOutOfDomainWireValuesAreBoundedAtTheCodec() {
|
||||
const PlaySeconds defaults;
|
||||
const InstrumentParams paramDefaults;
|
||||
ComponentState in;
|
||||
in.selectionId = "pad";
|
||||
in.params.keyTrack = 0.5; // a neighbouring field, to show the guards are per-field
|
||||
in.params.keyTrack = std::numeric_limits<double>::quiet_NaN();
|
||||
in.params.rootOverride = 9999;
|
||||
in.params.play.pitchEnv.peakSemitones = std::numeric_limits<double>::quiet_NaN();
|
||||
in.params.play.trigger.lengthFraction = std::numeric_limits<double>::quiet_NaN();
|
||||
in.params.play.adsr.releaseSeconds = std::numeric_limits<double>::infinity();
|
||||
in.params.play.filter.enabled = true;
|
||||
in.params.play.filter.env.attackSeconds = std::numeric_limits<double>::quiet_NaN();
|
||||
in.params.play.filter.env.holdSeconds = std::numeric_limits<double>::infinity();
|
||||
in.params.play.filter.env.decaySeconds = -std::numeric_limits<double>::infinity();
|
||||
in.params.play.filter.env.sustainLevel = std::numeric_limits<double>::quiet_NaN();
|
||||
in.params.play.filter.env.releaseSeconds = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
const ComponentState out = deserializeComponentState(serializeComponentState(in), 48000.0);
|
||||
CHECK(out.params.rootOverride && *out.params.rootOverride == 127);
|
||||
CHECK(out.params.play.pitchEnv.peakSemitones == defaults.pitchEnv.peakSemitones);
|
||||
CHECK(out.params.play.trigger.lengthFraction == defaults.trigger.lengthFraction);
|
||||
CHECK(out.params.play.adsr.releaseSeconds == defaults.adsr.releaseSeconds);
|
||||
CHECK(out.params.keyTrack == 0.5);
|
||||
CHECK(out.params.keyTrack == paramDefaults.keyTrack);
|
||||
CHECK(out.params.play.filter.enabled); // the fallback is per-field, not per-record
|
||||
CHECK(out.params.play.filter.env.attackSeconds == defaults.filter.env.attackSeconds);
|
||||
CHECK(out.params.play.filter.env.holdSeconds == defaults.filter.env.holdSeconds);
|
||||
CHECK(out.params.play.filter.env.decaySeconds == defaults.filter.env.decaySeconds);
|
||||
CHECK(out.params.play.filter.env.sustainLevel == defaults.filter.env.sustainLevel);
|
||||
CHECK(out.params.play.filter.env.releaseSeconds == defaults.filter.env.releaseSeconds);
|
||||
|
||||
ComponentState low = in;
|
||||
low.params.rootOverride = -5;
|
||||
@@ -980,6 +996,20 @@ static void testOutOfDomainWireValuesAreBoundedAtTheCodec() {
|
||||
CHECK(lowOut.params.rootOverride && *lowOut.params.rootOverride == 0);
|
||||
}
|
||||
|
||||
// The keyTrack guard's OTHER site: a pre-v7 (legacy zone-list) blob's own keyTrack field
|
||||
// (params_payload.cpp's readLegacyZonePayload, pv >= 6) is a second, independent read of the
|
||||
// same wire double — same hazard, same fallback, must not be missed just because the v8+
|
||||
// single-record reader above was fixed.
|
||||
static void testLegacyZoneKeyTrackNaNLiftsToDefault() {
|
||||
legacy::Zone z;
|
||||
z.sampleId = "kick";
|
||||
z.keyTrack = std::numeric_limits<double>::quiet_NaN();
|
||||
|
||||
const ComponentState out =
|
||||
deserializeComponentState(legacy::envelopeWithZones("kick", {z}, 7), 48000.0);
|
||||
CHECK(out.params.keyTrack == InstrumentParams{}.keyTrack);
|
||||
}
|
||||
|
||||
// A hard-flag tail truncated mid-COUNT-FIELD (only 2 of its 4 length bytes present, and
|
||||
// nothing else after) is a different failure shape than a declared-huge count: the u32 read
|
||||
// itself fails, tripping r.ok inside readHardFlags rather than its own bound check. That must
|
||||
@@ -1935,6 +1965,7 @@ int main() {
|
||||
testV13HardFlagOutOfBoundsCountSurvivesWithoutWipingTheRecord();
|
||||
testV13HardFlagCountThatStrandsAlignmentLeavesTheHoldAbsentNotFabricated();
|
||||
testOutOfDomainWireValuesAreBoundedAtTheCodec();
|
||||
testLegacyZoneKeyTrackNaNLiftsToDefault();
|
||||
testV13HardFlagTailTruncatedMidCountSurvivesWithoutWipingTheRecord();
|
||||
testBakeHoldRoundTripsAndDisturbsNothingElse();
|
||||
testV13BlobLiftsToTheDefaultHold();
|
||||
|
||||
Reference in New Issue
Block a user