// reasampler_embed.h — the S6 embedded TCP/MCP UI shell. Implements REAPER's // IReaperUIEmbedInterface (vendor/reaper-sdk/sdk/reaper_plugin_fx_embed.h + // reaper_vst3_interfaces.h) so the instrument draws a compact keymap/level strip INLINE in // the track/mixer control panel — the same Cockos surface REAPER's own embedded FX use. // // VERIFIED CONTRACT (against reaper_plugin_fx_embed.h + reaper_vst3_interfaces.h): // * VST3 exposes this by having the IEditController answer queryInterface for // IReaperUIEmbedInterface (iid {0x049bf9e7,0xbc74ead0,0xc4101e86,0x7f725981}). Our // SingleComponentEffect IS the edit controller, so the processor's queryInterface hands // REAPER a reference to this object. // * The single method is embed_message(int msg, TPtrInt parm2, TPtrInt parm3). msg is a // REAPER_FXEMBED_WM_* value (aliased to Win32 WM_*): // - WM_IS_SUPPORTED (0x0000): return 1 (supported+available), -1, or 0. // - WM_CREATE (0x0001) / WM_DESTROY (0x0002): embed begin/end; return ignored. // - WM_PAINT (0x000F): parm2 = REAPER_FXEMBED_IBitmap* (alias LICE_IBitmap) to draw // into; parm3 = REAPER_FXEMBED_DrawInfo* (context TCP=1/MCP=2, width/height, mouse, // flags). Return 1 if drawing occurred, 0 otherwise. // - WM_GETMINMAXINFO (0x0024): parm3 = SizeHints*; return 1 if filled. // - mouse WM_* (0x0200..0x020A): parm3 = DrawInfo*; return RETNOTIFY_INVALIDATE // (0x1000000) to force a redraw. Capture is auto-managed by the host. // * There is NO plugin-owned window/HWND here (unlike the IPlugView editor): REAPER hands // a LICE bitmap per paint; we only draw into it and read mouse coords from DrawInfo. // // RT DISCIPLINE (S6 constraint): all embed messages arrive on REAPER's UI thread; nothing // here runs in process(). It reads the same live state the editor reads (bank over the // bridge + the processor's performance map) with the same off-audio-thread accessors — no // new locks visible to process, read-only over the bank. Windows-only (D5), guarded so a // non-Windows build stays compilable. // // The strip's LAYOUT + HIT-TEST is pure (embed_strip.h, unit-tested); this shell marshals // REAPER's messages to/from it and draws with the same LICE idiom as reasampler_editor. #pragma once #include #include #include #include "pluginterfaces/base/funknown.h" #include "sample_map.h" // SampleChoice, PerformanceMap (the state the strip reflects) // REAPER's VST3-side embed interface (vendored). Uses UNQUALIFIED Steinberg types, so it is // pulled into the Steinberg namespace the same way reaper_bridge.cpp includes the host // interface header. Its iid is DEFINEd (DEF_CLASS_IID) in reasampler_embed.cpp. namespace Steinberg { #include "reaper_vst3_interfaces.h" } // namespace Steinberg namespace reasampler::vst { class ReaSamplerProcessor; // Implements IReaperUIEmbedInterface. Lifetime is OWNED by the processor (the processor // holds the sole unique_ptr and hands out AddRef'd references from queryInterface); the // back-pointer to the processor is therefore always valid while this lives. class ReaSamplerEmbed : public Steinberg::IReaperUIEmbedInterface { public: explicit ReaSamplerEmbed(ReaSamplerProcessor* processor) : processor_(processor) {} // The one embed entry point. Routes each REAPER_FXEMBED_WM_* message; see the header // note above for the per-message contract. UI thread only. Steinberg::TPtrInt embed_message(int msg, Steinberg::TPtrInt parm2, Steinberg::TPtrInt parm3) override; // FUnknown: this object's lifetime is owned by the processor, not the host refcount, so // AddRef/release are no-ops (the processor's unique_ptr governs destruction) and // queryInterface answers only FUnknown + IReaperUIEmbedInterface. This mirrors how the // SDK's OBJ refcount would otherwise churn; here the owning processor guarantees the // object outlives every borrowed reference REAPER holds during embedding. Steinberg::tresult PLUGIN_API queryInterface(const Steinberg::TUID iid, void** obj) override; Steinberg::uint32 PLUGIN_API addRef() override { return 1000; } Steinberg::uint32 PLUGIN_API release() override { return 1000; } private: #ifdef _WIN32 // Draw the current strip into REAPER's supplied LICE bitmap. Returns true if it drew. bool paint(Steinberg::TPtrInt bitmap, Steinberg::TPtrInt drawInfo); // Handle a mouse-down inside the strip: map to a zone and select it (S6: selection at // most — no new editing semantics). Returns true if the selection changed (the caller // then asks REAPER to invalidate). bool onMouseDown(Steinberg::TPtrInt drawInfo); #endif // Snapshot the live bank + the instrument's performance map for the next paint, exactly // as the editor's refreshSampleList does (bridge read + processor accessors, UI thread). void refresh(); // The S9 dirty-guard over refresh() (the S6 flagged follow-up): read the cheap bank- // generation stamp; do the EXPENSIVE bank-blob bridge read (refresh()) only when the // generation changed since the last paint (or on the first paint) — the strip re-read // per paint was wasteful now that a generation counter exists. The performance map (a // cheap in-process accessor, edited by the editor independently of bank content) is // ALWAYS refreshed so a zone edit still reflects immediately. UI thread only. void maybeRefresh(); ReaSamplerProcessor* processor_ = nullptr; // The bank generation last folded into samples_ (S9 dirty-guard). -1 forces the first // maybeRefresh() to do a full read (no generation can be negative — parseBankGeneration // yields >= 0 — so -1 is an "unprimed" sentinel distinct from a real generation 0). std::int64_t lastSeenBankGeneration_ = -1; // Snapshotted for the current paint (refreshed each paint off the audio thread). std::vector samples_; PerformanceMap map_; // The zone the last click selected (local/visual only — S6 selection constraint; the // processor's editor-shared selection is NOT updated from here); -1 = none. // Drives the strip's highlight. int selectedZone_ = -1; }; } // namespace reasampler::vst