// reasampler_embed.cpp — see reasampler_embed.h. The IReaperUIEmbedInterface shell. // Windows-only (D5); guarded so a non-Windows build degrades to a stub that reports // "not supported" and draws nothing. #include "reasampler_embed.h" #include #include #include "editor_geometry.h" // Rect (shared with embed_strip) #include "embed_strip.h" // the pure strip layout + hit-test #include "ext_keys.h" // kProjExtBanksKey #include "reaper_bridge.h" #include "reasampler_processor.h" // wdltypes.h first: it defines INT_PTR portably (and pulls on Windows), which // reaper_plugin_fx_embed.h's REAPER_FXEMBED_IBitmap::Extended needs as its return type. #include "wdltypes.h" // REAPER's embed message/bitmap contract (vendored). REAPER_FXEMBED_IBitmap is an alias of // LICE_IBitmap, and the WM_* / DrawInfo / SizeHints definitions live here. #include "reaper_plugin_fx_embed.h" #ifdef _WIN32 // LICE — the same drawing stack the IPlugView editor and bank_panel use. REAPER hands us a // LICE bitmap; we draw into it with the same calls, then return (REAPER blits it). #include "lice/lice.h" #endif using namespace Steinberg; // DECLARE_CLASS_IID in the REAPER header only DECLARES IReaperUIEmbedInterface::iid; some // TU must DEFINE it. This is the only place that answers queryInterface for it, so the // definition lives with its sole use (mirrors reaper_bridge.cpp doing this for // IReaperHostApplication). DEF_CLASS_IID(Steinberg::IReaperUIEmbedInterface) namespace reasampler::vst { namespace { #ifdef _WIN32 // Palette — mirrored from reasampler_editor.cpp so the inline strip reads as the same tool. const LICE_pixel kColBackground = LICE_RGBA(28, 28, 30, 255); const LICE_pixel kColZone = LICE_RGBA(44, 44, 48, 255); const LICE_pixel kColZoneSel = LICE_RGBA(58, 96, 84, 255); const LICE_pixel kColZoneBorder = LICE_RGBA(20, 20, 22, 255); const LICE_pixel kColLevelBg = LICE_RGBA(20, 20, 22, 255); const LICE_pixel kColLevelFill = LICE_RGBA(120, 200, 160, 255); const LICE_pixel kColEmpty = LICE_RGBA(70, 70, 74, 255); const COLORREF kRgbText = RGB(210, 230, 220); // A short display name for a bank sample id, from the snapshotted list (the editor's helper, // duplicated small rather than shared across the shell/pure boundary). std::string sampleLabel(const std::vector& samples, const std::string& id) { for (const SampleChoice& c : samples) { if (c.id == id) return c.displayName.empty() ? c.id : c.displayName; } return "?"; } #endif // Project the instrument's performance map into the strip's minimal zone shape (key ranges // only). Pure projection — kept here (shell side) because it reads PerformanceMap, a shell // type; embed_strip stays free of it. std::vector toEmbedZones(const PerformanceMap& map) { std::vector out; out.reserve(map.zones.size()); for (const PerformanceZone& z : map.zones) out.push_back(EmbedZone{z.lowNote, z.highNote}); return out; } } // namespace tresult PLUGIN_API ReaSamplerEmbed::queryInterface(const TUID iid, void** obj) { QUERY_INTERFACE(iid, obj, FUnknown::iid, IReaperUIEmbedInterface) QUERY_INTERFACE(iid, obj, IReaperUIEmbedInterface::iid, IReaperUIEmbedInterface) *obj = nullptr; return kNoInterface; } void ReaSamplerEmbed::refresh() { if (!processor_) { samples_.clear(); map_.zones.clear(); selectedZone_ = -1; return; } auto banks = processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey); samples_ = banks ? listSamples(*banks) : std::vector{}; map_ = processor_->performanceMap(); if (selectedZone_ >= static_cast(map_.zones.size())) selectedZone_ = -1; } TPtrInt ReaSamplerEmbed::embed_message(int msg, TPtrInt parm2, TPtrInt parm3) { switch (msg) { case REAPER_FXEMBED_WM_IS_SUPPORTED: #ifdef _WIN32 return 1; // supported and available #else return 0; // not a build target off Windows #endif case REAPER_FXEMBED_WM_CREATE: refresh(); // prime the first paint's snapshot return 0; case REAPER_FXEMBED_WM_DESTROY: return 0; case REAPER_FXEMBED_WM_GETMINMAXINFO: { auto* hints = reinterpret_cast(parm3); if (!hints) return 0; // Minimum usable strip height: the keymap must not collapse below its floor // (kEmbedKeymapMinHeight) plus the level band. hints->min_width = 64; hints->max_width = 0; // 0 = unconstrained hints->min_height = kEmbedKeymapMinHeight + kEmbedLevelBandHeight; hints->max_height = 0; // 0 = unconstrained // Preferred aspect: wide strip, roughly 8:1 (w:h). 16.16 fixed point. hints->preferred_aspect = (8 << 16) / 1; hints->minimum_aspect = (4 << 16) / 1; return 1; } #ifdef _WIN32 case REAPER_FXEMBED_WM_PAINT: return paint(parm2, parm3) ? 1 : 0; case REAPER_FXEMBED_WM_LBUTTONDOWN: // Selection at most (S6): map the click to a zone; force a redraw if it changed. return onMouseDown(parm3) ? REAPER_FXEMBED_RETNOTIFY_INVALIDATE : 0; #endif default: return 0; // unhandled messages (cursor, wheel, hittest) fall through } } #ifdef _WIN32 bool ReaSamplerEmbed::paint(TPtrInt bitmap, TPtrInt drawInfo) { auto* bmp = reinterpret_cast(bitmap); auto* di = reinterpret_cast(drawInfo); if (!bmp || !di) return false; const int w = di->width; const int h = di->height; if (w <= 0 || h <= 0) return false; // Re-read live state each paint (UI thread) so the strip reflects keymap edits + bank // changes without its own timer — REAPER repaints the embed surface on its cadence. refresh(); // REAPER hands us its own bitmap sized to the embed area; draw directly into it (unlike // the editor, which owns a LICE_SysBitmap and BitBlt's). Origin is the bitmap's (0,0). LICE_FillRect(bmp, 0, 0, w, h, kColBackground, 1.0f, 0); const EmbedLayout layout = layoutEmbed(w, h); if (map_.zones.empty()) { // No opt-in zones authored: show a single faint band spanning the keymap area so the // strip reads as "present, no zones" — the default single-capture face lives in the // editor (this S6 strip mirrors the zones map only). LICE_FillRect(bmp, layout.keymap.left, layout.keymap.top, layout.keymap.width(), layout.keymap.height(), kColEmpty, 0.5f, 0); HDC dc = bmp->getDC(); SetBkMode(dc, TRANSPARENT); SetTextColor(dc, kRgbText); RECT gr{layout.keymap.left + 4, layout.keymap.top, layout.keymap.right, layout.keymap.bottom}; const std::string label = samples_.empty() ? "ReaSampler 9000 (bank empty)" : "ReaSampler 9000 (no zones)"; DrawTextA(dc, label.c_str(), -1, &gr, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX); } else { // Draw each zone as a segment across the keymap span, first-match order (so the // painted order matches selection + playback). The selected zone is highlighted. for (int i = 0; i < static_cast(map_.zones.size()); ++i) { const PerformanceZone& z = map_.zones[i]; const Rect r = zoneSegmentRect(layout, z.lowNote, z.highNote); if (r.width() <= 0) continue; const bool sel = (i == selectedZone_); LICE_FillRect(bmp, r.left, r.top, r.width(), r.height(), sel ? kColZoneSel : kColZone, 1.0f, 0); LICE_DrawRect(bmp, r.left, r.top, r.width() - 1, r.height() - 1, kColZoneBorder, 1.0f, 0); // Label the segment with the sample name when it is wide enough to read. if (r.width() >= 24) { HDC dc = bmp->getDC(); SetBkMode(dc, TRANSPARENT); SetTextColor(dc, kRgbText); RECT gr{r.left + 3, r.top, r.right - 2, r.bottom}; const std::string label = sampleLabel(samples_, z.sampleId); DrawTextA(dc, label.c_str(), -1, &gr, DT_LEFT | DT_VCENTER | DT_SINGLELINE | DT_NOPREFIX | DT_END_ELLIPSIS); } } } // The level band: a static background here; a live activity level is a later refinement // (the processor would publish a peak the UI thread reads). Draw the empty band so the // strip's geometry is complete and the DAW-verify sees the band lifecycle now. if (layout.levelBand.height() > 0) { LICE_FillRect(bmp, layout.levelBand.left, layout.levelBand.top, layout.levelBand.width(), layout.levelBand.height(), kColLevelBg, 1.0f, 0); const double level = processor_ ? processor_->embedActivityLevel() : 0.0; const Rect fill = levelFillRect(layout, level); if (fill.width() > 0) { LICE_FillRect(bmp, fill.left, fill.top, fill.width(), fill.height(), kColLevelFill, 1.0f, 0); } } return true; } bool ReaSamplerEmbed::onMouseDown(TPtrInt drawInfo) { auto* di = reinterpret_cast(drawInfo); if (!di || di->width <= 0 || di->height <= 0) return false; refresh(); const EmbedLayout layout = layoutEmbed(di->width, di->height); const std::vector zones = toEmbedZones(map_); const int hit = zoneAtPoint(layout, zones.data(), static_cast(zones.size()), di->mouse_x, di->mouse_y); if (hit == selectedZone_) return false; // no change -> no redraw selectedZone_ = hit; return true; } #endif // _WIN32 } // namespace reasampler::vst