7151e19432
Reverse S4 auto-select (empty=silence+empty state), add peak-thumbnail capture browser + bank filter + keyboard-strip drag machine, v3 component state (selection + zones), and the S-NAME-1 filename/display rename (UID locked). MSVC min/max macro collisions fixed post-implementation; 26/26 tests green.
227 lines
9.8 KiB
C++
227 lines
9.8 KiB
C++
// 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 <string>
|
|
#include <vector>
|
|
|
|
#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 <windows.h> 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<SampleChoice>& 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<EmbedZone> toEmbedZones(const PerformanceMap& map) {
|
|
std::vector<EmbedZone> 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<SampleChoice>{};
|
|
map_ = processor_->performanceMap();
|
|
if (selectedZone_ >= static_cast<int>(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<REAPER_FXEMBED_SizeHints*>(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<LICE_IBitmap*>(bitmap);
|
|
auto* di = reinterpret_cast<const REAPER_FXEMBED_DrawInfo*>(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<int>(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<const REAPER_FXEMBED_DrawInfo*>(drawInfo);
|
|
if (!di || di->width <= 0 || di->height <= 0) return false;
|
|
refresh();
|
|
const EmbedLayout layout = layoutEmbed(di->width, di->height);
|
|
const std::vector<EmbedZone> zones = toEmbedZones(map_);
|
|
const int hit = zoneAtPoint(layout, zones.data(), static_cast<int>(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
|