fix(vst): master-gain per-sample ramp (no zipper/click); voice-count commits once on release; minor review items
This commit is contained in:
@@ -43,6 +43,13 @@ namespace {
|
||||
// FIXED so raising the voice count never multiplies shifter CPU past the profiled budget.
|
||||
constexpr std::size_t kPreserveVoiceCap = 8;
|
||||
|
||||
// FB1 post-mixer gain ramp rate (per sample). gainCurrent_ converges to masterGain_ at this
|
||||
// linear step; it ramps from 0 to unity (or vice versa) in ~20 ms at 48 kHz. The early-out
|
||||
// (|current - target| < threshold) snaps to the target and avoids the ramp loop on idle blocks.
|
||||
// kGainRampSnap is the threshold below which we snap to the target (avoids long sub-LSB creep).
|
||||
constexpr float kGainRampRate = 1.0f / 960.0f; // 960 samples @ 48 kHz ≈ 20 ms
|
||||
constexpr float kGainRampSnap = kGainRampRate * 0.5f;
|
||||
|
||||
// Read a whole file into a byte buffer. Off-thread only (blocking file I/O). Empty on
|
||||
// any failure — the caller treats an unreadable WAV as "nothing to play".
|
||||
std::vector<std::uint8_t> readFileBytes(const std::string& path) {
|
||||
@@ -893,14 +900,28 @@ tresult PLUGIN_API ReaSamplerProcessor::process(ProcessData& data) {
|
||||
drain->engine.render(ch0, ch1, static_cast<std::size_t>(frames));
|
||||
drain->preview.render(ch0, ch1, static_cast<std::size_t>(frames));
|
||||
}
|
||||
// FB1 post-mixer master gain: ONE relaxed load per block, applied AFTER the voice sum
|
||||
// (engine + drain + preview) and BEFORE the extra-channel mirror + peak, so the mirror
|
||||
// and the level indicator both see the actual output. A cheap multiply — no per-voice
|
||||
// cost, no alloc, no lock (RT discipline).
|
||||
// FB1 post-mixer master gain: ramp gainCurrent_ toward the atomic target per-sample so
|
||||
// continuous knob drags produce no zipper noise and the true-zero bottom causes no click.
|
||||
// Applied AFTER the voice sum and BEFORE the extra-channel mirror + peak so both see the
|
||||
// actual output. Branch-free inner loop; early-out when already at target. RT-safe.
|
||||
{
|
||||
const float g = masterGain_.load(std::memory_order_relaxed);
|
||||
if (g != 1.f) {
|
||||
for (int32 i = 0; i < frames; ++i) { ch0[i] *= g; ch1[i] *= g; }
|
||||
const float gTarget = masterGain_.load(std::memory_order_relaxed);
|
||||
const float diff = gTarget - gainCurrent_;
|
||||
if (diff < -kGainRampSnap || diff > kGainRampSnap) {
|
||||
// Ramp toward target: step per sample, then apply the per-sample gain.
|
||||
for (int32 i = 0; i < frames; ++i) {
|
||||
const float d = gTarget - gainCurrent_;
|
||||
if (d > kGainRampRate) gainCurrent_ += kGainRampRate;
|
||||
else if (d < -kGainRampRate) gainCurrent_ -= kGainRampRate;
|
||||
else gainCurrent_ = gTarget;
|
||||
ch0[i] *= gainCurrent_;
|
||||
ch1[i] *= gainCurrent_;
|
||||
}
|
||||
} else {
|
||||
gainCurrent_ = gTarget;
|
||||
if (gTarget != 1.f) {
|
||||
for (int32 i = 0; i < frames; ++i) { ch0[i] *= gTarget; ch1[i] *= gTarget; }
|
||||
}
|
||||
}
|
||||
}
|
||||
// Any channels beyond the first two mirror ch0 (defensive — REAPER negotiates 1 or 2).
|
||||
@@ -930,12 +951,24 @@ tresult PLUGIN_API ReaSamplerProcessor::process(ProcessData& data) {
|
||||
drain->engine.render(ch0, static_cast<std::size_t>(frames));
|
||||
drain->preview.render(ch0, static_cast<std::size_t>(frames));
|
||||
}
|
||||
// FB1 post-mixer master gain (mono path) — same contract as the stereo branch above:
|
||||
// post-sum, pre-peak/replicate, one relaxed load, RT-safe.
|
||||
// FB1 post-mixer master gain (mono path) — same ramp contract as the stereo branch:
|
||||
// post-sum, pre-peak/replicate, per-sample gainCurrent_ ramp toward target, RT-safe.
|
||||
{
|
||||
const float g = masterGain_.load(std::memory_order_relaxed);
|
||||
if (g != 1.f) {
|
||||
for (int32 i = 0; i < frames; ++i) ch0[i] *= g;
|
||||
const float gTarget = masterGain_.load(std::memory_order_relaxed);
|
||||
const float diff = gTarget - gainCurrent_;
|
||||
if (diff < -kGainRampSnap || diff > kGainRampSnap) {
|
||||
for (int32 i = 0; i < frames; ++i) {
|
||||
const float d = gTarget - gainCurrent_;
|
||||
if (d > kGainRampRate) gainCurrent_ += kGainRampRate;
|
||||
else if (d < -kGainRampRate) gainCurrent_ -= kGainRampRate;
|
||||
else gainCurrent_ = gTarget;
|
||||
ch0[i] *= gainCurrent_;
|
||||
}
|
||||
} else {
|
||||
gainCurrent_ = gTarget;
|
||||
if (gTarget != 1.f) {
|
||||
for (int32 i = 0; i < frames; ++i) ch0[i] *= gTarget;
|
||||
}
|
||||
}
|
||||
}
|
||||
float peak = 0.f;
|
||||
|
||||
Reference in New Issue
Block a user