Cut shell/instrument comment bloat ~34% (comments only, zero code change)
This commit is contained in:
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user