// 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) // * TakeFX_GetCount(MediaItem_Take*) (~6710) // * TakeFX_GetFXName(MediaItem_Take*, int, char*, int) -> bool (~6758) // * TakeFX_GetFXGUID(MediaItem_Take*, int) -> GUID* (~6750) // * TakeFX_GetEnabled(MediaItem_Take*, int) -> bool (~6718) // * 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 #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_TakeFX_GetCount #define REAPERAPI_WANT_TakeFX_GetFXName #define REAPERAPI_WANT_TakeFX_GetFXGUID #define REAPERAPI_WANT_TakeFX_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 rows; const int n = TrackFX_GetCount(tr); rows.reserve(static_cast(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(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); } std::string fxChainIdentityForItems(const std::vector& items) { // For Item scope the in-scope chain is each item's active take's FX chain, NOT // the owning track's FX chain (the track chain is out-of-scope and is bypassed // during render). TakeFX_* is the correct family here. std::vector perItem; perItem.reserve(items.size()); for (MediaItem* it : items) { if (!it) { perItem.push_back(fxChainIdentity({})); continue; } MediaItem_Take* take = GetActiveTake(it); if (!take) { perItem.push_back(fxChainIdentity({})); continue; } std::vector rows; const int n = TakeFX_GetCount(take); rows.reserve(static_cast(n < 0 ? 0 : n)); for (int i = 0; i < n; ++i) { FxIdentityEntry e; char nameBuf[512] = {0}; if (TakeFX_GetFXName(take, i, nameBuf, static_cast(sizeof(nameBuf)))) e.name = nameBuf; if (GUID* g = TakeFX_GetFXGUID(take, i)) { char gb[64] = {0}; guidToString(g, gb); e.guid = gb; } e.enabled = TakeFX_GetEnabled(take, i); rows.push_back(std::move(e)); } perItem.push_back(fxChainIdentity(rows)); } return combineChainIdentities(perItem); } 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(sizeof(buf))); return normalizeSlashes(std::string(buf)); } } // namespace std::vector selectedItemSourceFiles() { std::vector 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 trackItemSourceFiles(const std::vector& tracks, double startSeconds, double endSeconds) { std::vector 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 bankFileRefs(const BankBook& book, const std::string& projectDir) { std::vector 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