#pragma once // ingest โ€” the "ingest through the bank" shell (EXTENSION side). REAPER-facing // (PCM_Source metadata reads, Media-Explorer query, ext-state assignment write, // action registration), so DAW-verified, not unit-tested; the pure serialization it // drives lives in assignment_request (CTest). // // 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 (arrange // access, Media-Explorer access, drop-target surface); the instrument stays a // READ-ONLY bank consumer. Three surfaces: (1) arrange capture -> bank -> assign, // (2) Media-Explorer import -> bank -> assign (single-file, pull-on-action), (3) // drop-onto-panel -> bank -> assign (multi-file: import all, assign the first). // // LOAD-BEARING: ingest NEVER inserts a timeline item โ€” capture writes a file + index // entry, import copies a file + adds an index entry, assignment is a bank-index + // instance-selection act, not a placement. Any InsertMedia call here is a bug. // // Import is a FILE COPY into the project-relative bank folder + an index add // (relative-paths-only, hash-dedup). If the active bank already holds the content // (by hash), the import collapses onto the existing sample instead of duplicating. #include #include // Forward declarations keep this header REAPER-free (the .cpp pulls the SDK). struct reaper_plugin_info_t; namespace reasampler { class ReaSamplerSession; // FOREVER-STABLE command-id suffixes โ€” NEVER change after ship; user keybindings key off // the composed ids. Two, because the Media-Explorer import publishes into two action // sections and a custom_action idStr must be unique across all of them. Exposed here so // the id-composition contract is assertable without linking this REAPER-facing TU. inline constexpr const char* kIngestImportMediaExplorerId = "INGEST_IMPORT_MEDIA_EXPLORER"; inline constexpr const char* kIngestImportMediaExplorerMxId = "INGEST_IMPORT_MEDIA_EXPLORER_MX"; // `session` is shared with the capture / bank / Design-View families; main.cpp's two // dispatch hooks route fired ids back through the two handlers below. Both registration // mechanisms are specified in root CLAUDE.md ยง"REAPER extension contract". void ingestRegisterActions(reaper_plugin_info_t* rec, ReaSamplerSession* session); // Main-section dispatch ("hookcommand"). bool ingestHandleCommand(int command); // Non-main-section dispatch ("hookcommand2"). Claims only ids this family published // outside the Main section, so the two hooks never both claim one command. bool ingestHandleSectionCommand(int command); void ingestUnregisterActions(reaper_plugin_info_t* rec); // "The active sampler instance should now play (bankId, sampleId)." Called by EVERY // ingest surface after the sample lands in the bank. No-op-safe: an unsaved/no-active // project silently drops the write; `sampleId` empty -> no write. void ingestAssignActiveInstance(const std::string& bankId, const std::string& sampleId); // Called by the bank_panel's WM_DROPFILES handler. Imports EVERY file into the // active bank (hash-dedup) and assigns the FIRST successfully-imported sample to the // active instance. No-op on an empty list or an unsaved/no-active project. void ingestDroppedFiles(const std::vector& absolutePaths); } // namespace reasampler