instrument: spline EGs — hard points on the one shared spline, a drawn contour per envelope beside its staged state, payload v13

This commit is contained in:
2026-07-31 21:33:59 -04:00
parent f115904e4f
commit e44bd42dd9
33 changed files with 1739 additions and 364 deletions
+20
View File
@@ -72,6 +72,26 @@ void Voice::start(int note, int velocity, const SampleData& sample, bool declick
loop_ = instrument::engine::loop::resolveLoop(sample.loop, sample.loopCrossfadeFrames,
frameCount, playMode_ == PlayMode::Gate);
// Bind whichever EGs are drawn. Rebound on EVERY note-on rather than cached: a reload hands
// the engine a fresh SampleData, so a stale pointer into the previous one is the bug this
// avoids. A Staged EG clears its cursor, which is what keeps the per-sample path off the
// spline branch entirely.
splineScale_ = frameCount > 0 ? 1.0 / static_cast<double>(frameCount) : 0.0;
if (p.ampSpline.mode == EnvMode::Spline) ampSplineCur_.bind(p.ampSpline.contour);
else ampSplineCur_.clear();
if (p.pitchEnv.enabled && p.pitchSpline.mode == EnvMode::Spline) {
pitchSplineCur_.bind(p.pitchSpline.contour);
pitchSplineDepth_ = p.pitchEnv.peakSemitones;
} else {
pitchSplineCur_.clear();
pitchSplineDepth_ = 0.0;
}
if (p.filter.enabled && p.filterSpline.mode == EnvMode::Spline) {
filterSplineCur_.bind(p.filterSpline.contour);
} else {
filterSplineCur_.clear();
}
// Amplitude envelope: Gate = AHDSR (all five fields read from play.adsr, resolved to
// frames from stored seconds at load time); Trigger = the staged AHD over the % play span.
const std::int64_t postStart = frameCount - start; // >= 1 (start clamped < frameCount)