Widen the deck row block to 1028 so the filter tie-line is exact, and accumulate the meter's block peaks instead of sampling one in 47

This commit is contained in:
2026-08-02 08:27:05 -04:00
parent 0627398bbb
commit df10ddacc2
29 changed files with 552 additions and 250 deletions
+39 -15
View File
@@ -128,16 +128,17 @@ 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. The loudest of
// the two published bus peaks — one publication serves the strip and the meter.
// The embedded-strip activity level (0..1) for the embed shell, UI thread. The last block's
// loudest channel — a non-consuming read, so it stays correct however often the editor's
// meter drains its own accumulators (or never does, with no editor open).
double embedActivityLevel() const {
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);
return static_cast<double>(advisoryPeak_.load(std::memory_order_relaxed));
}
// What the audio thread published about the output bus last block. UI thread.
MasterBusMeter masterBusMeter() const;
// What the audio thread published since the LAST call: peaks maxed and minGain minimised
// across every block in that window. CONSUMING — it resets the accumulators as it reads, so
// exactly one reader may call it, and that reader is the editor's meter tick. UI thread.
MasterBusMeter masterBusMeter();
void clearMasterBusClip();
// Resolves the selection against the instance-owned SampleRefs, decodes its WAV
@@ -284,13 +285,28 @@ 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.
// Folds one block's reading into the accumulator it belongs to — a running max for a peak,
// a running min for the limiter's gain. Read-modify-write rather than a bare store, so the
// ~47 blocks that elapse between two 500 ms UI frames at 48 kHz/512 all reach the meter
// instead of the one it happened to sample. Relaxed throughout: the accumulators are
// advisory, and no other state is ordered against them. Block rate, never per frame.
static void foldPeak(std::atomic<float>& acc, float blockPeak) {
if (blockPeak > acc.load(std::memory_order_relaxed)) {
acc.store(blockPeak, std::memory_order_relaxed);
}
}
static void foldMinGain(std::atomic<float>& acc, float blockMinGain) {
if (blockMinGain < acc.load(std::memory_order_relaxed)) {
acc.store(blockMinGain, std::memory_order_relaxed);
}
}
// Publishes a silent block. EVERY process() path that emits no audio calls this. It clears
// only the ADVISORY level, which is a last-block reading: the meter accumulators need
// nothing here, because a block that emitted no audio contributes no peak and no gain
// reduction, and the UI's own read is what resets them.
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);
advisoryPeak_.store(0.f, std::memory_order_relaxed);
}
// Mirrors the persisted limiter enable onto the audio thread and the latency reader. Called
@@ -456,13 +472,21 @@ private:
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;
// What the audio thread publishes about the output bus each block, relaxed. The peaks and
// minGain ACCUMULATE (max / min) across every block since the UI last read, and
// masterBusMeter() resets them as it reads — the fix for a bar that displayed roughly one
// block in fifty. 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};
// The embed strip's activity level: the LAST block's loudest channel, plainly overwritten.
// Separate from the accumulators above because it answers a different question ("how loud
// is it now") and has a different reader — sharing one would make each reader's reset
// silently truncate the other's window.
std::atomic<float> advisoryPeak_{0.f};
};
} // namespace reasampler::vst