fix(m10): item-scope take-FX fingerprint; Windows path case-fold; comments
Item scope now reads the active take's FX chain via TakeFX_* (new fxChainIdentityForItems helper), not the owning track's chain. normalizeSlashes lowercases on _WIN32 for detectParent. Stale comments corrected; case-fold tests added.
This commit is contained in:
+47
-17
@@ -438,8 +438,8 @@ static reasampler::ProvenanceScope provenanceScopeFor(reasampler::CaptureScope s
|
||||
// fingerprint (P1=a) — scope + source mode + exact range + tail + rate + channels +
|
||||
// source track GUIDs + the in-scope source FX-chain identity — so "re-capture from
|
||||
// source" can replay the request and report drift. NEVER a serialized chain to
|
||||
// restore. Item scope folds an empty FX identity (take/item FX are not enumerable
|
||||
// via TrackFX_*); the drift signal then keys on scope+range, which is honest.
|
||||
// restore. Item scope reads the active take's TakeFX chain (via TakeFX_*) per
|
||||
// selected item, combined in item order; Track scope reads the track FX chain.
|
||||
static std::optional<reasampler::Provenance> buildCaptureProvenance(
|
||||
const reasampler::CaptureRequest& req,
|
||||
reasampler::CaptureScope scope,
|
||||
@@ -472,15 +472,27 @@ static std::optional<reasampler::Provenance> buildCaptureProvenance(
|
||||
recipe.sampleRate = req.sampleRate;
|
||||
recipe.channelCount = req.channelCount;
|
||||
recipe.trackGuids = req.trackGuids;
|
||||
// The in-scope FX-chain identity is the per-track chains combined in track order
|
||||
// (Track scope), length-prefixed so distinct partitions never collide. Item scope
|
||||
// has no readable take-FX chain, so each track folds to an empty identity and the
|
||||
// combined result stays stable/honest (drift then keys on scope + range).
|
||||
std::vector<std::string> perTrack;
|
||||
perTrack.reserve(src.sourceTracks.size());
|
||||
for (MediaTrack* tr : src.sourceTracks)
|
||||
perTrack.push_back(reasampler::fxChainIdentityForTrack(tr));
|
||||
recipe.fxChainIdentity = reasampler::combineChainIdentities(perTrack);
|
||||
// The in-scope FX-chain identity:
|
||||
// Track scope — per-track chains combined in track order (TrackFX_*).
|
||||
// Item scope — per-item active-take chains combined in item order (TakeFX_*);
|
||||
// the owning track's FX chain is OUT OF SCOPE for an item capture and must
|
||||
// not be fingerprinted here (it is bypassed during render, not heard).
|
||||
if (scope == reasampler::CaptureScope::Item) {
|
||||
const int n = CountSelectedMediaItems(nullptr);
|
||||
std::vector<MediaItem*> items;
|
||||
items.reserve(static_cast<std::size_t>(n < 0 ? 0 : n));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MediaItem* it = GetSelectedMediaItem(nullptr, i);
|
||||
if (it) items.push_back(it);
|
||||
}
|
||||
recipe.fxChainIdentity = reasampler::fxChainIdentityForItems(items);
|
||||
} else {
|
||||
std::vector<std::string> perTrack;
|
||||
perTrack.reserve(src.sourceTracks.size());
|
||||
for (MediaTrack* tr : src.sourceTracks)
|
||||
perTrack.push_back(reasampler::fxChainIdentityForTrack(tr));
|
||||
recipe.fxChainIdentity = reasampler::combineChainIdentities(perTrack);
|
||||
}
|
||||
|
||||
reasampler::Provenance prov;
|
||||
prov.parentSampleId = *parentId;
|
||||
@@ -883,12 +895,26 @@ static void RunRecaptureFromSource()
|
||||
req.trackGuids = recipe->trackGuids;
|
||||
|
||||
// Read the CURRENT source FX-chain identity BEFORE the render bypasses it, to
|
||||
// compare against the recorded identity for drift reporting.
|
||||
std::vector<std::string> perTrackNow;
|
||||
perTrackNow.reserve(sourceTracks.size());
|
||||
for (MediaTrack* tr : sourceTracks)
|
||||
perTrackNow.push_back(reasampler::fxChainIdentityForTrack(tr));
|
||||
const std::string currentIdentity = reasampler::combineChainIdentities(perTrackNow);
|
||||
// compare against the recorded identity for drift reporting. Mirror the same
|
||||
// scope split as buildCaptureProvenance: item scope reads take FX via TakeFX_*;
|
||||
// track scope reads the track FX chain via TrackFX_*.
|
||||
std::string currentIdentity;
|
||||
if (scope == reasampler::CaptureScope::Item) {
|
||||
const int n = CountSelectedMediaItems(nullptr);
|
||||
std::vector<MediaItem*> items;
|
||||
items.reserve(static_cast<std::size_t>(n < 0 ? 0 : n));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MediaItem* it = GetSelectedMediaItem(nullptr, i);
|
||||
if (it) items.push_back(it);
|
||||
}
|
||||
currentIdentity = reasampler::fxChainIdentityForItems(items);
|
||||
} else {
|
||||
std::vector<std::string> perTrackNow;
|
||||
perTrackNow.reserve(sourceTracks.size());
|
||||
for (MediaTrack* tr : sourceTracks)
|
||||
perTrackNow.push_back(reasampler::fxChainIdentityForTrack(tr));
|
||||
currentIdentity = reasampler::combineChainIdentities(perTrackNow);
|
||||
}
|
||||
const bool drifted = (currentIdentity != recipe->fxChainIdentity);
|
||||
|
||||
// Render (bank-only; renderOffline never touches the timeline).
|
||||
@@ -918,6 +944,10 @@ static void RunRecaptureFromSource()
|
||||
updated.captureTempo = res.sample.captureTempo;
|
||||
updated.trackGuids = res.sample.trackGuids;
|
||||
updated.createdTimestamp = res.sample.createdTimestamp;
|
||||
// NOTE: levels, clipped, and lengthBeats are carried from the original (via the
|
||||
// *orig copy above) because the offline backend does not populate them today
|
||||
// (res.sample leaves them at defaults). If a later milestone populates these
|
||||
// fields at capture time, refresh them here from res.sample instead.
|
||||
reasampler::Provenance prov;
|
||||
prov.parentSampleId = recordedParentId;
|
||||
prov.fxChainSnapshot = reasampler::buildFingerprint(refreshed);
|
||||
|
||||
Reference in New Issue
Block a user