instrument: latch the note done at the read-head run-off, fit the migrated fades, and lift the curve dial and overlay selection into pure modules
This commit is contained in:
@@ -91,8 +91,26 @@ namespace reasampler::instrument::map {
|
||||
// seconds at the project rate the reader is handed. v10 writes ZERO into both — the values
|
||||
// live in the AHD now, so a DOWNGRADE to a pre-v10 binary loses the Trigger amp shape.
|
||||
//
|
||||
// LOSSY UNDER A RATE MISMATCH. The fades were SOURCE frames and the AHD stores wall-clock
|
||||
// seconds, so the lift divides by the PROJECT rate while the build later multiplies by the
|
||||
// DECODE rate: a file whose own rate differs from the project's comes back scaled by that
|
||||
// ratio (a 441-frame fade on a 44.1 kHz file in a 48 kHz project resolves to 405 source
|
||||
// frames, ~8% short). The codec cannot close this — it never sees the file — and deferring the
|
||||
// lift to build time would mean carrying the retired fade pair through the parameter set,
|
||||
// reintroducing the mechanism the AHD replaced.
|
||||
//
|
||||
// A truncated/unknown/empty payload yields the DEFAULT parameter set.
|
||||
|
||||
// The exponents the lifted fades take. The AHD's curve law is phi^p (core/util/curve_law.h),
|
||||
// which cannot reproduce the retired pair's equal-power sin/cos exactly at ANY exponent — so
|
||||
// the lift takes the MINIMAX fit rather than the linear neutral, which is free (one constant,
|
||||
// written once here) and several times closer. The two differ because the two stages fit
|
||||
// different forms: attack fits phi^p to sin(pi*phi/2), decay fits 1 - t^q to cos(pi*t/2).
|
||||
// The measured bounds are asserted in tests/test_component_state_io.cpp. Every OTHER curve on
|
||||
// a migrated blob still lifts to the neutral — only the fades had a prior shape to reproduce.
|
||||
inline constexpr double kTriggerFadeLiftAttackCurve = 0.6133;
|
||||
inline constexpr double kTriggerFadeLiftDecayCurve = 1.7437;
|
||||
|
||||
inline constexpr std::uint32_t kPerformanceStateVersion = 2;
|
||||
|
||||
// The params-payload format version and its detection marker. The marker is a high sentinel
|
||||
|
||||
@@ -61,15 +61,17 @@ void putAhd(std::vector<std::uint8_t>& out, const AhdSeconds& a) {
|
||||
}
|
||||
// THE lift of the retired Trigger fade pair onto the AHD that replaced it: Attack takes the
|
||||
// fade-in, Decay the fade-out, Hold the whole remainder — so a zero fade-out lands Decay = 0
|
||||
// and the abrupt end an old instance could express stays representable. The fades were SOURCE
|
||||
// frames and the AHD stores wall-clock seconds, so the conversion goes through the same
|
||||
// project rate the v3 lift already uses. A v10-or-newer blob overwrites this from its own tail.
|
||||
// and the abrupt end an old instance could express stays representable. The seconds conversion
|
||||
// and its rate-mismatch bound, and the two fitted exponents, are documented in
|
||||
// component_state_io.h. A v10-or-newer blob overwrites all five fields from its own tail.
|
||||
void liftTriggerFades(std::int64_t fadeInFrames, std::int64_t fadeOutFrames, double projectRate,
|
||||
AhdSeconds& out) {
|
||||
const double rate = projectRate > 0.0 ? projectRate : 1.0;
|
||||
out.attackSeconds = static_cast<double>(fadeInFrames > 0 ? fadeInFrames : 0) / rate;
|
||||
out.decaySeconds = static_cast<double>(fadeOutFrames > 0 ? fadeOutFrames : 0) / rate;
|
||||
out.holdFraction = 1.0;
|
||||
out.attackCurve = kTriggerFadeLiftAttackCurve;
|
||||
out.decayCurve = kTriggerFadeLiftDecayCurve;
|
||||
}
|
||||
|
||||
// Read the play tail (v5 shape onward) into `p`. Shared by the legacy zone reader and the
|
||||
|
||||
Reference in New Issue
Block a user