S10: capture-first ReaSampler 9000 editor — browser, single-capture setup, zones toggle
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.
This commit is contained in:
+87
-42
@@ -1,90 +1,135 @@
|
||||
// reasampler_editor.h — the VST3 IPlugView LICE editor (Phase S1 bridge spike). THIN
|
||||
// shell: hosts a LICE-drawn child window inside the host's IPlugView seat and routes
|
||||
// host paint/click into the pure editor_geometry hit-test. Windows-only (D5).
|
||||
// reasampler_editor.h — the VST3 IPlugView LICE editor for the ReaSampler 9000
|
||||
// capture-first UI (Phase S10). THIN shell: hosts a LICE-drawn child window inside the
|
||||
// host's IPlugView seat and routes host paint/mouse into the pure geometry modules
|
||||
// (capture_browser, keyboard_strip) + the pure mapping (sample_map). Windows-only (D5).
|
||||
//
|
||||
// This is the phase's ONE genuine unknown (per CONTEXT.md §Phase S / S1): wiring LICE
|
||||
// into an IPlugView. It reuses the bank_panel LICE/SWELL competence — a LICE_SysBitmap
|
||||
// blitted in WM_PAINT, GET_X/Y_LPARAM hit-testing in WM_LBUTTONDOWN — but hangs it off a
|
||||
// child HWND created in IPlugView::attached() rather than a docked SWELL dialog.
|
||||
// The default face is the CAPTURE BROWSER: a bank-filter tab strip over a grid of
|
||||
// scannable capture cards (peak thumbnail + name + root/key badge). A fresh instance with
|
||||
// no pick shows a "pick a capture" EMPTY STATE and plays silence (the S10 policy reversal
|
||||
// of the S4 first-sample auto-play). Picking a card loads that one capture and reveals a
|
||||
// guided SINGLE-CAPTURE SETUP surface (a keyboard strip with the capture's root marker +
|
||||
// a level readout). Multi-zone keymap editing is a demoted, opt-in ZONES panel (S10-Z),
|
||||
// reached by a toggle and driven by the same keyboard_strip drag machine.
|
||||
//
|
||||
// Subclasses CPluginView (public.sdk/source/common/pluginview.h) for the IPlugView
|
||||
// refcount + attached/removed/getSize/onSize boilerplate; we override the attach/remove
|
||||
// hooks to create/destroy the child window and onSize to resize it.
|
||||
// All layout/hit-test/drag math lives in the pure modules; this shell only draws + routes
|
||||
// (a LICE_SysBitmap blitted in WM_PAINT, a WM_LBUTTONDOWN/WM_MOUSEMOVE/WM_LBUTTONUP
|
||||
// drag-state machine hit-testing via the pure resolvers). Peak thumbnails are computed
|
||||
// shell-side from the decoded WAV (bank_model's Sample carries no envelope) and cached —
|
||||
// the mirror of bank_panel::thumbnailFor. Every edit commits OFF the audio thread via the
|
||||
// processor's reloadFromBank (RT path untouched).
|
||||
//
|
||||
// Subclasses CPluginView for the IPlugView boilerplate; overrides the attach/remove hooks
|
||||
// to create/destroy the child window and onSize to resize it.
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "public.sdk/source/common/pluginview.h"
|
||||
|
||||
#include "sample_map.h" // SampleChoice (the list the editor draws)
|
||||
#include "editor_geometry.h" // Rect (the shell's sub-rect type, shared with the pure modules)
|
||||
#include "peaks.h" // Envelope (the cached peak thumbnail)
|
||||
#include "sample_map.h" // SampleChoice, BankChoice, PerformanceMap (the shell's snapshot)
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
class LICE_IBitmap; // fwd: the paint helpers take one; lice.h is included only in the .cpp
|
||||
|
||||
namespace reasampler::vst {
|
||||
|
||||
class ReaSamplerProcessor;
|
||||
|
||||
class ReaSamplerEditor : public Steinberg::CPluginView {
|
||||
public:
|
||||
// `processor` owns this editor's lifetime domain and outlives it; the editor reads
|
||||
// the live bank through it (the sample list) and drives selection + reload when the
|
||||
// user clicks a row. May be null (defensive — a real host always supplies one).
|
||||
// `processor` owns this editor's lifetime domain and outlives it; the editor reads the
|
||||
// live bank through it and drives selection/zone edits + reload on user input. May be
|
||||
// null (defensive — a real host always supplies one).
|
||||
explicit ReaSamplerEditor(ReaSamplerProcessor* processor);
|
||||
~ReaSamplerEditor() override;
|
||||
|
||||
// Accept only the Windows HWND platform type (D5: Windows-only).
|
||||
Steinberg::tresult PLUGIN_API isPlatformTypeSupported(
|
||||
Steinberg::FIDString type) override;
|
||||
|
||||
// The view is user-resizable in the spike so we exercise the onSize path.
|
||||
Steinberg::tresult PLUGIN_API canResize() override;
|
||||
|
||||
protected:
|
||||
// CPluginView hooks: systemWindow is set by the time attachedToParent() fires.
|
||||
void attachedToParent() override;
|
||||
void removedFromParent() override;
|
||||
|
||||
Steinberg::tresult PLUGIN_API onSize(Steinberg::ViewRect* newSize) override;
|
||||
|
||||
private:
|
||||
// Which face the editor shows. The browser is the default; the Zones panel is the
|
||||
// demoted opt-in view reached by the toggle. Both draw over the same snapshotted bank.
|
||||
enum class View { kBrowser, kZones };
|
||||
|
||||
// What a mouse drag is currently editing (the drag-state machine). kNone = no drag in
|
||||
// flight. The zone-edit grabs mirror keyboard_strip::ZoneGrab; kRootMarker is the
|
||||
// single-capture root drag on the setup strip.
|
||||
enum class DragKind { kNone, kRootMarker, kZoneLow, kZoneHigh, kZoneBody };
|
||||
|
||||
#ifdef _WIN32
|
||||
// Draw the current surface into the child window's DC via a LICE bitmap.
|
||||
void paint(HDC hdc);
|
||||
// Route a client-space click: bank-sample pick (left list), zone edit (right panel),
|
||||
// or the "Add Zone" button.
|
||||
void onClick(int x, int y);
|
||||
void paintBrowser(LICE_IBitmap* bmp, int w, int h);
|
||||
void paintSetup(LICE_IBitmap* bmp, const Rect& area);
|
||||
void paintZones(LICE_IBitmap* bmp, int w, int h);
|
||||
void paintEmptyState(LICE_IBitmap* bmp, const Rect& area);
|
||||
|
||||
void onMouseDown(int x, int y);
|
||||
void onMouseMove(int x, int y);
|
||||
void onMouseUp(int x, int y);
|
||||
|
||||
static LRESULT CALLBACK wndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
void invalidate();
|
||||
|
||||
HWND childHwnd_ = nullptr;
|
||||
#endif
|
||||
|
||||
// Re-read the bank's sample list from the live bridge into `samples_`, and snapshot the
|
||||
// instrument's performance map. Main/UI thread only (reads ext-state); called on attach
|
||||
// and after any edit that reloads the instrument.
|
||||
void refreshSampleList();
|
||||
// Re-read the bank (samples + banks) from the live bridge and snapshot the instrument's
|
||||
// selection + performance map. Main/UI thread only. Called on attach and after any edit.
|
||||
void refreshFromBank();
|
||||
|
||||
// Push the edited performance map to the processor and rebuild the instrument OFF the
|
||||
// audio thread. UI thread only. Centralizes the "commit an edit" path so every zone
|
||||
// mutation reloads identically.
|
||||
void commitMapAndReload();
|
||||
// Publish the edited zones/selection to the processor, then rebuild the instrument OFF
|
||||
// the audio thread. UI thread only. One place so every edit commits identically.
|
||||
void commitAndReload();
|
||||
|
||||
// Recompute the capture cards visible under the current bank filter (samples_ narrowed by
|
||||
// activeFilterBankId_; "" = All) into visible_. Called on refresh + filter change.
|
||||
void rebuildVisible();
|
||||
|
||||
// The peak thumbnail for a bank sample id at `binCount` bins, computed once from the
|
||||
// decoded WAV (mirror of bank_panel::thumbnailFor) and cached by (id, binCount). Returns
|
||||
// an empty envelope when the WAV can't be resolved/decoded. UI thread only (file I/O).
|
||||
const Envelope& thumbnailFor(const std::string& sampleId, int binCount);
|
||||
|
||||
ReaSamplerProcessor* processor_ = nullptr;
|
||||
// The bank's samples, snapshotted for the current paint. Refreshed off the audio
|
||||
// thread; the paint just draws it.
|
||||
std::vector<SampleChoice> samples_;
|
||||
// The currently-selected sample id (Tier-0 fallback + the "sample to add" for a new
|
||||
// zone), mirrored for the paint's highlight.
|
||||
std::string selectedId_;
|
||||
// The instrument's performance map, snapshotted for edit + paint. Edits mutate this
|
||||
// copy then commit it to the processor via commitMapAndReload.
|
||||
PerformanceMap map_;
|
||||
// The zone row currently highlighted (for the paint); -1 = none.
|
||||
int selectedZone_ = -1;
|
||||
|
||||
// --- Snapshot of the live bank (drawn each paint; refreshed off the audio thread) ---
|
||||
std::vector<SampleChoice> samples_; // every bank sample, bank order
|
||||
std::vector<BankChoice> banks_; // the named banks, for the filter tab strip
|
||||
std::vector<SampleChoice> visible_; // samples_ narrowed by the active bank filter
|
||||
std::string selectedId_; // the single-capture pick ("" = empty state)
|
||||
PerformanceMap map_; // the opt-in zones (empty = no zones)
|
||||
|
||||
// --- Transient UI state (not persisted; component state carries selection + zones) ---
|
||||
View view_ = View::kBrowser; // default face is the browser
|
||||
std::string activeFilterBankId_; // "" = All; else a bank id from banks_
|
||||
int selectedZone_ = -1; // highlighted zone in the Zones panel; -1 = none
|
||||
|
||||
// --- Drag-state machine ------------------------------------------------------
|
||||
DragKind drag_ = DragKind::kNone;
|
||||
int dragStartX_ = 0; // grab x (px), for the pixel-delta resolver
|
||||
int dragStartLow_ = 0; // the grabbed field's note at grab time
|
||||
int dragStartHigh_ = 0;
|
||||
int dragStartRoot_ = 60;
|
||||
|
||||
// --- Peak-thumbnail cache (mirror of bank_panel; id -> envelope at a bin width) ------
|
||||
// Keyed by "id|binCount" so a resize recomputes at the new width. Cleared on refresh so
|
||||
// a bank edit (a re-captured or deleted sample) does not show a stale thumbnail.
|
||||
std::unordered_map<std::string, Envelope> thumbCache_;
|
||||
};
|
||||
|
||||
} // namespace reasampler::vst
|
||||
|
||||
Reference in New Issue
Block a user