Ψ-W1-T4: resolve drop targets per move, not once — every surface gets a defined outcome, a cue, and no silent no-op
This commit is contained in:
@@ -81,7 +81,7 @@ This directory owns two cross-artifact contracts specifically:
|
||||
- `reasampler_uid.h` — SDK-free header owning the FOREVER-FROZEN VST3 class-UID integer macros (stable + beta pairs, `REASAMPLER_PROC_UID_*` / `REASAMPLER_PROC_UID_BETA_*`) and the `REASAMPLER_ACTIVE_UID_*` channel-selector macros. Split out of `reasampler_vst.h` so the pure extension side (`instrument_drop`) can derive the `.vstpreset` class-ID hex string without pulling in the VST3 SDK. Both `reasampler_vst.h` (runtime `FUID`) and `instrument_drop` (preset hex string) source from this single header — the binary identity and the preset-file identity cannot diverge.
|
||||
- `assignment_request` — pure ingest-assign wire: typed request record carrying the drop payload from the `ingest` shell through to the VST3 bridge.
|
||||
- `bake_wire` — the resample bake's request/outcome pair on ONE per-instance key (`rsbake_<guid>`): the instrument writes a `BakeRequest`, invokes the extension's action synchronously, and reads the extension's `BakeOutcome` back over the same key inside that one call. Not a handshake — a call and a return, and it must not grow a claim protocol. Also the ONE home of the bake action's command-id suffix and of the leading underscore `NamedCommandLookup` needs but `rec->Register("command_id", …)` does not, so both artifacts name one action. `BakeStatus` values are WIRE INTEGERS: never renumber, only append, and an unrecognized value decodes as `Failed` rather than as the numeric default `Ok`.
|
||||
- `instrument_drop` — pure FX-drop payload builder: constructs a Steinberg-format `.vstpreset` image (channel-active class ID + the instrument's own component state, capture pre-selected) the shell applies via `TrackFX_SetPreset`; owns the `infoNamesFxHotspot` prefix classifier for `GetThingFromPoint` tokens. All-or-nothing contract — caller rolls back via `TrackFX_Delete` on any failure.
|
||||
- `instrument_drop` — pure FX-drop payload builder: constructs a Steinberg-format `.vstpreset` image (channel-active class ID + the instrument's own component state, capture pre-selected) the shell applies via `TrackFX_SetPreset`; owns `classifyReaperSurface`, the prefix classifier mapping a `GetThingFromPoint` (info token, track-present) pair onto `core/ui/drag_out`'s `ReaperSurface`. Classifier ordering is load-bearing: the embed strip is matched before the `tcp`/`mcp` panel family, which now claims the WHOLE track panel rather than just its FX sub-elements. All-or-nothing contract — caller rolls back via `TrackFX_Delete` on any failure.
|
||||
- `sample_usage` — instance-usage wire: `UsageRecord`, `planUsagePublish` (fresh/heal/clean-replace/union/remint publish plan), `foldUsageRecords`/`usageHeldPaths` (liveness fold — protect-all when records exist but no instance is live; abort→protect-all on unreadable record; `counted` carries key-attributed live records), `identityMatches` (ReaSampler 9000 FX identity). REAPER-free, unit-tested. The mirror of `assignment_request` on the instrument→extension direction: the wire format and the two safety-critical decisions (what to write on publish, which records count at prune time) are pure so they are provable without a DAW. It lives here because it is a *wire format* with an instrument-side writer; the fold's output is consumed by `core/tracking`'s authority, which owns every consumer-facing decision built on it.
|
||||
|
||||
## Gotchas
|
||||
|
||||
@@ -18,6 +18,9 @@ reasampler_test(sample_usage LINK sample_usage prune_reconcile)
|
||||
# parallel byte writer, so the cross-artifact contract cannot drift — hence the link to
|
||||
# component_state_io, which stays engine-free. The class-ID string derives from the frozen
|
||||
# UID macros, channel-selected via the generated version header, hence its include dir.
|
||||
reasampler_pure_library(instrument_drop SOURCES instrument_drop.cpp LINK PUBLIC component_state_io)
|
||||
# drag_out is the dependency-free owner of the ReaperSurface vocabulary this module classifies
|
||||
# INTO; the edge points this way so drag_out (and card_drag through it) stays free of the
|
||||
# serializer.
|
||||
reasampler_pure_library(instrument_drop SOURCES instrument_drop.cpp LINK PUBLIC component_state_io drag_out)
|
||||
target_include_directories(instrument_drop PUBLIC ${PROJECT_BINARY_DIR}/generated)
|
||||
reasampler_test(instrument_drop LINK instrument_drop)
|
||||
|
||||
@@ -87,11 +87,16 @@ DropOutcome decideDropOutcome(const DropAttempt& attempt) {
|
||||
return out;
|
||||
}
|
||||
|
||||
bool infoNamesFxHotspot(const std::string& info) {
|
||||
// See the header contract for the prefix rule and the embed-strip exclusion.
|
||||
ui::ReaperSurface classifyReaperSurface(const std::string& info, bool haveTrack) {
|
||||
// See the header contract for the prefix rule and the ordering it depends on.
|
||||
auto startsWith = [&info](const char* p) { return info.rfind(p, 0) == 0; };
|
||||
if (startsWith("tcp.fxembed") || startsWith("mcp.fxembed")) return false;
|
||||
return startsWith("fx_") || startsWith("tcp.fx") || startsWith("mcp.fx");
|
||||
|
||||
if (info.empty()) return haveTrack ? ui::ReaperSurface::Other : ui::ReaperSurface::OffReaper;
|
||||
if (startsWith("tcp.fxembed") || startsWith("mcp.fxembed")) return ui::ReaperSurface::FxEmbed;
|
||||
if (startsWith("fx_")) return ui::ReaperSurface::FxSurface;
|
||||
if (startsWith("tcp") || startsWith("mcp")) return ui::ReaperSurface::TrackPanel;
|
||||
if (startsWith("arrange")) return ui::ReaperSurface::Arrange;
|
||||
return ui::ReaperSurface::Other;
|
||||
}
|
||||
|
||||
} // namespace reasampler::wire
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#pragma once
|
||||
// instrument_drop — pure payload-construction core of drop-and-load: dropping a
|
||||
// bank capture onto a track's FX surface instantiates ReaSampler 9000 on that
|
||||
// track already playing that capture. No REAPER/SWELL/VST3 SDK/vendor includes
|
||||
// bank capture onto a track's panel or FX surface instantiates ReaSampler 9000 on
|
||||
// that track already playing that capture. Also the classifier that names those
|
||||
// surfaces. No REAPER/SWELL/VST3 SDK/vendor includes
|
||||
// (+ the pure sample_map it reuses and the SDK-free UID macros in
|
||||
// reasampler_uid.h); unit-tested outside the DAW.
|
||||
//
|
||||
@@ -25,6 +26,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "core/ui/drag_out.h" // ReaperSurface — the surface vocabulary the gesture law reads
|
||||
|
||||
namespace reasampler::wire {
|
||||
|
||||
// The 32-char uppercase-hex class-ID string of this build's channel-active
|
||||
@@ -52,16 +55,23 @@ std::vector<std::uint8_t> buildVstPresetBytes(const std::string& classIdHex32,
|
||||
// preset. Deterministic.
|
||||
std::vector<std::uint8_t> buildInstrumentDropPreset(const std::string& sampleId);
|
||||
|
||||
// Pure classifier for GetThingFromPoint's info string: is the point over a
|
||||
// surface where an instrument drop should instantiate ReaSampler 9000? The
|
||||
// SDK warns future versions may append information, so the rule is
|
||||
// PREFIX-based: "fx_" (FX-chain/floating windows) or "tcp.fx"/"mcp.fx" (the
|
||||
// TCP/MCP FX button + sibling elements) EXCEPT "tcp.fxembed"/"mcp.fxembed" —
|
||||
// the embed-strip surface where an instance already draws; dropping there
|
||||
// must not add a second instance. Bare "tcp"/"mcp" and non-FX sub-elements
|
||||
// are not hotspots. The exact live token is DAW-only — confirm via
|
||||
// Pure classifier for GetThingFromPoint's (info string, track-was-returned) pair
|
||||
// into the surfaces the drag-out gesture law distinguishes. The SDK warns future
|
||||
// versions may append information, so every rule is PREFIX-based:
|
||||
// "tcp.fxembed*" / "mcp.fxembed*" -> FxEmbed. Checked FIRST: it is the surface
|
||||
// an existing instance already draws on, and a drop must not stack a second.
|
||||
// "fx_*" -> FxSurface (FX chain + floating FX windows).
|
||||
// "tcp*" / "mcp*" -> TrackPanel — the WHOLE panel, every
|
||||
// sub-element, not just the FX button. A TCP too narrow to draw that button
|
||||
// still means "sampler on this track", and the old glyph-only rule is exactly
|
||||
// why such a drop landed nowhere.
|
||||
// "arrange*" -> Arrange.
|
||||
// "" with no track -> OffReaper (the pointer left REAPER).
|
||||
// anything else, incl. "" WITH a track -> Other. Over REAPER on a surface we
|
||||
// cannot name: refuse visibly, never guess an outcome.
|
||||
// The exact live token is DAW-only — confirm via
|
||||
// reaper.GetThingFromPoint(reaper.GetMousePosition()) in ReaScript if unsure.
|
||||
bool infoNamesFxHotspot(const std::string& info);
|
||||
ui::ReaperSurface classifyReaperSurface(const std::string& info, bool haveTrack);
|
||||
|
||||
// The raw component-state bytes the preset carries, exposed so the round-trip
|
||||
// test can decode them back through sample_map::deserializeComponentState and
|
||||
|
||||
Reference in New Issue
Block a user