Merge Insert as FX: load ReaSampler 9000 onto the selected track from the panel

# Conflicts:
#	src/shell/actions/CLAUDE.md
This commit is contained in:
2026-08-03 16:46:40 -04:00
14 changed files with 442 additions and 21 deletions
+1
View File
@@ -43,6 +43,7 @@ is owned by other directories and only skinned here.
- `package_export_action` — the "export bank as package" skin: survey and report first, confirm what is absent (and, separately, a destination being replaced), pick a destination, write. Every prompt in the flow lives here so `shell/package/export_bank` stays promptless. Read-only against the project — it holds the session by `const&`, so no ext-state write, generation bump or undo point is reachable. Registration rides `main.cpp`'s action table (`EXPORT_BANK_PACKAGE`); the panel's tab menu is the second skin over the same body.
- `drag_out_win` — the host/OS boundary of drag-out: Windows OLE `DoDragDrop`/`CF_HDROP`, copy-only (`DROPEFFECT_MOVE` not offered); macOS/Linux via `SWELL_InitiateDragDropOfFileList`. Also owns `pointerOverHostWindow`, the window-ownership test behind `ui::DropContext::pointerOffHost` (`core/ui/CLAUDE.md` owns the hand-off rule it feeds) — process identity on Windows (`WindowFromPoint` + `GetWindowThreadProcessId`), non-null `WindowFromPoint` under SWELL, whose window list is already this-process-only. Its shipped exception: a plugin bridged into `reaper_host*.exe` reports off-host though the pointer never left REAPER.
- `instrument_drop_win` — instrument-drop shell: `probeDropTarget` resolves a screen point to a track + a `ReaperSurface` (via the pure `wire::classifyReaperSurface`, whose token rules `core/wire/CLAUDE.md` owns) — it answers only what REAPER named, never whether the pointer is still in REAPER — and the drop half adds a ReaSampler 9000 instance and applies the dragged capture's state via a transient `.vstpreset` + `TrackFX_SetPreset` (the former `TrackFX_SetNamedConfigParm` "vst_chunk" write was silently unappliable for VST3). Exposes `loadInstrumentOntoTrack` (inner half, no own undo block) and `performInstrumentDrop` (wraps in its own undo block). **Never captures, never writes the bank, never inserts a timeline item.**
- `insert_fx_action` — the "Insert as FX" verb: a second ENTRY POINT to `instrument_drop_win`'s drop body (the bank panel's Placement button and a bindable action), loading the panel's focused capture onto the first selected track. Owns no insert logic of its own — it resolves the selection, folds `core/ui/insert_fx_enable`, and calls `performInstrumentDrop` (which keeps the one undo block). Refuses visibly on every failing precondition; the panel button additionally paints dead for the two panel-local ones.
- `arrange_drop_win` — the drag-out gesture's arrange outcome: `arrangeTimeAtScreenX` (pointer column → time via `GetSet_ArrangeView2`'s one-pixel-span reading — inferred, not SDK-documented) and `performArrangeDrop` (snap the drop time, then one `InsertMedia` per capture on the pointer's track — assumed, not confirmed, to land end-to-end via REAPER's own cursor advance — in ONE undo block, counting only InsertMedia's reported successes, with the caller's track selection and edit cursor restored). The one timeline-placing shell here, per the invariant above; it never captures and never writes the bank.
- `package_import_action` — the bindable / bank-menu / file-drop skin over `shell/package`'s `importBankPackage`. Owns the **ledger gate**, which runs BEFORE the file picker (a refusal must not cost the user a file choice) and is keyed on the session's `LedgerStatus` alone — never on `PruneReport::blockedByTracking`, whose undecodable-`rsusage_*` arm governs deletion-time protection and would refuse an import that only writes birth records. Builds and shows every message the import produces, but the ledger-refusal body itself is `core/package::ledgerRefusalMessage` — a pure fold this TU only supplies the channel-correct namespace to — so the wording is assertable without a DAW. `doImportBankPackage`/`doImportBankPackageFile` return the minted bank id on a landed import (empty otherwise) so a caller can focus it; the verb itself is promptless.
- `ingest` — ingest-through-the-bank shell on the EXTENSION side: three surfaces — (1) arrange capture→bank→assign (bindable action), (2) Media-Explorer import→bank→instrument on the selected track, (3) file drop onto the bank panel→bank only. Only surface (1) writes the `assignment_request` ext-state wire. **ingest NEVER inserts a timeline item.** Surface (2)'s action is the one in this directory published into a NON-main action section (Media Explorer) as well as Main — two ids, one handler, two dispatch hooks; see root `CLAUDE.md` §"REAPER extension contract" for the mechanism.
+78
View File
@@ -0,0 +1,78 @@
// insert_fx_action.cpp — see insert_fx_action.h. main.cpp owns the API pointers; this TU
// gets them extern via the WANT list.
#include "shell/actions/insert_fx_action.h"
#include <cstdint>
#include <string>
#include <vector>
#include "core/ui/insert_fx_enable.h" // the refusal fold + its wording
#include "core/wire/instrument_drop.h" // buildInstrumentDropPreset — the pure payload
#include "shell/actions/instrument_drop_win.h" // performInstrumentDrop — the shared drop body
#include "shell/panel/panel_bank_ops.h" // bankPanelSelectedSampleIds
#include "reaper_plugin.h"
#define REAPERAPI_MINIMAL
#define REAPERAPI_WANT_GetSelectedTrack
#define REAPERAPI_WANT_GetMasterTrack
#define REAPERAPI_WANT_GetMediaTrackInfo_Value
#define REAPERAPI_WANT_GetTrackName
#define REAPERAPI_WANT_ShowConsoleMsg
#include "reaper_plugin_functions.h"
namespace reasampler {
namespace {
void report(const std::string& sentence) {
if (ShowConsoleMsg) ShowConsoleMsg(("ReaSampler: " + sentence + "\n").c_str());
}
// GetSelectedTrack ignores the master, so a master-only selection reads back as "no
// track" -- distinguish it here for a clearer refusal (core/ui/insert_fx_enable.h).
bool isMasterOnlySelected() {
if (!GetMasterTrack || !GetMediaTrackInfo_Value) return false;
MediaTrack* master = GetMasterTrack(nullptr);
return master && GetMediaTrackInfo_Value(master, "I_SELECTED") != 0.0;
}
// "Track N" for an unnamed track, matching REAPER's own convention (GetTrackName,
// not P_NAME, for the same reason scope_resolve::trackName picks it).
std::string trackDisplayName(MediaTrack* tr) {
std::vector<char> buf(256, '\0');
if (GetTrackName && GetTrackName(tr, buf.data(), static_cast<int>(buf.size())))
return std::string(buf.data());
return "the selected track";
}
} // namespace
void doInsertAsFx() {
const std::vector<std::string> selected = bankPanelSelectedSampleIds();
// seltrackidx 0 = the FIRST selected track, master excluded (SDK header) — the same
// "the selected track" the Media-Explorer ingest surface targets. A multi-track
// selection therefore loads onto the first, rather than refusing or fanning out.
MediaTrack* track = GetSelectedTrack ? GetSelectedTrack(nullptr, 0) : nullptr;
const ui::InsertFxRefusal refusal =
ui::insertFxRefusal(static_cast<int>(selected.size()), track != nullptr,
!track && isMasterOnlySelected());
if (refusal != ui::InsertFxRefusal::None) {
report(ui::insertFxRefusalMessage(refusal));
return;
}
// performInstrumentDrop opens its own undo block (one Ctrl-Z) and rolls the FX back
// itself if the preset never applies — there is nothing here to group with it, so no
// second block is opened around it.
if (!performInstrumentDrop(track, wire::buildInstrumentDropPreset(selected.front())))
report("could not add ReaSampler 9000 to the selected track -- check the plug-in "
"is installed and scanned");
else
report("added ReaSampler 9000 to \"" + trackDisplayName(track) +
"\", loaded with the selected capture");
}
} // namespace reasampler
+14
View File
@@ -0,0 +1,14 @@
#pragma once
// insert_fx_action — the "Insert as FX" verb: a ReaSampler 9000 instance on the SELECTED
// track, preloaded with the bank panel's focused capture. A second ENTRY POINT to
// instrument_drop_win's drop body — reached by a button or a keybinding instead of a
// drag — never a second insert path, so that body's undo block and its
// never-capture/never-place guarantees carry over unchanged.
namespace reasampler {
// Refuses visibly on every failing precondition (no capture / many captures / no track):
// see core/ui/insert_fx_enable for the fold and its wording.
void doInsertAsFx();
} // namespace reasampler