M10: provenance populate + re-capture from source (bank-only)
Pure provenance core (recipe fingerprint, FX-chain identity, parent detection) + shell reads; capture stamps provenance on resample-from-sample; re-capture regenerates a provenanced sample from its source, never touching the timeline. Adds BankIndex/BankBook in-place update. CTest-covered.
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
// provenance_shell.cpp — the REAPER reads behind Milestone 10. See provenance_shell.h.
|
||||
//
|
||||
// Compiled into the reaper_reasampler MODULE. Includes reaper_plugin_functions.h
|
||||
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp is the one TU that defines the API pointers
|
||||
// (CLAUDE.md §contract). Every REAPER symbol used here is verified against
|
||||
// vendor/reaper-sdk/sdk/reaper_plugin_functions.h:
|
||||
// * TrackFX_GetCount(MediaTrack*) (~7283)
|
||||
// * TrackFX_GetFXName(MediaTrack*, int, char*, int) -> bool (~7356)
|
||||
// * TrackFX_GetFXGUID(MediaTrack*, int) -> GUID* (~7348)
|
||||
// * TrackFX_GetEnabled(MediaTrack*, int) -> bool (~7291)
|
||||
// * CountSelectedMediaItems / GetSelectedMediaItem (selection reads)
|
||||
// * GetActiveTake(MediaItem*) -> MediaItem_Take* (active take)
|
||||
// * GetMediaItemTake_Source(MediaItem_Take*) -> PCM_source* (~2053)
|
||||
// * GetMediaSourceFileName(PCM_source*, char*, int) (~2141)
|
||||
// * CountTracks / GetTrack (track scan)
|
||||
// * guidToString (via track_guid)
|
||||
|
||||
#include "provenance_shell.h"
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include "bank_book.h" // BankBook, Bank, BankIndex::all
|
||||
#include "capture_paths.h" // resolveBankFile, normalizeSlashes
|
||||
#include "track_guid.h" // guidString — the ONE canonical GUID key formatter
|
||||
|
||||
#define REAPERAPI_MINIMAL
|
||||
#define REAPERAPI_WANT_TrackFX_GetCount
|
||||
#define REAPERAPI_WANT_TrackFX_GetFXName
|
||||
#define REAPERAPI_WANT_TrackFX_GetFXGUID
|
||||
#define REAPERAPI_WANT_TrackFX_GetEnabled
|
||||
#define REAPERAPI_WANT_CountSelectedMediaItems
|
||||
#define REAPERAPI_WANT_GetSelectedMediaItem
|
||||
#define REAPERAPI_WANT_CountTrackMediaItems
|
||||
#define REAPERAPI_WANT_GetTrackMediaItem
|
||||
#define REAPERAPI_WANT_GetMediaItemInfo_Value
|
||||
#define REAPERAPI_WANT_GetActiveTake
|
||||
#define REAPERAPI_WANT_GetMediaItemTake_Source
|
||||
#define REAPERAPI_WANT_GetMediaSourceFileName
|
||||
#define REAPERAPI_WANT_CountTracks
|
||||
#define REAPERAPI_WANT_GetTrack
|
||||
#define REAPERAPI_WANT_guidToString
|
||||
#include "reaper_plugin_functions.h"
|
||||
|
||||
namespace reasampler {
|
||||
|
||||
std::string fxChainIdentityForTrack(MediaTrack* tr) {
|
||||
if (!tr) return fxChainIdentity({});
|
||||
std::vector<FxIdentityEntry> rows;
|
||||
const int n = TrackFX_GetCount(tr);
|
||||
rows.reserve(static_cast<std::size_t>(n < 0 ? 0 : n));
|
||||
for (int i = 0; i < n; ++i) {
|
||||
FxIdentityEntry e;
|
||||
char nameBuf[512] = {0};
|
||||
if (TrackFX_GetFXName(tr, i, nameBuf, static_cast<int>(sizeof(nameBuf))))
|
||||
e.name = nameBuf;
|
||||
// Per-instance GUID: the stable identity of THIS FX in the chain, so swapping
|
||||
// one FX for another of the same name registers as drift. guidToString needs a
|
||||
// >=64-char destination (SDK contract).
|
||||
if (GUID* g = TrackFX_GetFXGUID(tr, i)) {
|
||||
char gb[64] = {0};
|
||||
guidToString(g, gb);
|
||||
e.guid = gb;
|
||||
}
|
||||
e.enabled = TrackFX_GetEnabled(tr, i);
|
||||
rows.push_back(std::move(e));
|
||||
}
|
||||
return fxChainIdentity(rows);
|
||||
}
|
||||
|
||||
namespace {
|
||||
// The active take source file of one item, normalized. Empty if unresolvable.
|
||||
std::string itemSourceFile(MediaItem* it) {
|
||||
if (!it) return {};
|
||||
MediaItem_Take* take = GetActiveTake(it);
|
||||
if (!take) return {}; // empty (MIDI-less?) / no active take -> unresolvable
|
||||
PCM_source* src = GetMediaItemTake_Source(take);
|
||||
if (!src) return {};
|
||||
char buf[4096] = {0};
|
||||
GetMediaSourceFileName(src, buf, static_cast<int>(sizeof(buf)));
|
||||
return normalizeSlashes(std::string(buf));
|
||||
}
|
||||
} // namespace
|
||||
|
||||
std::vector<std::string> selectedItemSourceFiles() {
|
||||
std::vector<std::string> files;
|
||||
const int n = CountSelectedMediaItems(nullptr); // nullptr = active project
|
||||
for (int i = 0; i < n; ++i) {
|
||||
std::string f = itemSourceFile(GetSelectedMediaItem(nullptr, i));
|
||||
if (!f.empty()) files.push_back(std::move(f)); // omit unresolvable (never empty)
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<std::string> trackItemSourceFiles(const std::vector<MediaTrack*>& tracks,
|
||||
double startSeconds, double endSeconds) {
|
||||
std::vector<std::string> files;
|
||||
for (MediaTrack* tr : tracks) {
|
||||
if (!tr) continue;
|
||||
const int n = CountTrackMediaItems(tr);
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MediaItem* it = GetTrackMediaItem(tr, i);
|
||||
if (!it) continue;
|
||||
const double pos = GetMediaItemInfo_Value(it, "D_POSITION");
|
||||
const double len = GetMediaItemInfo_Value(it, "D_LENGTH");
|
||||
// Positive overlap with the capture range (a zero-length touch is not an
|
||||
// overlap): item [pos, pos+len) intersects [startSeconds, endSeconds).
|
||||
if (pos < endSeconds && (pos + len) > startSeconds) {
|
||||
std::string f = itemSourceFile(it);
|
||||
if (!f.empty()) files.push_back(std::move(f));
|
||||
}
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
std::vector<BankFileRef> bankFileRefs(const BankBook& book, const std::string& projectDir) {
|
||||
std::vector<BankFileRef> refs;
|
||||
for (const Bank& b : book.banks()) {
|
||||
for (const Sample& s : b.index.all()) {
|
||||
BankFileRef ref;
|
||||
ref.sampleId = s.id;
|
||||
// Resolve to the same normalized absolute form selectedItemSourceFiles
|
||||
// produces, so detectParent compares like-for-like. Empty projectDir /
|
||||
// relativePath -> empty absolutePath (never a false match).
|
||||
ref.absolutePath = normalizeSlashes(resolveBankFile(projectDir, s.relativePath));
|
||||
refs.push_back(std::move(ref));
|
||||
}
|
||||
}
|
||||
return refs;
|
||||
}
|
||||
|
||||
MediaTrack* trackByGuid(const std::string& guid) {
|
||||
if (guid.empty()) return nullptr;
|
||||
const int n = CountTracks(nullptr); // nullptr = active project; excludes master
|
||||
for (int i = 0; i < n; ++i) {
|
||||
MediaTrack* tr = GetTrack(nullptr, i);
|
||||
if (!tr) continue;
|
||||
if (guidString(tr) == guid) return tr;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace reasampler
|
||||
Reference in New Issue
Block a user