mirror of
https://github.com/got-feedBack/feedBack-desktop.git
synced 2026-09-11 03:14:09 +00:00
audio: per-slot postGain for parallel-branch loudness leveling (#58)
Adds an optional per-slot output gain (ProcessorSlot.postGain, default 1.0 = no-op) applied in SignalChain::runSlot after processBlock + pan, plumbed through setPostGain / IPC / preload / loadPreset / getChainState (mirroring setPan). Gives each parallel branch its own loudness trim. Review hardening (multi-angle + Codex): savePreset() now serializes postGain (it was read back but never written → save/load dropped it); setPostGain() rejects non-finite input (NaN would poison the buffer); new signalchain_postgain_test.cpp asserts gain scaling, NaN rejection, and serialization. Verified locally: build:audio clean; signalchain_postgain_test 5/5 pass. (Org CI runners failed to start — infra/billing, unrelated to the change; changes are platform-neutral C++.) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
27e8f56ad8
commit
c8415113c8
@@ -2539,6 +2539,18 @@ static Napi::Value SetPan(const Napi::CallbackInfo& info)
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
static Napi::Value SetPostGain(const Napi::CallbackInfo& info)
|
||||
{
|
||||
auto liveEngine = snapshotEngine();
|
||||
if (liveEngine && info.Length() >= 2)
|
||||
{
|
||||
int slotId = info[0].As<Napi::Number>().Int32Value();
|
||||
float gain = (float) info[1].As<Napi::Number>().DoubleValue();
|
||||
liveEngine->getSignalChain().setPostGain(slotId, gain);
|
||||
}
|
||||
return info.Env().Undefined();
|
||||
}
|
||||
|
||||
static Napi::Value SetBranch(const Napi::CallbackInfo& info)
|
||||
{
|
||||
auto liveEngine = snapshotEngine();
|
||||
@@ -2586,6 +2598,7 @@ static Napi::Value GetChainState(const Napi::CallbackInfo& info)
|
||||
obj.Set("pan", slots[i]->pan);
|
||||
obj.Set("branch", slots[i]->branch);
|
||||
obj.Set("branchSrc", slots[i]->branchSrc);
|
||||
obj.Set("postGain", slots[i]->postGain);
|
||||
obj.Set("hasEditor", slots[i]->processor && slots[i]->processor->hasEditor());
|
||||
result.Set((uint32_t)i, obj);
|
||||
}
|
||||
@@ -3221,6 +3234,8 @@ public:
|
||||
liveEngine->getSignalChain().setPan(slotId, (float)(double)slotObj->getProperty("pan"));
|
||||
if (slotObj->hasProperty("branch"))
|
||||
liveEngine->getSignalChain().setBranch(slotId, (int)slotObj->getProperty("branch"));
|
||||
if (slotObj->hasProperty("postGain"))
|
||||
liveEngine->getSignalChain().setPostGain(slotId, (float)(double)slotObj->getProperty("postGain"));
|
||||
if (slotObj->hasProperty("branchSrc"))
|
||||
liveEngine->getSignalChain().setBranchSrc(slotId, (int)slotObj->getProperty("branchSrc"));
|
||||
}
|
||||
@@ -3486,6 +3501,7 @@ static Napi::Object InitModule(Napi::Env env, Napi::Object exports)
|
||||
exports.Set("setBypass", Napi::Function::New(env, SetBypass));
|
||||
exports.Set("setPan", Napi::Function::New(env, SetPan));
|
||||
exports.Set("setBranch", Napi::Function::New(env, SetBranch));
|
||||
exports.Set("setPostGain", Napi::Function::New(env, SetPostGain));
|
||||
exports.Set("setBranchSrc", Napi::Function::New(env, SetBranchSrc));
|
||||
exports.Set("clearChain", Napi::Function::New(env, ClearChain));
|
||||
exports.Set("getChainState", Napi::Function::New(env, GetChainState));
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "SignalChain.h"
|
||||
#include "Sandbox/SandboxedProcessor.h"
|
||||
#include <cmath> // std::isfinite (postGain sanitisation)
|
||||
|
||||
#if ! JUCE_WINDOWS
|
||||
#include <csetjmp>
|
||||
@@ -287,6 +288,8 @@ void SignalChain::process(juce::AudioBuffer<float>& buffer, juce::MidiBuffer& mi
|
||||
slotMidi.addEvent(drained[i].msg, 0);
|
||||
invokePlugin(*slot, [&](juce::AudioProcessor& p) { p.processBlock(buf, slotMidi); });
|
||||
applyPan(buf, numSamples, slot->pan);
|
||||
if (slot->postGain != 1.0f)
|
||||
buf.applyGain(0, numSamples, slot->postGain);
|
||||
};
|
||||
|
||||
// Fast path: no parallel branch → plain serial chain. Behaviour is unchanged
|
||||
@@ -548,6 +551,18 @@ void SignalChain::setPan(int slotId, float pan)
|
||||
if (idx >= 0) slots[idx]->pan = juce::jlimit(-1.0f, 1.0f, pan);
|
||||
}
|
||||
|
||||
void SignalChain::setPostGain(int slotId, float gain)
|
||||
{
|
||||
// Reject non-finite input: juce::jlimit passes NaN through unchanged (both
|
||||
// of its comparisons are false for NaN), and a NaN gain would then multiply
|
||||
// the slot buffer to NaN and poison the whole chain until the slot rebuilds.
|
||||
if (! std::isfinite(gain)) return;
|
||||
const juce::ScopedLock sl(lock);
|
||||
int idx = findSlotIndex(slotId);
|
||||
// Clamp to [0, 16] — a 0..+24 dB linear ceiling for the per-slot trim.
|
||||
if (idx >= 0) slots[idx]->postGain = juce::jlimit(0.0f, 16.0f, gain);
|
||||
}
|
||||
|
||||
void SignalChain::setBranch(int slotId, int branch)
|
||||
{
|
||||
const juce::ScopedLock sl(lock);
|
||||
@@ -674,6 +689,9 @@ juce::String SignalChain::savePreset() const
|
||||
if (slot->pan != 0.0f) slotObj->setProperty("pan", slot->pan);
|
||||
if (slot->branch != 0) slotObj->setProperty("branch", slot->branch);
|
||||
if (slot->branchSrc != 0) slotObj->setProperty("branchSrc", slot->branchSrc);
|
||||
// Per-slot output trim (loudness leveling). LoadPresetWorker reads this
|
||||
// back, so it must be written here or a save/load round-trip drops it.
|
||||
if (slot->postGain != 1.0f) slotObj->setProperty("postGain", slot->postGain);
|
||||
|
||||
// Save processor state as base64
|
||||
auto state = slot->getState();
|
||||
|
||||
@@ -30,6 +30,9 @@ struct ProcessorSlot
|
||||
float pan = 0.0f;
|
||||
int branch = 0;
|
||||
int branchSrc = 0;
|
||||
// Linear output gain applied right after the processor (and pan). Carries
|
||||
// the per-amp loudness trim / per-branch level; 1.0 (default) = no-op.
|
||||
float postGain = 1.0f;
|
||||
|
||||
// For VST plugins — their state as base64 for preset save/load
|
||||
juce::MemoryBlock getState() const;
|
||||
@@ -81,6 +84,7 @@ public:
|
||||
void setPan(int slotId, float pan);
|
||||
void setBranch(int slotId, int branch);
|
||||
void setBranchSrc(int slotId, int src);
|
||||
void setPostGain(int slotId, float gain);
|
||||
void clear();
|
||||
|
||||
// Info
|
||||
|
||||
Reference in New Issue
Block a user