Cut shell/instrument comment bloat ~34% (comments only, zero code change)

This commit is contained in:
2026-07-29 20:48:43 -04:00
parent 1f24c4b095
commit d4d29146c7
19 changed files with 1131 additions and 1769 deletions
+64 -87
View File
@@ -1,10 +1,8 @@
// editor_session.cpp — the ReaSamplerEditor's SESSION/BRIDGE state (Q-W2v split of
// reasampler_editor.cpp, T4-11): construction, the live-bank snapshot (refreshFromBank /
// rebuildVisible), the S9/S8 sync tick, the commit-and-reload seam, selection loading,
// the picked-capture marker resolution/upsert helpers, and the decoded-PCM + peak
// thumbnail caches (the mirror of bank_panel's, keyed through the pure ThumbnailKey —
// T2-10 rider). UI thread only; every edit commits OFF the audio thread via the
// processor's reloadInstrument.
// editor_session.cpp — the ReaSamplerEditor's session/bridge state: construction, the
// live-bank snapshot (refreshFromBank / rebuildVisible), the sync tick, the
// commit-and-reload seam, selection loading, the picked-capture marker resolution/upsert
// helpers, and the decoded-PCM + peak thumbnail caches. UI thread only; every edit commits
// off the audio thread via the processor's reloadInstrument.
#include "shell/instrument/reasampler_editor.h"
@@ -14,12 +12,12 @@
#include <vector>
#include "core/audio/peaks.h" // computeEnvelope (the cached peak thumbnail)
#include "core/capture/capture_paths.h" // resolveBankFile (shared M4 path resolution)
#include "core/capture/capture_paths.h" // resolveBankFile (shared path resolution)
#include "core/capture/wav_codec.h" // parseWavLayout, extractFloatFrames
#include "core/ui/bank_grid.h" // ThumbnailKey / thumbnailKeyString (T2-10: the pure key)
#include "core/util/file_bytes.h" // shared whole-file loader (Q-W1, T2-03)
#include "core/ui/bank_grid.h" // ThumbnailKey / thumbnailKeyString (the pure key)
#include "core/util/file_bytes.h" // shared whole-file loader
#include "ext_keys.h"
#include "core/instrument/ui/browser_scroll.h" // nameMatchesQuery (S12 type-to-filter)
#include "core/instrument/ui/browser_scroll.h" // nameMatchesQuery (type-to-filter)
#include "shell/instrument/reaper_bridge.h"
#include "shell/instrument/reasampler_processor.h"
@@ -40,11 +38,8 @@ using util::readFileBytes;
ReaSamplerEditor::ReaSamplerEditor(ReaSamplerProcessor* processor)
: CPluginView(nullptr), processor_(processor) {
// Default view size (S-VIEW-SIZE-1 tuned to the concrete Sample-face band heights). The Sample
// home stacks: title (26) + hero waveform (150) + cluster (52) + the control strip, whose Gate
// mode shows 12 rows at ~26px ≈ 312px. 840×620 clears the full three-band face without scroll
// on a 1080p screen with headroom. Wide enough that the control strip's label + value columns
// read comfortably.
// Default view size, tuned to the Sample-face band heights: title + hero waveform +
// cluster + control strip. 840x620 clears the full face without scroll on 1080p.
ViewRect r(0, 0, 840, 620);
setRect(r);
}
@@ -52,7 +47,7 @@ ReaSamplerEditor::ReaSamplerEditor(ReaSamplerProcessor* processor)
void ReaSamplerEditor::refreshFromBank() {
// Main/UI thread only — reads the live bank over the bridge (allocates, calls REAPER).
thumbCache_.clear(); // a bank edit may have re-captured/removed a sample; drop stale peaks
pcmCache_.clear(); // and its decoded PCM (the S11 waveform + snap source)
pcmCache_.clear(); // and its decoded PCM (the waveform + snap source)
if (!processor_) {
samples_.clear();
banks_.clear();
@@ -69,17 +64,15 @@ void ReaSamplerEditor::refreshFromBank() {
const auto prevZoneCount = static_cast<int>(map_.zones.size());
map_ = processor_->performanceMap();
channelMode_ = processor_->channelMode();
voiceCount_ = processor_->voiceCount(); // Phase S voice-deck snapshot
voiceCount_ = processor_->voiceCount();
voiceMode_ = processor_->voiceMode();
monoTrigger_ = processor_->monoTrigger();
if (selectedZone_ >= static_cast<int>(map_.zones.size())) selectedZone_ = -1;
// r11: a refresh that emptied the selection (a bank change on the sync tick) closes the
// curve popup — the empty-state Sample face no longer draws it, and an open-but-invisible
// modal would swallow clicks.
// A refresh that emptied the selection closes the curve popup — an open-but-invisible
// modal would otherwise swallow clicks on the empty state.
if (selectedId_.empty() && map_.zones.empty()) curvePopupOpen_ = false;
// FB2: on the Zone surface the popup edits the SELECTED zone; close it if the zones list
// shrank (selectedZone_ past-end), OR if the zone count changed at all — a mid-list
// deletion leaves selectedZone_ in range but now naming a DIFFERENT zone (silent retarget).
// On the Zone surface, close the popup if the zone count changed at all — a mid-list
// deletion can leave selectedZone_ in range but silently naming a different zone.
if (view_ == View::kZone && curvePopupOpen_) {
const auto newZoneCount = static_cast<int>(map_.zones.size());
if (selectedZone_ < 0 || newZoneCount != prevZoneCount) curvePopupOpen_ = false;
@@ -94,8 +87,7 @@ void ReaSamplerEditor::refreshFromBank() {
}
void ReaSamplerEditor::rebuildVisible() {
// S12 composition: the bank filter picks the bank FIRST, then the type-to-filter search
// narrows the survivors by name substring (nameMatchesQuery — empty query is the identity).
// Bank filter first, then type-to-filter search narrows by name substring.
visible_.clear();
for (const SampleChoice& s : samples_) {
const bool inBank = activeFilterBankId_.empty() || s.bankId == activeFilterBankId_;
@@ -103,39 +95,30 @@ void ReaSamplerEditor::rebuildVisible() {
const std::string& name = s.displayName.empty() ? s.id : s.displayName;
if (nameMatchesQuery(name, searchQuery_)) visible_.push_back(s);
}
// NOTE: scrollOffset_ is clamped at paint + wheel time (where the browser layout / panel
// height is known); rebuildVisible runs cross-platform + on the sync-timer refresh, so it
// must not reset the user's scroll here.
// scrollOffset_ is clamped at paint/wheel time (where layout is known); this runs on
// the sync-timer refresh too, so it must not reset the user's scroll here.
}
#ifdef _WIN32
// Windows-only (the WM_TIMER cadence + invalidate() are the Win32 child-window path). Declared
// under the same _WIN32 guard in the header; keep the definition guarded to match (D5 makes
// Windows the only build target, but the TU must still compile elsewhere).
// Windows-only (the WM_TIMER cadence + invalidate() are the Win32 child-window path).
void ReaSamplerEditor::onSyncTimer() {
// UI thread (WM_TIMER). Poll the S9 bank generation + the S8 assignment request via the
// processor (off the audio thread — the poll itself never touches process()). NEVER while a
// drag is in flight: a reload mid-drag would rebuild the instrument and repaint under the
// user's cursor, yanking the edit. The next tick (500 ms) picks up the change after release.
// UI thread (WM_TIMER). Never while a drag is in flight: a reload mid-drag would
// rebuild the instrument and repaint under the cursor, yanking the edit — the next
// tick picks up the change after release.
if (!processor_) return;
if (drag_ != DragKind::kNone) return; // defer past the in-flight edit
// An open editor marks THIS instance the focused assignment target (the thundering-herd
// policy — only an editor-open instance applies a pending assign; see the handoff). Pass
// true so this instance consumes the request; instances with no editor open do not poll at
// all (the timer is bound to the child window), so they never contend for the request.
// An open editor is the focused assignment target (thundering-herd policy); instances
// with no editor open never poll (the timer is bound to the child window).
const ReaSamplerProcessor::BankSyncResult r = processor_->pollBankSync(/*isFocusedTarget=*/true);
// Re-snapshot the editor's own view only when something changed (a reload from a bank
// content change, or an applied assignment). refreshFromBank re-reads the bank blob + the
// processor's (possibly just-updated) selection/map and drops the stale thumbnail/PCM
// caches, then repaints — so the browser + setup surface reflect the new bank hands-free.
// Re-snapshot only when something changed.
if (r.reloaded || r.applied) {
refreshFromBank();
invalidate();
}
// S13: decay the drop-affordance banner so it auto-dismisses a few ticks after a drop.
// Decay the drop-affordance banner so it auto-dismisses a few ticks after a drop.
if (dropHintTicks_ > 0) {
--dropHintTicks_;
invalidate();
@@ -144,18 +127,16 @@ void ReaSamplerEditor::onSyncTimer() {
#endif // _WIN32
void ReaSamplerEditor::commitAndReload() {
// UI thread only. Publish the edited selection + zones to the processor, then rebuild
// the instrument off the audio thread (reloadInstrument bakes them into the live Keymap).
// pS: the reload also COPIES the picked capture's file ref + intrinsics from the bank
// blob into the instance-owned refs table (refreshRefsFromBank) — a browser load is the
// moment the instance becomes self-contained for that sample.
// UI thread only. Publishes the edited selection + zones, then rebuilds off the audio
// thread. The reload also copies the picked capture's file ref + intrinsics into the
// instance-owned refs table — a browser load is the moment the instance becomes
// self-contained for that sample.
if (!processor_) return;
processor_->setSelectedSampleId(selectedId_);
processor_->setPerformanceMap(map_);
processor_->reloadInstrument();
// GA: the reload may have AUTO-DEFAULTED the channel mode from the loaded capture's
// channel count (implicit mode only) — re-read so the Mono/Stereo toggle draws the mode
// the engine actually decoded with.
// The reload may have auto-defaulted the channel mode (implicit only) — re-read so the
// toggle draws what the engine actually decoded with.
channelMode_ = processor_->channelMode();
#ifdef _WIN32
invalidate();
@@ -163,10 +144,9 @@ void ReaSamplerEditor::commitAndReload() {
}
void ReaSamplerEditor::loadSelection(const std::string& id) {
// Zone-bleed fix (3a): a Sample-face load REPLACES the loaded sound. The previous
// sample's materialized full-range zone must not linger first-match resolve would
// keep playing it while the editor draws the new pick's zone (matched by sampleId,
// order-blind). Authored Zone-view maps (any narrow key range) are left untouched.
// A Sample-face load REPLACES the loaded sound: the previous sample's materialized
// full-range zone must not linger, or first-match resolve would keep playing it.
// Authored Zone-view maps (narrow key ranges) are left untouched.
selectedId_ = id;
if (reconcileSingleCaptureZones(map_, selectedId_)) {
selectedZone_ = map_.zones.empty() ? -1 : 0;
@@ -176,11 +156,11 @@ void ReaSamplerEditor::loadSelection(const std::string& id) {
ReaSamplerEditor::SetupMarkers ReaSamplerEditor::pickedMarkers(std::int64_t frames) const {
SetupMarkers m;
// Seed from the bank's S2 intrinsic loop (fact about the file), then let a per-zone override
// for the picked id win (the instrument's performance choice, D-B). Read the loop intrinsic
// from the live bank blob (the same path selectSample uses); when that is not readable
// (extension absent / not yet parsed) the instance-OWNED ref carries the same intrinsics
// (pS fallback). The override lives in map_.
// Seed from the bank's intrinsic loop (fact about the file), then let a per-zone override
// for the picked id win (the instrument's performance choice). Read the loop intrinsic from
// the live bank blob (the same path selectSample uses); when that is not readable (extension
// absent / not yet parsed) the instance-owned ref carries the same intrinsics. The override
// lives in map_.
if (processor_) {
std::optional<SelectedSample> sel;
auto banksJson =
@@ -215,10 +195,10 @@ ReaSamplerEditor::SetupMarkers ReaSamplerEditor::pickedMarkers(std::int64_t fram
}
int ReaSamplerEditor::upsertPickedOverride(const SetupMarkers& m) {
// Find-or-append the zone for selectedId_ and write the loop/start override fields.
// The bank intrinsic is NEVER written (read-only bank consumer, D-B). selectedId_ must
// be non-empty; callers are responsible for that guard.
// Returns the zone index (0-based) so callers can update selectedZone_.
// Find-or-append the zone for selectedId_ and write the loop/start override fields. The
// bank intrinsic is never written (read-only bank consumer). selectedId_ must be
// non-empty; callers are responsible for that guard. Returns the zone index (0-based) so
// callers can update selectedZone_.
SampleLoop loop;
loop.hasLoop = m.hasLoop;
loop.start = m.loopStart;
@@ -243,8 +223,8 @@ int ReaSamplerEditor::upsertPickedOverride(const SetupMarkers& m) {
PerformanceZone ReaSamplerEditor::effectiveSampleZone() const {
// The picked id's one-zone override, if the map already carries one; else a product-default
// zone bound to the picked id (NOT appended — a read-only resolve; a control edit materializes
// it via ensureSampleZone). Mirrors the S15-F2 single-storage-site lean.
// zone bound to the picked id (not appended — a read-only resolve; a control edit
// materializes it via ensureSampleZone).
for (const PerformanceZone& z : map_.zones) {
if (z.sampleId == selectedId_) return z;
}
@@ -280,11 +260,10 @@ int ReaSamplerEditor::ensureSampleZone() {
}
void ReaSamplerEditor::commitPickedMarkers(const SetupMarkers& m) {
// Materialize the edited markers as a per-zone loop/start override on the picked id (upsert,
// mirror of the root-marker path): a full-keyboard zone carrying the override. This plays
// identically to the un-zoned single capture (one chromatic zone) and round-trips through
// the component state; the zone becomes visible if the user opens the Zones panel. The bank
// intrinsic is NEVER written (read-only bank consumer, D-B).
// Materialize the edited markers as a per-zone loop/start override on the picked id (upsert):
// a full-keyboard zone carrying the override. This plays identically to the un-zoned single
// capture (one chromatic zone) and round-trips through the component state; the zone becomes
// visible if the user opens the Zones panel. The bank intrinsic is never written.
if (selectedId_.empty()) return;
upsertPickedOverride(m);
commitAndReload();
@@ -294,11 +273,11 @@ const std::vector<AudioSample>& ReaSamplerEditor::monoPcmFor(const std::string&
auto it = pcmCache_.find(sampleId);
if (it != pcmCache_.end()) return it->second;
// SampleChoice is the browser's metadata projection and does NOT carry the WAV path, so
// SampleChoice is the browser's metadata projection and does not carry the WAV path, so
// resolve the path from the live bank blob (selectSample) and decode via the shared WAV
// parse — the mirror of the processor's decodeRelative. Every failure path caches an EMPTY
// vector so a broken/missing file is not re-decoded on every paint. Keyed by id (width-
// independent) — the thumbnail bins this at whatever width, the snap scans it directly.
// parse. Every failure path caches an empty vector so a broken/missing file is not
// re-decoded on every paint. Keyed by id (width-independent) — the thumbnail bins this at
// whatever width, the snap scans it directly.
std::string relativePath;
std::vector<AudioSample> mono;
if (processor_) {
@@ -308,9 +287,9 @@ const std::vector<AudioSample>& ReaSamplerEditor::monoPcmFor(const std::string&
if (auto sel = selectSample(*banksJson, sampleId)) relativePath = sel->relativePath;
}
if (relativePath.empty()) {
// pS fallback: the bank blob is not readable (extension absent / not yet parsed)
// or the id went stale there — the instance-OWNED ref still carries the path, so
// a self-contained instance draws its loaded sound's waveform regardless.
// Fallback: the bank blob is not readable (extension absent / not yet parsed) or
// the id went stale there — the instance-owned ref still carries the path, so a
// self-contained instance draws its loaded sound's waveform regardless.
const SampleRefs refs = processor_->sampleRefs();
if (const SelectedSample* r = findRef(refs, sampleId)) {
relativePath = r->relativePath;
@@ -319,8 +298,7 @@ const std::vector<AudioSample>& ReaSamplerEditor::monoPcmFor(const std::string&
if (!relativePath.empty()) {
const std::string projectDir = processor_->bridge().activeProjectDir();
const std::string abs = resolveBankFile(projectDir, relativePath);
// Shared core/util whole-file loader (Q-W1, T2-03): empty on any failure.
const std::vector<std::uint8_t> bytes = readFileBytes(abs);
const std::vector<std::uint8_t> bytes = readFileBytes(abs); // empty on any failure
const WavLayout layout = parseWavLayout(bytes);
if (layout.valid) {
std::vector<AudioSample> interleaved =
@@ -334,17 +312,16 @@ const std::vector<AudioSample>& ReaSamplerEditor::monoPcmFor(const std::string&
}
const Envelope& ReaSamplerEditor::thumbnailFor(const std::string& sampleId, int binCount) {
// T2-10 rider: key through the PURE ThumbnailKey (bank_grid) instead of the former
// ad-hoc "id|binCount" concat, so both thumbnail pipelines share one tested key
// grammar (length-prefixed id — collision-proof). The editor invalidates by wholesale
// clear() on refresh/resize, so the bank generation carries no information here — 0.
// Key through the pure ThumbnailKey (bank_grid, length-prefixed id — collision-proof) so
// both thumbnail pipelines share one tested key grammar. The editor invalidates by
// wholesale clear() on refresh/resize, so the bank generation carries no information here.
const std::string key =
thumbnailKeyString(ThumbnailKey{sampleId, binCount, /*generation=*/0});
auto it = thumbCache_.find(key);
if (it != thumbCache_.end()) return it->second;
// Bin the (cached) decoded mono PCM at the requested width — one decode per id, reused by
// every thumbnail width AND the S11 waveform surface + snap.
// every thumbnail width AND the waveform surface + snap.
const std::vector<AudioSample>& mono = monoPcmFor(sampleId);
Envelope env;
if (!mono.empty()) {