242 lines
10 KiB
C++
242 lines
10 KiB
C++
// reasampler_embed.cpp — see reasampler_embed.h. The IReaperUIEmbedInterface shell.
|
|
// Windows-only; guarded so a non-Windows build degrades to a stub that reports "not
|
|
// supported" and draws nothing.
|
|
|
|
#include "shell/instrument/reasampler_embed.h"
|
|
|
|
#include <algorithm>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include "core/version/app_version.h" // vstPluginName (channel-derived embed label)
|
|
#include "core/instrument/map/bank_sync.h" // parseBankGeneration (dirty-guard over the per-paint refresh)
|
|
#include "core/ui/component_geometry.h" // KitBox — the kit text/fill draw box
|
|
#include "shell/panel/draw_kit.h" // the shared draw kit: fillSurface/text
|
|
#include "core/instrument/ui/editor_geometry.h" // Rect (shared with embed_strip)
|
|
#include "core/instrument/ui/embed_strip.h" // the pure strip layout + hit-test
|
|
#include "ext_keys.h" // kProjExtBanksKey / kProjExtBankGenKey
|
|
#include "shell/instrument/reaper_bridge.h"
|
|
#include "shell/instrument/reasampler_processor.h"
|
|
#include "core/ui/theme.h" // Role / InteractionState / spectralColor
|
|
|
|
// wdltypes.h first: it defines INT_PTR portably (needed by REAPER_FXEMBED_IBitmap::Extended's
|
|
// return type in the header below).
|
|
#include "wdltypes.h"
|
|
|
|
// REAPER's embed message/bitmap contract (vendored): REAPER_FXEMBED_IBitmap aliases
|
|
// LICE_IBitmap; WM_* / DrawInfo / SizeHints 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;
|
|
|
|
// This is the only TU that answers queryInterface for IReaperUIEmbedInterface, so the
|
|
// DEFINE lives here (mirrors reaper_bridge.cpp's IReaperHostApplication).
|
|
DEF_CLASS_IID(Steinberg::IReaperUIEmbedInterface)
|
|
|
|
namespace reasampler::vst {
|
|
|
|
using namespace reasampler::instrument::map;
|
|
using namespace reasampler::instrument::ui;
|
|
using reasampler::ui::spectralColor;
|
|
using version::vstPluginName;
|
|
|
|
namespace {
|
|
#ifdef _WIN32
|
|
// Kit adapter: the embed shell's Rect -> the kit's KitBox.
|
|
KitBox toKitBox(const Rect& r) {
|
|
return KitBox{r.x, r.y, r.width, r.height};
|
|
}
|
|
|
|
// A short display name for a bank sample id (small duplicate of the editor's helper
|
|
// 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
|
|
|
|
} // 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;
|
|
}
|
|
|
|
// The loaded capture id + effective root, both cheap in-process accessors.
|
|
void ReaSamplerEmbed::refreshLoaded() {
|
|
loadedId_ = processor_->selectedSampleId();
|
|
const InstrumentParams params = processor_->instrumentParams();
|
|
if (params.rootOverride) {
|
|
rootNote_ = *params.rootOverride;
|
|
} else {
|
|
const SampleRefs refs = processor_->sampleRefs();
|
|
if (const SelectedSample* ref = findRef(refs, loadedId_)) {
|
|
rootNote_ = ref->rootNote;
|
|
} else {
|
|
rootNote_ = 60;
|
|
}
|
|
}
|
|
}
|
|
|
|
void ReaSamplerEmbed::refresh() {
|
|
if (!processor_) {
|
|
samples_.clear();
|
|
loadedId_.clear();
|
|
rootNote_ = 60;
|
|
return;
|
|
}
|
|
auto banks = processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
|
samples_ = banks ? listSamples(*banks) : std::vector<SampleChoice>{};
|
|
refreshLoaded();
|
|
}
|
|
|
|
void ReaSamplerEmbed::maybeRefresh() {
|
|
if (!processor_) { refresh(); return; } // clears state; cheap
|
|
|
|
// The loaded capture + root are cheap in-process accessors, and the editor may change
|
|
// either with no bank-content change — always re-snapshot so an edit reflects at once.
|
|
refreshLoaded();
|
|
|
|
// The expensive part is the bank-blob bridge read: gate it on the bank-generation
|
|
// stamp, re-reading only when it changed (or on the first paint). A project with no
|
|
// stamp reads generation 0; the first paint folds it and idle paints skip the read.
|
|
std::int64_t currentGen = lastSeenBankGeneration_;
|
|
if (auto rawGen =
|
|
processor_->bridge().readReasamplerExtState(reasampler::kProjExtBankGenKey)) {
|
|
currentGen = parseBankGeneration(*rawGen);
|
|
} else if (lastSeenBankGeneration_ < 0) {
|
|
currentGen = 0; // unprimed + no stamp: treat as generation 0 for the first read
|
|
}
|
|
// Intentional asymmetry: a transient bridge failure after priming leaves currentGen
|
|
// unchanged, skipping the read — a stale-but-intact list beats clearing samples_ on
|
|
// every hiccup.
|
|
|
|
if (lastSeenBankGeneration_ < 0 || currentGen != lastSeenBankGeneration_) {
|
|
auto banks =
|
|
processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
|
samples_ = banks ? listSamples(*banks) : std::vector<SampleChoice>{};
|
|
lastSeenBankGeneration_ = currentGen;
|
|
}
|
|
}
|
|
|
|
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:
|
|
#ifdef _WIN32
|
|
// Idempotent + process-global (shared with the editor); not torn down per-view
|
|
// — the OS reclaims the tiny static HFONT set at module unload.
|
|
kitFontsInit();
|
|
#endif
|
|
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;
|
|
// The keymap must not collapse below its floor 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;
|
|
#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 (no own timer) — REAPER repaints the embed surface on
|
|
// its own cadence.
|
|
maybeRefresh();
|
|
|
|
// 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).
|
|
fillSurface(bmp, KitBox{0, 0, w, h}, Role::BgBase, InteractionState::Rest);
|
|
|
|
const EmbedLayout layout = layoutEmbed(w, h);
|
|
|
|
if (loadedId_.empty()) {
|
|
// Nothing loaded: a faint band so the strip reads as "present, silent" — the pick
|
|
// affordance lives in the editor.
|
|
LICE_FillRect(bmp, layout.keymap.x, layout.keymap.y, layout.keymap.width,
|
|
layout.keymap.height, toLice(roleColor(Role::BgCell)), 0.5f, 0);
|
|
const std::string label = version::vstPluginName() + // channel-derived
|
|
(samples_.empty() ? " (bank empty)" : " (pick a capture)");
|
|
const Rect labelR = Rect::ltrb(layout.keymap.x + 4, layout.keymap.y, layout.keymap.right(),
|
|
layout.keymap.bottom());
|
|
text(bmp, toKitBox(labelR), label.c_str(), Font::Label, Role::TextPrimary, Align::Left);
|
|
} else {
|
|
// The loaded capture spans the whole keyboard, drawn in its root's spectral hue so
|
|
// the strip reads as the same spectrum as the editor's keyboard strip; the root key
|
|
// lifts to accent-primary with a static glow ("this is where it plays at unity").
|
|
const Rect span = keySpanRect(layout, 0, 127);
|
|
LICE_FillRect(bmp, span.x, span.y, span.width, span.height,
|
|
toLice(spectralColor(rootNote_ / 127.0)), 0.65f, 0);
|
|
LICE_DrawRect(bmp, span.x, span.y, span.width - 1, span.height - 1,
|
|
toLice(roleColor(Role::LineHairline)), 1.0f, 0);
|
|
const Rect root = keySpanRect(layout, rootNote_, rootNote_);
|
|
const int rw = (std::max)(2, root.width);
|
|
LICE_FillRect(bmp, root.x - 2, root.y, rw + 4, root.height,
|
|
toLice(roleColor(Role::AccentHot)), 0.30f, 0);
|
|
LICE_FillRect(bmp, root.x, root.y, rw, root.height,
|
|
toLice(roleColor(Role::AccentPrimary)), 1.0f, 0);
|
|
if (span.width >= 24) {
|
|
const Rect lr = Rect::ltrb(span.x + 3, span.y, span.right() - 2, span.bottom());
|
|
text(bmp, toKitBox(lr), sampleLabel(samples_, loadedId_).c_str(), Font::Label,
|
|
Role::TextPrimary, Align::Left);
|
|
}
|
|
}
|
|
|
|
// The level band: a recessed channel with an accent-primary fill tracking the live
|
|
// activity level (the one permitted "motion").
|
|
if (layout.levelBand.height > 0) {
|
|
fillSurface(bmp, toKitBox(layout.levelBand), Role::BgCell, InteractionState::Pressed);
|
|
const double level = processor_ ? processor_->embedActivityLevel() : 0.0;
|
|
const Rect fill = levelFillRect(layout, level);
|
|
if (fill.width > 0) {
|
|
LICE_FillRect(bmp, fill.x, fill.y, fill.width, fill.height,
|
|
toLice(roleColor(Role::AccentPrimary)), 1.0f, 0);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#endif // _WIN32
|
|
|
|
} // namespace reasampler::vst
|