S4 Tier 0: the bank plays — VST3 marshals MIDI to the S3 core, reads the live bank + resolves WAV the M4 way, mono downmix, lock-free load handoff, LICE sample-pick
This commit is contained in:
@@ -7,7 +7,10 @@
|
||||
#include <string>
|
||||
|
||||
#include "editor_geometry.h"
|
||||
#include "ext_keys.h"
|
||||
#include "reaper_bridge.h"
|
||||
#include "reasampler_processor.h"
|
||||
#include "sample_map.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windowsx.h> // GET_X_LPARAM / GET_Y_LPARAM
|
||||
@@ -47,13 +50,25 @@ void drawText(LICE_IBitmap* bmp, const Rect& r, const char* s, COLORREF col) {
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
ReaSamplerEditor::ReaSamplerEditor(ReaperBridge* bridge)
|
||||
: CPluginView(nullptr), bridge_(bridge) {
|
||||
ReaSamplerEditor::ReaSamplerEditor(ReaSamplerProcessor* processor)
|
||||
: CPluginView(nullptr), processor_(processor) {
|
||||
// Default view size; the host may resize (canResize() == true).
|
||||
ViewRect r(0, 0, 420, 260);
|
||||
setRect(r);
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::refreshSampleList() {
|
||||
// Main/UI thread only — reads the live bank over the bridge (allocates, calls REAPER).
|
||||
if (!processor_) {
|
||||
samples_.clear();
|
||||
selectedId_.clear();
|
||||
return;
|
||||
}
|
||||
auto banks = processor_->bridge().readReasamplerExtState(reasampler::kProjExtBanksKey);
|
||||
samples_ = banks ? listSamples(*banks) : std::vector<SampleChoice>{};
|
||||
selectedId_ = processor_->selectedSampleId();
|
||||
}
|
||||
|
||||
ReaSamplerEditor::~ReaSamplerEditor() {
|
||||
#ifdef _WIN32
|
||||
// Defensive teardown: the host normally calls removed() (which destroys the child)
|
||||
@@ -102,6 +117,9 @@ void ReaSamplerEditor::attachedToParent() {
|
||||
classRegistered = true;
|
||||
}
|
||||
|
||||
// Snapshot the live bank so the first paint shows the sample list.
|
||||
refreshSampleList();
|
||||
|
||||
const ViewRect& r = getRect();
|
||||
childHwnd_ = CreateWindowExW(
|
||||
0, kChildClassName, L"", WS_CHILD | WS_VISIBLE, 0, 0, r.getWidth(),
|
||||
@@ -144,17 +162,13 @@ void ReaSamplerEditor::paint(HDC hdc) {
|
||||
|
||||
const EditorLayout layout = layoutEditor(w, h);
|
||||
|
||||
// Title band.
|
||||
// Title band: the plugin name + whether a live bank is linked.
|
||||
LICE_FillRect(&bmp, layout.titleBar.left, layout.titleBar.top,
|
||||
layout.titleBar.width(), layout.titleBar.height(), kColTitleBg, 1.0f,
|
||||
0);
|
||||
// Read a known live-state value over the bridge to prove the read spike. Show the
|
||||
// raw ext-state presence (never the full blob) so the title reflects live project
|
||||
// state without dumping JSON into the UI.
|
||||
std::string title = "ReaSampler Instrument";
|
||||
if (bridge_ && bridge_->isConnected()) {
|
||||
auto banks = bridge_->readReasamplerExtState("banks");
|
||||
title += banks ? " [bank: linked]" : " [bank: none]";
|
||||
if (processor_ && processor_->bridge().isConnected()) {
|
||||
title += samples_.empty() ? " [bank: empty]" : " [pick a sample]";
|
||||
} else {
|
||||
title += " [host: no bridge]";
|
||||
}
|
||||
@@ -162,27 +176,42 @@ void ReaSamplerEditor::paint(HDC hdc) {
|
||||
layout.titleBar.right - 8, layout.titleBar.bottom};
|
||||
drawText(&bmp, titleText, title.c_str(), kRgbText);
|
||||
|
||||
// The clickable button — fill reflects the last hit-test (the routing proof).
|
||||
LICE_FillRect(&bmp, layout.button.left, layout.button.top, layout.button.width(),
|
||||
layout.button.height(), buttonHit_ ? kColBtnHitBg : kColBtnBg, 1.0f,
|
||||
0);
|
||||
LICE_DrawRect(&bmp, layout.button.left, layout.button.top, layout.button.width(),
|
||||
layout.button.height(), kColBtnBorder, 1.0f, 0);
|
||||
Rect btnText{layout.button.left + 8, layout.button.top, layout.button.right,
|
||||
layout.button.bottom};
|
||||
drawText(&bmp, btnText, buttonHit_ ? "clicked" : "click me", kRgbText);
|
||||
// Sample list: one row per bank sample, the selected one highlighted. Clip drawing
|
||||
// to rows that fall within the canvas (sampleRowRect computes all; we skip off-screen
|
||||
// ones so a huge bank doesn't waste paint).
|
||||
for (int i = 0; i < static_cast<int>(samples_.size()); ++i) {
|
||||
const Rect row = sampleRowRect(layout, i);
|
||||
if (row.top >= layout.canvas.bottom) break; // past the visible area
|
||||
const bool sel = !selectedId_.empty() && samples_[i].id == selectedId_;
|
||||
LICE_FillRect(&bmp, row.left, row.top, row.width(), row.height(),
|
||||
sel ? kColBtnHitBg : kColBtnBg, 1.0f, 0);
|
||||
if (sel) {
|
||||
LICE_DrawRect(&bmp, row.left, row.top, row.width() - 1, row.height() - 1,
|
||||
kColBtnBorder, 1.0f, 0);
|
||||
}
|
||||
Rect textR{row.left + 8, row.top, row.right - 8, row.bottom};
|
||||
const std::string& name = samples_[i].displayName;
|
||||
drawText(&bmp, textR, name.empty() ? samples_[i].id.c_str() : name.c_str(),
|
||||
kRgbText);
|
||||
}
|
||||
|
||||
BitBlt(hdc, 0, 0, w, h, bmp.getDC(), 0, 0, SRCCOPY);
|
||||
}
|
||||
|
||||
void ReaSamplerEditor::onClick(int x, int y) {
|
||||
if (!processor_) return;
|
||||
RECT cr{};
|
||||
GetClientRect(childHwnd_, &cr);
|
||||
const EditorLayout layout = layoutEditor(cr.right - cr.left, cr.bottom - cr.top);
|
||||
if (hitTest(layout, x, y) == HitTarget::kButton) {
|
||||
buttonHit_ = !buttonHit_;
|
||||
InvalidateRect(childHwnd_, nullptr, FALSE);
|
||||
}
|
||||
const int row = sampleRowHitTest(layout, static_cast<int>(samples_.size()), x, y);
|
||||
if (row < 0) return;
|
||||
|
||||
// Select this sample and rebuild the instrument OFF the audio thread (this WM_
|
||||
// handler runs on the UI thread). reloadFromBank reads the id we just set.
|
||||
processor_->setSelectedSampleId(samples_[row].id);
|
||||
processor_->reloadFromBank();
|
||||
selectedId_ = samples_[row].id;
|
||||
InvalidateRect(childHwnd_, nullptr, FALSE);
|
||||
}
|
||||
|
||||
LRESULT CALLBACK ReaSamplerEditor::wndProc(HWND hwnd, UINT msg, WPARAM wParam,
|
||||
|
||||
Reference in New Issue
Block a user