feat(S8): ingest through the bank — capture/import/drop into bank + assign to active instance

This commit is contained in:
2026-07-26 21:29:13 -04:00
parent bf1bf5283c
commit 8074e21057
11 changed files with 1009 additions and 6 deletions
+77
View File
@@ -0,0 +1,77 @@
#pragma once
// ingest — the S8 "ingest through the bank" shell (EXTENSION side).
//
// Compiled into the reaper_reasampler MODULE. REAPER-facing (PCM_Source metadata reads,
// Media-Explorer query, ext-state assignment write, action registration), so it is
// DAW-verified, not unit-tested; the pure serialization it drives lives in
// assignment_request (tested in CTest).
//
// -- The one gesture (CONTEXT.md §Ingest through the bank) --------------------
//
// Loading a sample into the sampler is ONE gesture: capture/import-into-bank AND
// auto-assign to the active sampler instance. The EXTENSION owns ingest (it has arrange
// access, Media-Explorer access, and the drop-target surface on its own panels); the
// instrument stays a READ-ONLY bank consumer. Three ingest surfaces:
//
// 1. Arrange capture -> bank -> assign (a bindable action; reuses the capture path).
// 2. Media-Explorer import -> bank -> assign (a bindable action; single-file, pull-on-
// action via MediaExplorerGetLastPlayedFileInfo).
// 3. Drop-onto-panel -> bank -> assign (an OS file drop on the docked bank_panel HWND;
// multi-file: import all, assign the first).
//
// -- The load-bearing principle (restated) -----------------------------------
//
// Ingest NEVER inserts a timeline item. Capture writes a file + an index entry; import
// copies a file + adds an index entry; assignment is a bank-index + instance-selection
// act, not a placement. Any path here that calls InsertMedia would be a bug.
//
// -- Import semantics ---------------------------------------------------------
//
// A Media-Explorer/drop import is a FILE COPY into the project-relative bank folder +
// an index add, mirroring how a capture lands (relative-paths-only, hash-dedup). If the
// active bank already holds the imported content (by content hash), the import collapses
// onto the existing sample and assigns THAT sample's id — no redundant on-disk copy.
#include <string>
#include <vector>
// Forward declarations keep this header REAPER-free at its own boundary (the .cpp pulls
// the SDK). reaper_plugin_info_t is REAPER's dispatch struct; ReaSamplerSession owns the
// book + persist bridge the ingest paths mutate.
struct reaper_plugin_info_t;
namespace reasampler {
class ReaSamplerSession;
// Registers the S8 ingest action family (command_id/gaccel per the house contract),
// mirror of bankRegisterActions. `session` is the live session the ingest paths mutate
// (shared with the capture / bank / Design-View families). The single hookcommand in
// main.cpp routes fired ids here via ingestHandleCommand.
void ingestRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* session);
// Routes a fired command id to its ingest action. Returns true iff it was one of ours
// (claim-only, per the hookcommand contract); false otherwise so the hook keeps looking.
bool ingestHandleCommand(int command);
// Mirror-unregisters the ingest action family on unload (the '-'-prefixed strings).
void ingestUnregisterActions(reaper_plugin_info_t* rec);
// Write the S8 assignment request for a just-ingested sample: "the active sampler
// instance should now play (bankId, sampleId)." Encodes the pure assignment_request value
// (with a fresh monotonic generation stamp) and routes it to ext state via the session.
// Called by EVERY ingest surface after the sample lands in the bank — the arrange
// capture+assign action (main.cpp, alongside the capture machinery it reuses), the ME
// import action, and the drop path. A no-op-safe write: if there is no saved/active
// project the request is silently dropped (nothing to signal into), matching the
// book/manifest quiet-persist idiom. `sampleId` empty -> no write (nothing to assign).
void ingestAssignActiveInstance(const std::string& bankId, const std::string& sampleId);
// Ingest OS-dropped files onto a ReaSampler surface (S8 drop path). Called by the
// bank_panel's WM_DROPFILES handler with the dropped file paths (absolute, OS-native).
// Imports EVERY file into the active bank (copy + index add, hash-dedup) and assigns the
// FIRST successfully-imported sample to the active instance. A no-op on an empty list or
// an unsaved/no-active project (nothing to import into). Reports outcomes to the console.
void ingestDroppedFiles(const std::vector<std::string>& absolutePaths);
} // namespace reasampler