Decouple the instrument reload from VST3 activation, and make the master meter's accumulate exact
This commit is contained in:
@@ -153,10 +153,8 @@ InstrumentParams ReaSamplerProcessor::instrumentParams() {
|
||||
}
|
||||
|
||||
void ReaSamplerProcessor::setInstrumentParams(const InstrumentParams& params) {
|
||||
bool limiterFlagChanged = false;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(paramsMutex_);
|
||||
limiterFlagChanged = (params_.limiterEnabled != params.limiterEnabled);
|
||||
params_ = params;
|
||||
}
|
||||
// Every writer of the parameter set — setState, the editor's commits, the bake's adopt —
|
||||
@@ -167,11 +165,14 @@ void ReaSamplerProcessor::setInstrumentParams(const InstrumentParams& params) {
|
||||
// safe there (see this directory's CLAUDE.md).
|
||||
publishLimiterEnabled(params.limiterEnabled);
|
||||
// Armed AFTER the mirror, so getLatencySamples already answers the new value for the whole
|
||||
// window the arm stays outstanding. Sticky and idempotent: any number of changes before one
|
||||
// flush cost one restart, and the flush is the only thing that clears it.
|
||||
if (limiterFlagChanged) {
|
||||
latencyRestartPending_.store(true, std::memory_order_release);
|
||||
}
|
||||
// window the arm stays outstanding. Compared against the last ANNOUNCED enable rather than
|
||||
// against the previous parameter set: off->on->off inside one tick ends at the latency the
|
||||
// host already knows, and a restart rebuilds the instance, so announcing a latency that
|
||||
// never changed is pure cost. Any number of changes before one flush still cost at most one
|
||||
// restart, and this store is the only one that raises OR lowers the arm.
|
||||
latencyRestartPending_.store(
|
||||
params.limiterEnabled != latencyAnnounced_.load(std::memory_order_relaxed),
|
||||
std::memory_order_release);
|
||||
}
|
||||
|
||||
void ReaSamplerProcessor::flushLatencyRestart() {
|
||||
@@ -179,6 +180,11 @@ void ReaSamplerProcessor::flushLatencyRestart() {
|
||||
// its handler waits for a later flush instead of evaporating.
|
||||
if (!componentHandler) return;
|
||||
if (!latencyRestartPending_.exchange(false, std::memory_order_acquire)) return;
|
||||
// Latched BEFORE the call: a host that services the restart synchronously re-enters this
|
||||
// object inside it, so the next commit must compare against the value the host is about to
|
||||
// read, not against the one it held before.
|
||||
latencyAnnounced_.store(limiterEnabled_.load(std::memory_order_relaxed),
|
||||
std::memory_order_relaxed);
|
||||
// The SDK requires this on the UI thread and answers getLatencySamples only after the host's
|
||||
// own deactivate/reactivate — so the flag is long committed by the time the host asks. This
|
||||
// is a kLatencyChanged restart with the bus untouched, NOT the retired per-mode kIoChanged
|
||||
@@ -202,14 +208,14 @@ void ReaSamplerProcessor::setLimiterEnabled(bool on) {
|
||||
|
||||
MasterBusMeter ReaSamplerProcessor::masterBusMeter() {
|
||||
MasterBusMeter m;
|
||||
// Exchange, not load: the accumulators hold the window since this was last called, and
|
||||
// clearing them here is what starts the next window. The audio thread's own fold is a
|
||||
// load-max-store, so a store landing between this exchange and that store can retain one
|
||||
// window's peak for one extra frame — it can never LOSE one, which is the property that
|
||||
// matters for a peak meter.
|
||||
m.peakL = meterPeakL_.exchange(0.f, std::memory_order_relaxed);
|
||||
m.peakR = meterPeakR_.exchange(0.f, std::memory_order_relaxed);
|
||||
m.minGain = meterMinGain_.exchange(1.f, std::memory_order_relaxed);
|
||||
// Consuming: each read takes the window and reinstalls its identity element, which is what
|
||||
// starts the next one. The audio thread's fold is an unconditional CAS against exactly that
|
||||
// (meter_accumulate.h owns the argument), so a fold interleaved with these exchanges lands
|
||||
// in one window or the other and is never dropped between them.
|
||||
m.peakL = instrument::engine::consumePeak(meterPeakL_);
|
||||
m.peakR = instrument::engine::consumePeak(meterPeakR_);
|
||||
m.minGain = instrument::engine::consumeMinGain(meterMinGain_);
|
||||
// NOT consumed: the clip is a latch the user clears, not a window.
|
||||
m.clip = meterClip_.load(std::memory_order_relaxed);
|
||||
return m;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user