fix(drop): recognize TCP/MCP FX-button hotspot for instrument drop (S-VIEW-BUG-1)
The FX-hotspot classifier matched only fx_ (the FX-chain/floating windows), so a drop on a track TCP FX button (info tcp*) never armed the drop and the capture fell through to arrange as audio. Widen to fx_/tcp/mcp in a pure, unit-tested predicate; shell calls it.
This commit is contained in:
@@ -39,6 +39,14 @@ std::string buildInstrumentDropChunk(const std::string& sampleId) {
|
|||||||
return encodeBase64(instrumentDropStateBytes(sampleId));
|
return encodeBase64(instrumentDropStateBytes(sampleId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool infoNamesFxHotspot(const std::string& info) {
|
||||||
|
// See the header contract. The FX-chain / floating-FX windows report "fx_*"; the track /
|
||||||
|
// mixer panel that carries the FX button reports "tcp*" / "mcp*". Prefix-match all three —
|
||||||
|
// the TCP/MCP surfaces are the fix for S-VIEW-BUG-1 (the "fx_"-only predicate missed them).
|
||||||
|
auto startsWith = [&info](const char* p) { return info.rfind(p, 0) == 0; };
|
||||||
|
return startsWith("fx_") || startsWith("tcp") || startsWith("mcp");
|
||||||
|
}
|
||||||
|
|
||||||
std::string encodeBase64(const std::vector<std::uint8_t>& bytes) {
|
std::string encodeBase64(const std::vector<std::uint8_t>& bytes) {
|
||||||
std::string out;
|
std::string out;
|
||||||
out.reserve(((bytes.size() + 2) / 3) * 4);
|
out.reserve(((bytes.size() + 2) / 3) * 4);
|
||||||
|
|||||||
@@ -49,6 +49,26 @@ namespace reasampler {
|
|||||||
// Deterministic: the same sampleId always yields the same blob (base64 of the same bytes).
|
// Deterministic: the same sampleId always yields the same blob (base64 of the same bytes).
|
||||||
std::string buildInstrumentDropChunk(const std::string& sampleId);
|
std::string buildInstrumentDropChunk(const std::string& sampleId);
|
||||||
|
|
||||||
|
// -- FX-drop-target classification (S-VIEW-BUG-1) ------------------------------
|
||||||
|
//
|
||||||
|
// Pure classifier for GetThingFromPoint's info string: is the point over a surface where an
|
||||||
|
// instrument drop should instantiate ReaSampler 9000 on the resolved track? This is string
|
||||||
|
// logic (no REAPER types), so it lives here and is unit-tested outside the DAW — the shell
|
||||||
|
// (instrument_drop_win) only supplies the info bytes GetThingFromPoint filled.
|
||||||
|
//
|
||||||
|
// THE BUG (S-VIEW-BUG-1): the original shell predicate matched ONLY "fx_" — but per the SDK
|
||||||
|
// (reaper_plugin_functions.h §GetThingFromPoint) "fx_chain"/"fx_N" are the FX-CHAIN and
|
||||||
|
// FLOATING-FX windows; a hit on the TRACK PANEL (where the TCP/MCP FX button actually lives —
|
||||||
|
// the intuitive "drop onto the track's FX chain" target) reports a string that BEGINS WITH
|
||||||
|
// "tcp" or "mcp" (e.g. "tcp.fx"). So dropping on the TCP FX button never armed the drop; the
|
||||||
|
// gesture fell through and the file dropped to arrange as audio. The drop target is therefore
|
||||||
|
// EITHER of the SDK's two documented FX-bearing surfaces:
|
||||||
|
// * the FX chain / floating FX window -> info begins with "fx_" ("fx_chain", "fx_0", ...)
|
||||||
|
// * the track/mixer panel that hosts the FX button -> info begins with "tcp" or "mcp"
|
||||||
|
// A single-capture drag released over any of these, on a resolved track, is an instrument drop.
|
||||||
|
// Any other info ("arrange", "spacer_0", "", ...) is not an FX hotspot.
|
||||||
|
bool infoNamesFxHotspot(const std::string& info);
|
||||||
|
|
||||||
// The raw (pre-base64) component-state bytes — exposed so the round-trip test can decode them
|
// The raw (pre-base64) component-state bytes — exposed so the round-trip test can decode them
|
||||||
// back through the instrument's OWN reader (sample_map::deserializeComponentState) and assert
|
// back through the instrument's OWN reader (sample_map::deserializeComponentState) and assert
|
||||||
// the capture is selected, proving buildInstrumentDropChunk feeds the instrument exactly what
|
// the capture is selected, proving buildInstrumentDropChunk feeds the instrument exactly what
|
||||||
|
|||||||
@@ -5,10 +5,10 @@
|
|||||||
|
|
||||||
#include "instrument_drop_win.h"
|
#include "instrument_drop_win.h"
|
||||||
|
|
||||||
#include <cstring>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "app_version.h" // vstPluginName() — the CHANNEL-correct FX name (stable/beta pairing)
|
#include "app_version.h" // vstPluginName() — the CHANNEL-correct FX name (stable/beta pairing)
|
||||||
|
#include "instrument_drop.h" // infoNamesFxHotspot — the PURE, unit-tested hotspot classifier
|
||||||
|
|
||||||
#include "reaper_plugin.h"
|
#include "reaper_plugin.h"
|
||||||
|
|
||||||
@@ -23,19 +23,6 @@
|
|||||||
|
|
||||||
namespace reasampler {
|
namespace reasampler {
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
// GetThingFromPoint's info string prefixes (verified against reaper_plugin_functions.h:
|
|
||||||
// "Updates infoOut with information such as 'arrange', 'fx_chain', 'fx_0' ... If a track
|
|
||||||
// panel is hit, string will begin with 'tcp' or 'mcp' or 'tcp.mute' etc"). The FX region
|
|
||||||
// reports "fx_chain" (the FX list area) or "fx_N" (a specific FX button). We treat either
|
|
||||||
// as the FX hotspot — the S17 drop target.
|
|
||||||
bool infoNamesFxHotspot(const char* info) {
|
|
||||||
return std::strncmp(info, "fx_", 3) == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
FxDropTarget resolveFxDropTarget(int screenX, int screenY) {
|
FxDropTarget resolveFxDropTarget(int screenX, int screenY) {
|
||||||
FxDropTarget out;
|
FxDropTarget out;
|
||||||
char info[256] = {0};
|
char info[256] = {0};
|
||||||
@@ -46,6 +33,8 @@ FxDropTarget resolveFxDropTarget(int screenX, int screenY) {
|
|||||||
MediaTrack* track = GetThingFromPoint(screenX, screenY, info, sizeof(info));
|
MediaTrack* track = GetThingFromPoint(screenX, screenY, info, sizeof(info));
|
||||||
out.track = track;
|
out.track = track;
|
||||||
out.overReaperUi = (track != nullptr) || (info[0] != '\0');
|
out.overReaperUi = (track != nullptr) || (info[0] != '\0');
|
||||||
|
// S-VIEW-BUG-1: the hotspot is either the FX chain/floating window ("fx_*") OR the track/
|
||||||
|
// mixer panel that hosts the FX button ("tcp*"/"mcp*"). The pure classifier owns the rule.
|
||||||
out.overFxHotspot = (track != nullptr) && infoNamesFxHotspot(info);
|
out.overFxHotspot = (track != nullptr) && infoNamesFxHotspot(info);
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -105,6 +105,44 @@ static void testBase64DecodeRejectsMalformed() {
|
|||||||
CHECK(decodeBase64("Zg==Zg==").empty()); // interior padding (pad before the final quad)
|
CHECK(decodeBase64("Zg==Zg==").empty()); // interior padding (pad before the final quad)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- FX-hotspot classification (S-VIEW-BUG-1) ---------------------------------
|
||||||
|
//
|
||||||
|
// THE BUG: dropping a capture onto a track's FX button (in the TCP) never armed the
|
||||||
|
// instrument drop, because the old predicate matched only "fx_" — which the SDK reserves for
|
||||||
|
// the FX CHAIN / FLOATING-FX windows. A track-panel hit (where the FX button lives) reports a
|
||||||
|
// string beginning with "tcp"/"mcp". These tests fix the classifier at the boundary the shell
|
||||||
|
// consumes: each would FAIL under the old "fx_"-only rule for the tcp/mcp cases.
|
||||||
|
|
||||||
|
// The regression case: the TCP FX-button surface reports "tcp*" and MUST classify as an FX
|
||||||
|
// hotspot. Under the pre-fix "fx_"-only predicate these all returned false — the exact miss
|
||||||
|
// that produced the "drops as audio to arrange" symptom.
|
||||||
|
static void testTcpMcpPanelIsHotspot() {
|
||||||
|
CHECK(infoNamesFxHotspot("tcp")); // bare track control panel
|
||||||
|
CHECK(infoNamesFxHotspot("tcp.fx")); // the TCP FX-button WALTER element
|
||||||
|
CHECK(infoNamesFxHotspot("tcp.mute")); // any tcp.* sub-element resolves to the track
|
||||||
|
CHECK(infoNamesFxHotspot("mcp")); // mixer control panel
|
||||||
|
CHECK(infoNamesFxHotspot("mcp.fx")); // the MCP FX area
|
||||||
|
}
|
||||||
|
|
||||||
|
// The FX chain / floating-FX windows (the surfaces the ORIGINAL predicate matched) still
|
||||||
|
// classify as hotspots — the fix widens the rule, it does not regress the "fx_" surface.
|
||||||
|
static void testFxWindowStillHotspot() {
|
||||||
|
CHECK(infoNamesFxHotspot("fx_chain")); // FX chain window
|
||||||
|
CHECK(infoNamesFxHotspot("fx_0")); // first FX, floating
|
||||||
|
CHECK(infoNamesFxHotspot("fx_12")); // arbitrary floating-FX index
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-FX surfaces are NOT hotspots — a drop here is not an instrument drop (it would fall
|
||||||
|
// through to the OS drag / no-op). Guards against over-broad matching (e.g. "spacer_0" must
|
||||||
|
// not match despite living near the tracks; "arrange" is the audio-import surface).
|
||||||
|
static void testNonFxSurfacesAreNotHotspot() {
|
||||||
|
CHECK(!infoNamesFxHotspot("arrange"));
|
||||||
|
CHECK(!infoNamesFxHotspot("spacer_0"));
|
||||||
|
CHECK(!infoNamesFxHotspot("")); // pointer over nothing REAPER classifies
|
||||||
|
CHECK(!infoNamesFxHotspot("trans")); // transport
|
||||||
|
CHECK(!infoNamesFxHotspot("envcp")); // envelope control panel — a track thing, not FX
|
||||||
|
}
|
||||||
|
|
||||||
int main() {
|
int main() {
|
||||||
testBlobRoundTripsThroughInstrumentReader();
|
testBlobRoundTripsThroughInstrumentReader();
|
||||||
testGuidLikeIdRoundTrips();
|
testGuidLikeIdRoundTrips();
|
||||||
@@ -114,6 +152,10 @@ int main() {
|
|||||||
testBase64RoundTripAllBytes();
|
testBase64RoundTripAllBytes();
|
||||||
testBase64DecodeRejectsMalformed();
|
testBase64DecodeRejectsMalformed();
|
||||||
|
|
||||||
|
testTcpMcpPanelIsHotspot();
|
||||||
|
testFxWindowStillHotspot();
|
||||||
|
testNonFxSurfacesAreNotHotspot();
|
||||||
|
|
||||||
if (g_fail == 0) std::printf("All tests passed.\n");
|
if (g_fail == 0) std::printf("All tests passed.\n");
|
||||||
return g_fail ? 1 : 0;
|
return g_fail ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user