43994e0d9d
The pure bankRootFor(rppPath, unsavedRoot) and the shell bankRootOf(ReaProject*) replace eleven hand-rolled .rpp-parent derivations. No unsaved root is supplied yet, so behaviour and refusals are unchanged. Save-As relocation now reads the stored lastBankRoot_.
157 lines
6.8 KiB
C++
157 lines
6.8 KiB
C++
// insert.cpp — REAPER-facing placement shell. See insert.h.
|
|
//
|
|
// Compiled into the reaper_reasampler module. Includes reaper_plugin_functions.h
|
|
// WITHOUT REAPERAPI_IMPLEMENT — main.cpp owns the API pointers; here they are
|
|
// extern.
|
|
//
|
|
// Unlike capture / bank_panel (which never touch the arrange), insert deliberately
|
|
// adds items to the arrange — that is its whole job. Runs only from its own action.
|
|
//
|
|
// Runtime assumptions the SDK header doesn't fully spell out (flagged, not yet
|
|
// DAW-verified):
|
|
// A. InsertMedia mode 0 ("add to current track") is assumed to target the sole
|
|
// selected track — the header doesn't spell out how "current" resolves, so we
|
|
// force exactly one selection via SetOnlyTrackSelected before each call. If
|
|
// REAPER means last-focused rather than last-selected, this needs revisiting.
|
|
// B. Mode 0 is assumed to insert at the edit cursor (REAPER's documented
|
|
// convention for base modes 0/1; the header has no explicit "at cursor" bit).
|
|
// C. InsertMedia may advance the cursor to the end of the inserted media; we
|
|
// reset to the snapshot position before each track's insert, so this doesn't
|
|
// matter either way.
|
|
// D. SetEditCurPos(time, false, false) — moveview=false, seekplay=false — is
|
|
// assumed to move the cursor without scrolling the view or the transport.
|
|
// E. SetOnlyTrackSelected deselects all tracks and selects exactly one (per its
|
|
// header doc-comment — the strongest confirmation we have here).
|
|
|
|
#include "shell/capture/insert.h"
|
|
|
|
#include <filesystem>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "core/model/bank_model.h"
|
|
#include "shell/panel/panel_bank_ops.h" // bankPanelSelectedSampleIds / SourceBankId
|
|
#include "core/capture/capture_paths.h"
|
|
#include "shell/persist/bank_root.h"
|
|
#include "shell/persist/session.h"
|
|
#include "shell/undo/undo_block.h"
|
|
|
|
#define REAPERAPI_MINIMAL
|
|
#define REAPERAPI_WANT_CountSelectedTracks
|
|
#define REAPERAPI_WANT_GetCursorPosition
|
|
#define REAPERAPI_WANT_GetSelectedTrack
|
|
#define REAPERAPI_WANT_InsertMedia
|
|
#define REAPERAPI_WANT_SetEditCurPos
|
|
#define REAPERAPI_WANT_SetOnlyTrackSelected
|
|
#define REAPERAPI_WANT_SetTrackSelected
|
|
#define REAPERAPI_WANT_ShowConsoleMsg
|
|
#include "reaper_plugin_functions.h"
|
|
|
|
namespace reasampler {
|
|
|
|
using capture::computeInsertMode;
|
|
using capture::resolveBankFile;
|
|
using capture::TempoConform;
|
|
|
|
namespace {
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
// Snapshot of the currently-selected track set (master is skipped, matching
|
|
// CountSelectedTracks/GetSelectedTrack), in selection order, for restore later.
|
|
std::vector<MediaTrack*> snapshotSelectedTracks() {
|
|
const int n = CountSelectedTracks(nullptr); // nullptr = active project
|
|
std::vector<MediaTrack*> tracks;
|
|
tracks.reserve(static_cast<size_t>(n));
|
|
for (int i = 0; i < n; ++i)
|
|
tracks.push_back(GetSelectedTrack(nullptr, i));
|
|
return tracks;
|
|
}
|
|
|
|
// Restores a snapshotted selection: deselect all via the first track, then
|
|
// re-select the rest. Empty snapshot -> no-op (guards an empty project).
|
|
void restoreSelectedTracks(const std::vector<MediaTrack*>& tracks) {
|
|
if (tracks.empty()) return;
|
|
SetOnlyTrackSelected(tracks[0]);
|
|
for (size_t i = 1; i < tracks.size(); ++i)
|
|
SetTrackSelected(tracks[i], true);
|
|
}
|
|
|
|
} // namespace
|
|
|
|
InsertResult runInsert(ReaSamplerSession* session, const InsertRequest& request) {
|
|
InsertResult result;
|
|
if (!session) { result.status = InsertStatus::NoSelection; return result; }
|
|
|
|
// WHO: the user's selected track set. No-op when nothing is selected — inserting
|
|
// without a target track would create an unintended track or behave unpredictably.
|
|
const std::vector<MediaTrack*> selectedTracks = snapshotSelectedTracks();
|
|
if (selectedTracks.empty()) {
|
|
ShowConsoleMsg("ReaSampler insert: select a track first.\n");
|
|
result.status = InsertStatus::NoSelection;
|
|
return result;
|
|
}
|
|
|
|
// WHAT: the single focused sample from the panel; multi-select is deprioritized,
|
|
// so take the first id. Empty selection -> no-op.
|
|
const std::vector<std::string> ids = bankPanelSelectedSampleIds();
|
|
if (ids.empty()) { result.status = InsertStatus::NoSelection; return result; }
|
|
const std::string& id = ids.front(); // focused / first selected — single sample
|
|
|
|
// WHERE: no bank root means no resolvable bank file; no-op rather than
|
|
// resolving against CWD.
|
|
const std::string projectDir = bankRootOf(nullptr);
|
|
if (projectDir.empty()) { result.status = InsertStatus::NoProject; return result; }
|
|
|
|
// Resolve against the bank the selection came from — it may be the pool or a
|
|
// shown named bank, not necessarily the active/capture-target bank. Fall back to
|
|
// the active bank when the source id names no bank (defensive).
|
|
const std::string srcBankId = bankPanelSelectedSourceBankId();
|
|
const BankModel* srcIndex = session->book().index(srcBankId);
|
|
const BankModel& bank = srcIndex ? *srcIndex : session->bank();
|
|
const Sample* sample = bank.query(id);
|
|
if (!sample) { result.status = InsertStatus::NothingResolved; return result; }
|
|
|
|
const std::string abs = resolveBankFile(projectDir, sample->relativePath);
|
|
if (abs.empty() || !fs::exists(fs::path(abs))) {
|
|
result.status = InsertStatus::NothingResolved;
|
|
return result;
|
|
}
|
|
|
|
const int mode = computeInsertMode(request.options);
|
|
|
|
// Snapshot the edit cursor position up front so we can restore it to the same
|
|
// position for each track insert (and after the whole operation).
|
|
const double cursorPos = GetCursorPosition();
|
|
|
|
// One undo block around the whole placement (all tracks + selection/cursor
|
|
// restore) so a single undo removes every item and restores prior state.
|
|
UndoBlock undo(nullptr);
|
|
|
|
// Insert onto each selected track at the same cursor position (assumption B): per
|
|
// track, isolate it as the only selection (A + E), reset the cursor (C is
|
|
// irrelevant since we own the reset), then insert.
|
|
for (MediaTrack* track : selectedTracks) {
|
|
SetOnlyTrackSelected(track); // assumption A + E
|
|
SetEditCurPos(cursorPos, false, false); // assumption D
|
|
InsertMedia(abs.c_str(), mode);
|
|
++result.inserted;
|
|
}
|
|
|
|
// Restore the original selection + cursor — non-destructive to the user's DAW state.
|
|
restoreSelectedTracks(selectedTracks);
|
|
SetEditCurPos(cursorPos, false, false);
|
|
|
|
// Label reflects count + conform choice for a clear undo history. extraflags -1 =
|
|
// UNDO_STATE_ALL (tracks, items, envelope points, project state).
|
|
undo.recordPoint("ReaSampler: insert on " + std::to_string(result.inserted) +
|
|
(result.inserted == 1 ? " track" : " tracks") +
|
|
(request.options.conform == TempoConform::None ? "" : " (conform)"),
|
|
-1);
|
|
|
|
result.status = InsertStatus::Ok;
|
|
return result;
|
|
}
|
|
|
|
} // namespace reasampler
|