Γ-W1-T2: the master bus — a true-peak limiter whose ceiling is a theorem, the meter's published half, and the plugin's first PDC report
This commit is contained in:
@@ -20,6 +20,7 @@
|
||||
#include "shell/instrument/reaper_bridge.h"
|
||||
#include "core/instrument/map/sample_map.h" // InstrumentParams (the one parameter set)
|
||||
#include "core/instrument/map/component_state_io.h" // ComponentState codec
|
||||
#include "core/instrument/engine/limiter.h" // the master bus's post-gain limiter
|
||||
#include "core/instrument/engine/live_params.h" // LiveParams (the live-parameter block)
|
||||
#include "core/instrument/engine/voice_engine.h"
|
||||
|
||||
@@ -33,6 +34,16 @@ using instrument::map::kPreviewVelocityDefault;
|
||||
|
||||
class ReaSamplerEmbed; // embedded TCP/MCP UI shell (owned below; see queryInterface)
|
||||
|
||||
// What the audio thread publishes about the OUTPUT BUS, post-limiter, once per block. Raw
|
||||
// magnitudes only — the UI converts to dB and runs the ballistics (engine/meter_ballistics),
|
||||
// because a hold timer or a log on the audio thread would be per-block work that buys nothing.
|
||||
struct MasterBusMeter {
|
||||
float peakL = 0.f; // max |x| this block
|
||||
float peakR = 0.f;
|
||||
float minGain = 1.f; // smallest limiter gain applied this block; 1 = no reduction
|
||||
bool clip = false; // LATCHED at a block peak >= 0 dBFS; only clearMasterBusClip lowers it
|
||||
};
|
||||
|
||||
// The decoded capture + the voice engine playing it. The engine holds a reference to the
|
||||
// sample, so both must live/die together at a stable address — heap-allocated,
|
||||
// non-copyable, non-movable. process() only ever reads this through an atomic pointer.
|
||||
@@ -92,6 +103,12 @@ public:
|
||||
Steinberg::tresult PLUGIN_API process(
|
||||
Steinberg::Vst::ProcessData& data) override;
|
||||
|
||||
// The plugin's PDC report: 0 with the limiter bypassed, the limiter's lookahead with it
|
||||
// engaged. Read from the PERSISTED enable, never from a transient — the SDK's contract
|
||||
// (pluginterfaces/vst/ivsteditcontroller.h, kLatencyChanged) is that the host asks this
|
||||
// AFTER the deactivate/reactivate it performs, and setActive(false) clears the engine.
|
||||
Steinberg::uint32 PLUGIN_API getLatencySamples() override;
|
||||
|
||||
// Fixed stereo output bus — channel mode is a decode policy, never a bus fact; mono
|
||||
// renders dual-mono through it. Do not reintroduce per-instance bus renegotiation.
|
||||
// Accepts only a single stereo output proposal; otherwise rejects and keeps stereo.
|
||||
@@ -108,12 +125,18 @@ public:
|
||||
Steinberg::tresult PLUGIN_API queryInterface(const Steinberg::TUID iid,
|
||||
void** obj) override;
|
||||
|
||||
// The embedded-strip activity level (0..1) for the embed shell, UI thread. Backed by
|
||||
// embedPeak_, a lock-free relaxed atomic the audio thread writes each block.
|
||||
// The embedded-strip activity level (0..1) for the embed shell, UI thread. The loudest of
|
||||
// the two published bus peaks — one publication serves the strip and the meter.
|
||||
double embedActivityLevel() const {
|
||||
return static_cast<double>(embedPeak_.load(std::memory_order_relaxed));
|
||||
const float l = meterPeakL_.load(std::memory_order_relaxed);
|
||||
const float r = meterPeakR_.load(std::memory_order_relaxed);
|
||||
return static_cast<double>(l > r ? l : r);
|
||||
}
|
||||
|
||||
// What the audio thread published about the output bus last block. UI thread.
|
||||
MasterBusMeter masterBusMeter() const;
|
||||
void clearMasterBusClip();
|
||||
|
||||
// Resolves the selection against the instance-owned SampleRefs, decodes its WAV
|
||||
// off-thread, and publishes the built instrument via atomic swap — no bank read
|
||||
// required. When the bank blob is readable it's first folded into the refs table
|
||||
@@ -208,6 +231,15 @@ public:
|
||||
}
|
||||
void setMasterGainLinear(double linear); // clamped to [0, masterGainMaxLinear()]
|
||||
|
||||
// The master-bus limiter's single enable (persisted in the parameter set). UI thread only:
|
||||
// the setter requests the host's kLatencyChanged restart, which the SDK requires be issued
|
||||
// from the UI thread and which process() must therefore never trigger. Setting the value it
|
||||
// already holds is a no-op, so repeated clicks on one segment cost no restart.
|
||||
bool limiterEnabled() const {
|
||||
return limiterEnabled_.load(std::memory_order_relaxed);
|
||||
}
|
||||
void setLimiterEnabled(bool on);
|
||||
|
||||
// Fires a one-shot preview note-on/off through the live VoiceEngine — the same
|
||||
// noteOn/noteOff host MIDI uses, so a preview is a real voice (counts against voice
|
||||
// count, can steal/be stolen, respects Poly/Mono + Retrigger/Legato). Off the audio
|
||||
@@ -248,6 +280,19 @@ private:
|
||||
// Requires reloadMutex_ held — shared by reloadInstrument and rebuildVoiceEngine.
|
||||
void publishBuiltLocked(std::unique_ptr<LoadedInstrument> built);
|
||||
|
||||
// Publishes a silent block to the meter. EVERY process() path that emits no audio calls
|
||||
// this, or the bar freezes at the last peak it saw. The clip latch is deliberately not
|
||||
// touched — it survives silence until the user clears it.
|
||||
void publishSilentMeterBlock() {
|
||||
meterPeakL_.store(0.f, std::memory_order_relaxed);
|
||||
meterPeakR_.store(0.f, std::memory_order_relaxed);
|
||||
meterMinGain_.store(1.f, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
// Mirrors the persisted limiter enable onto the audio thread and the latency reader. Called
|
||||
// from every writer of the parameter set, so the three views can never disagree.
|
||||
void publishLimiterEnabled(bool on);
|
||||
|
||||
// Publishes this instance's held captures to its per-instance ext-state key
|
||||
// ("rsusage_<instanceGuid>") so the extension's prune can never reclaim them. Called at
|
||||
// the tail of every reloadInstrument, off the audio thread. Mints instanceGuid_ on
|
||||
@@ -401,9 +446,19 @@ private:
|
||||
// unique_ptr, so its own refcount is a no-op.
|
||||
std::unique_ptr<ReaSamplerEmbed> embed_;
|
||||
|
||||
// Per-block mono peak the audio thread stores relaxed; embedActivityLevel() reads it
|
||||
// for the embed strip's level indicator. Advisory only.
|
||||
std::atomic<float> embedPeak_{0.f};
|
||||
// The master-bus limiter, applied post-gain over the summed output. Its own enable target
|
||||
// is the mirror of params_.limiterEnabled; limiterEnabled_ is the lock-free copy
|
||||
// getLatencySamples answers from.
|
||||
instrument::engine::Limiter limiter_;
|
||||
std::atomic<bool> limiterEnabled_{false};
|
||||
|
||||
// What the audio thread publishes about the output bus each block, relaxed — peaks, the
|
||||
// latched clip, and the limiter's smallest gain. No dB, no ballistics, no hold timer here;
|
||||
// the UI runs those off these values and its own elapsed time.
|
||||
std::atomic<float> meterPeakL_{0.f};
|
||||
std::atomic<float> meterPeakR_{0.f};
|
||||
std::atomic<float> meterMinGain_{1.f};
|
||||
std::atomic<bool> meterClip_{false};
|
||||
};
|
||||
|
||||
} // namespace reasampler::vst
|
||||
|
||||
Reference in New Issue
Block a user