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
+53 -89
View File
@@ -1,10 +1,9 @@
// processor_state.cpp — the ReaSamplerProcessor's COMPONENT-STATE I/O (setState /
// getState against the component_state_io codec) and its UI-thread parameter
// accessors/setters (selection, performance map, channel mode, preview velocity,
// voice-system params, master gain, preview-note mailbox posts). Split out of
// reasampler_processor.cpp (Q-W2v, T4-12). Everything here runs OFF the audio
// thread (UI / host load-save); the setters hand work to the reload family
// (processor_reload.cpp) or store atomics process() picks up at block start.
// processor_state.cpp — ReaSamplerProcessor's component-state I/O (setState/getState
// against the component_state_io codec) and its UI-thread parameter accessors/setters
// (selection, performance map, channel mode, preview velocity, voice-system params,
// master gain, preview-note mailbox posts). Everything here runs off the audio thread;
// setters hand work to the reload family (processor_reload.cpp) or store atomics
// process() picks up at block start.
#include "shell/instrument/reasampler_processor.h"
@@ -14,8 +13,8 @@
#include "pluginterfaces/base/ibstream.h"
#include "core/instrument/engine/master_gain.h" // masterGainMaxLinear (FB1 post-mixer gain clamp)
#include "core/instrument/map/component_state_io.h" // the ComponentState codec (Q-W2v split)
#include "core/instrument/engine/master_gain.h" // masterGainMaxLinear (post-mixer gain clamp)
#include "core/instrument/map/component_state_io.h" // the ComponentState codec
#include "core/instrument/map/sample_map.h" // reconcileSingleCaptureZones / retainRefs / referencedSampleIds
using namespace Steinberg;
@@ -24,135 +23,107 @@ using namespace Steinberg::Vst;
namespace reasampler::vst {
using namespace instrument::map; // the codec + resolution vocabulary this TU marshals
using instrument::engine::masterGainMaxLinear; // FB1 taper ceiling (Q-W6: shim retired)
using instrument::engine::masterGainMaxLinear; // taper ceiling
tresult PLUGIN_API ReaSamplerProcessor::setState(IBStream* state) {
if (!state) return kResultFalse;
// Read the whole component-state blob (the performance map, versioned). The blob is
// small; read in one shot into a growable buffer.
// The blob is small; read it in one shot into a growable buffer.
std::vector<std::uint8_t> bytes;
std::uint8_t chunk[256];
int32 got = 0;
while (state->read(chunk, sizeof(chunk), &got) == kResultOk && got > 0) {
bytes.insert(bytes.end(), chunk, chunk + got);
}
// Component state (v3, S10) is {single-capture selection id, opt-in zones}. The
// selection and the zones are DISTINCT — the default face is one picked capture, zones
// are a demoted overlay — so both are restored explicitly (no more inferring a selection
// from a lone zone). deserializeComponentState lifts older blobs cleanly: a v2 zones-only
// blob restores {"", zones}; a v1 S4 single-selection blob restores {id, one-zone map} so
// the old pick survives as both; an empty/unknown blob restores {"", no zones} — the S10
// silent empty state (no first-sample fallback in reloadInstrument).
// Pass sampleRate_ as the project rate for legacy v3 blob conversion (frames -> seconds at
// the v3 read boundary). sampleRate_ is set by setupProcessing; REAPER calls setupProcessing
// before setState on project load, so sampleRate_ is the real host rate here. A v3 blob on a
// pre-setup call would assert inside readZonesPayload (a programming error, not a field case).
// Component state is {single-capture selection id, opt-in zones}, restored explicitly
// since they're distinct (default face vs. a demoted overlay). deserializeComponentState
// lifts older blobs cleanly (no first-sample fallback in reloadInstrument). sampleRate_
// is the real host rate here — REAPER calls setupProcessing before setState on load.
const ComponentState cs = deserializeComponentState(bytes, sampleRate_);
setSelectedSampleId(cs.selectionId);
// Zone-bleed fix (3a) heal-on-load: a blob saved under the pre-fix editor may carry a
// pile of stale full-range zones (one per sample ever browsed), the oldest shadowing the
// saved selection under first-match resolve. Reconciling here restores "the sample the
// editor shows is the sample the engine plays" for already-affected projects; authored
// Zone-view maps (any narrow key range) pass through untouched.
// Heal-on-load: a blob saved under the pre-fix editor may carry stale full-range zones
// (one per sample ever browsed), the oldest shadowing the saved selection under
// first-match resolve. Authored Zone-view maps (narrow key ranges) pass through untouched.
PerformanceMap restored = cs.map;
reconcileSingleCaptureZones(restored, cs.selectionId); // bool return ignored: setPerformanceMap + reloadInstrument run unconditionally on load
reconcileSingleCaptureZones(restored, cs.selectionId); // bool return ignored: reload below runs unconditionally
setPerformanceMap(restored);
// S8: restore the last-consumed assignment generation so a re-open does not re-apply a
// stale assign_request (the user may have manually changed the selection after the assign).
// Restore the last-consumed assignment generation so a re-open does not re-apply a
// stale assign_request.
{
std::lock_guard<std::mutex> lock(assignMarkerMutex_);
lastConsumedAssignGeneration_ = cs.lastConsumedAssignGeneration;
}
// Restore the S7 channel mode + the GA explicit flag. The output bus is FIXED stereo (see
// initialize) — the mode only governs how the reload below decodes, so no bus work here.
// The output bus is fixed stereo (see initialize) — the mode only governs decode below.
{
std::lock_guard<std::mutex> lock(channelModeMutex_);
channelMode_ = cs.channelMode;
channelModeExplicit_ = cs.channelModeExplicit;
}
// S-VIEW-4: restore the per-instance preview velocity. Guarded by previewMutex_ — since Wave 2
// the editor's velocity knob is a concurrent UI-thread writer.
{
std::lock_guard<std::mutex> lock(previewMutex_);
previewVelocity_ = cs.previewVelocity;
}
// Phase S: restore the voice-system parameters (v7; older blobs lift to {16, Poly,
// Retrigger} in deserializeComponentState — pre-Phase-S behavior). Restored BEFORE the
// reload below so the rebuilt engine is born with the saved polyphony/mode.
// Restore before the reload so the rebuilt engine is born with the saved polyphony/mode.
{
std::lock_guard<std::mutex> lock(voiceParamsMutex_);
voiceCount_ = cs.voiceCount;
voiceMode_ = cs.voiceMode;
monoTrigger_ = cs.monoTrigger;
}
// FB1: restore the post-mixer master gain (v8; older blobs lift to unity in
// deserializeComponentState — pre-FB1 output). One atomic store; the audio thread picks
// it up at the next block start.
setMasterGainLinear(cs.masterGainLinear);
// pS self-contained playback: restore the instance-OWNED sample refs (v10) BEFORE the
// reload so it decodes straight from them — no bank read required to play. A pre-v10
// blob lifts to an EMPTY table; the reload then resolves nothing until the bank blob
// becomes readable (the reload's opportunistic refresh, or pollBankSync's legacy lift),
// after which the next save is self-contained.
// Restore the instance-owned sample refs before the reload so it decodes straight from
// them — no bank read required. A pre-v10 blob lifts to an empty table; the reload
// resolves nothing until the bank blob becomes readable (opportunistic refresh, or
// pollBankSync's legacy lift), after which the next save is self-contained.
{
std::lock_guard<std::mutex> lock(refsMutex_);
sampleRefs_ = cs.sampleRefs;
}
// pS-usage: restore the persisted publish identity (v11; pre-v11 lifts to empty
// minted on first publish). usageNonce_ resets: a restored blob is a NEW LIFETIME
// for the copy-collision analysis (the fresh nonce means this incarnation can never
// be mistaken for the previous one's writes — or for a copy-sibling's).
// Restore the publish identity (pre-v11 lifts to empty, minted on first publish).
// usageNonce_ resets: a restored blob is a new lifetime, so this incarnation can never
// be mistaken for the previous one's writes or a copy-sibling's.
{
std::lock_guard<std::mutex> lock(usageMutex_);
instanceGuid_ = cs.instanceGuid;
usageNonce_.clear();
}
// A new blob is new facts: a staleness proof latched against the PREVIOUS state does
// not carry over (#A — the legacy lift gets one fresh run per restored state).
// A new blob is new facts — the legacy lift gets one fresh run per restored state.
legacyLiftConcluded_.store(false, std::memory_order_relaxed);
// Rebuild from the restored state (off-thread — setState is a load-time call).
reloadInstrument();
return kResultOk;
}
tresult PLUGIN_API ReaSamplerProcessor::getState(IBStream* state) {
if (!state) return kResultFalse;
// Persist the full instance state (v3, S10): the single-capture selection id AND the
// opt-in zones — the instrument's own state (D-B), NEVER written to the "reasampler"
// bank ext-state. An instance with no pick and no zones serializes to {"", no zones}
// and restores as the S10 empty state (silence + "pick a capture"), never auto-playing
// sample #1.
// Persists the full instance state — never written to the "reasampler" bank ext-state.
// No pick + no zones serializes to {"", no zones}, restoring as silence (never
// auto-playing sample #1).
ComponentState state_out;
state_out.selectionId = selectedSampleId();
state_out.map = performanceMap();
{
// S7: persist the per-instance mono/stereo decode mode + the GA explicit flag (v9).
std::lock_guard<std::mutex> lock(channelModeMutex_);
state_out.channelMode = channelMode_;
state_out.channelModeExplicit = channelModeExplicit_;
}
{
std::lock_guard<std::mutex> lock(assignMarkerMutex_);
state_out.lastConsumedAssignGeneration = lastConsumedAssignGeneration_; // S8 reader marker
state_out.lastConsumedAssignGeneration = lastConsumedAssignGeneration_;
}
state_out.previewVelocity = previewVelocity(); // S-VIEW-4: persist the preview strike velocity
state_out.previewVelocity = previewVelocity();
{
// Phase S: persist the voice-system parameters (component state v7).
std::lock_guard<std::mutex> lock(voiceParamsMutex_);
state_out.voiceCount = voiceCount_;
state_out.voiceMode = voiceMode_;
state_out.monoTrigger = monoTrigger_;
}
state_out.masterGainLinear = masterGainLinear(); // FB1: persist the post-mixer gain (v8)
// pS: persist the OWNED sample refs (v10) — the saved blob carries everything needed to
// decode + play with no extension present. Filtered (on the snapshot copy, the member is
// untouched) to exactly what the instance currently plays, so the table cannot grow with
// browsing history.
state_out.masterGainLinear = masterGainLinear();
// Persist the owned sample refs — the saved blob decodes + plays with no extension
// present. Filtered (snapshot copy only) to what the instance currently plays, so the
// table cannot grow with browsing history.
state_out.sampleRefs = sampleRefs();
retainRefs(state_out.sampleRefs,
referencedSampleIds(state_out.selectionId, state_out.map));
// pS-usage: persist the publish identity (v11) so the instance's usage key is
// stable across sessions (records do not proliferate per reopen).
// Persist the publish identity so the usage key is stable across sessions.
{
std::lock_guard<std::mutex> lock(usageMutex_);
state_out.instanceGuid = instanceGuid_;
@@ -202,8 +173,7 @@ std::uint8_t ReaSamplerProcessor::previewVelocity() {
}
void ReaSamplerProcessor::setPreviewVelocity(std::uint8_t velocity) {
// Clamp to the MIDI-note range [1,127] (0 would be a note-off by convention a preview
// strike must sound). The editor's knob maps its 0..1 domain into this range before calling.
// Clamp to [1,127] 0 would be a note-off by convention, and a preview strike must sound.
if (velocity < 1) velocity = 1;
if (velocity > 127) velocity = 127;
std::lock_guard<std::mutex> lock(previewMutex_);
@@ -216,8 +186,8 @@ int ReaSamplerProcessor::voiceCount() {
}
void ReaSamplerProcessor::setVoiceCount(int count) {
// Clamp to the shared pure-core range so the engine, the state bytes, and the editor's
// control can never disagree about the legal polyphony span.
// Clamp to the shared pure-core range so the engine, state bytes, and editor control
// can never disagree about the legal polyphony span.
if (count < kMinVoiceCount) count = kMinVoiceCount;
if (count > kMaxVoiceCount) count = kMaxVoiceCount;
{
@@ -225,11 +195,8 @@ void ReaSamplerProcessor::setVoiceCount(int count) {
if (voiceCount_ == count) return; // no-op: don't churn a rebuild
voiceCount_ = count;
}
// LIGHT rebuild OFF-thread through the drain-slot swap: the engine is reconstructed from
// the already-decoded keymap (no bridge re-read, no WAV re-decode — a polyphony change
// touches no audio data) and the displaced instrument keeps rendering its ringing tails,
// so a voice-param change never cuts a sounding note NOR stalls the UI re-decoding every
// zone from disk. Same contract for the mode/trigger setters below.
// Light rebuild through the drain-slot swap (no bridge re-read, no WAV re-decode) so a
// voice-param change never cuts a sounding tail. Same contract below.
rebuildVoiceEngine();
}
@@ -262,9 +229,8 @@ void ReaSamplerProcessor::setMonoTrigger(MonoTrigger trigger) {
}
void ReaSamplerProcessor::setMasterGainLinear(double linear) {
// Clamp to the control's legal span (the master_gain taper: 0 = -inf/silence, cap =
// +24 dB). One relaxed atomic store — the audio thread reads it at the next block start;
// no rebuild, no lock (a post-sum output trim is not a keymap fact).
// Clamp to the master_gain taper (0 = silence, cap = +24 dB). One relaxed atomic
// store — no rebuild, no lock (a post-sum trim is not a keymap fact).
if (!(linear >= 0.0)) linear = 0.0; // also catches NaN
const double maxLin = masterGainMaxLinear();
if (linear > maxLin) linear = maxLin;
@@ -275,9 +241,8 @@ void ReaSamplerProcessor::previewNoteOn(int note) {
if (note < 0) note = 0;
if (note > 127) note = 127;
const std::uint8_t vel = previewVelocity(); // latch the current knob value into the request
// Advance the sequence (wrapping; process compares for inequality, so a wrap is harmless as
// long as we never land back on the exact value the audio thread last consumed in one step —
// 16 bits gives 65535 posts between collisions, unreachable at UI-click rates).
// Advance the sequence (wrapping; process compares for inequality — 16 bits gives 65535
// posts between collisions, unreachable at UI-click rates).
const std::uint16_t seq = ++previewOnSeq_ == 0 ? ++previewOnSeq_ : previewOnSeq_;
const std::uint32_t packed = (static_cast<std::uint32_t>(seq) << 16) |
(static_cast<std::uint32_t>(vel) << 8) |
@@ -297,15 +262,14 @@ void ReaSamplerProcessor::previewNoteOff(int note) {
void ReaSamplerProcessor::setChannelMode(ChannelMode mode) {
{
std::lock_guard<std::mutex> lock(channelModeMutex_);
// The editor toggle is a DELIBERATE choice either way: latch explicit even on a
// same-mode click (the user confirmed the mode; the GA auto-default stops fighting it).
// A deliberate choice either way: latch explicit even on a same-mode click so
// auto-default stops fighting it.
channelModeExplicit_ = true;
if (channelMode_ == mode) return; // no decode change: don't churn a reload
channelMode_ = mode;
}
// The DECODE policy changed. The output bus is FIXED stereo (GA fix — no bus repoint, no
// restartComponent): reloading re-decodes the loaded WAV(s) under the new mode off-thread
// (mono = downmix, stereo = L/R split) and the RT path just keeps rendering.
// The output bus is fixed stereo (no bus repoint): reloading re-decodes off-thread
// under the new mode and the RT path just keeps rendering.
reloadInstrument();
}