audio: replaceIR updates slot name/path so getChainState reflects the swap

replaceProcessor deliberately preserves the target slot's name/path during
the prepare/fault window (so a fault in prepareToPlay is blocklisted against
the right plugin path). It kept them even on success, so after a successful
replaceIR the slot's audio was the new IR but getChainState()/preset-save
still reported the OLD IR name+path — a footgun for any consumer that
persists a chain read back from getChainState().

Add optional newName/newPath to replaceProcessor, applied under the swap lock
ONLY on success (empty = keep, so the sandbox-promotion caller is unchanged).
ReplaceIRWorker passes "IR: <name>" + the new path, mirroring LoadIRWorker.

Built (npm run build:audio) clean.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
byrongamatos
2026-07-08 22:07:21 +02:00
co-authored by Claude Opus 4.8
parent ce15581ca8
commit a63ef18756
3 changed files with 17 additions and 3 deletions
+4 -1
View File
@@ -2499,7 +2499,10 @@ public:
processor->prepareToPlay(sr, bs);
if (! processor->loadIR(juce::File(juce::String(irPath_)))) { ok_ = false; return; }
ok_ = liveEngine->getSignalChain().replaceProcessor(slotId_, std::move(processor));
auto name = processor->getIRName();
ok_ = liveEngine->getSignalChain().replaceProcessor(
slotId_, std::move(processor),
"IR: " + name, juce::String(irPath_));
if (ok_ && gain_ >= 0.0f)
liveEngine->getSignalChain().setPostGain(slotId_, gain_);
}
+7 -1
View File
@@ -432,7 +432,8 @@ void SignalChain::removeProcessor(int slotId)
if (idx >= 0) slots.remove(idx);
}
bool SignalChain::replaceProcessor(int slotId, std::unique_ptr<juce::AudioProcessor> processor)
bool SignalChain::replaceProcessor(int slotId, std::unique_ptr<juce::AudioProcessor> processor,
const juce::String& newName, const juce::String& newPath)
{
if (!processor) return false;
@@ -469,6 +470,11 @@ bool SignalChain::replaceProcessor(int slotId, std::unique_ptr<juce::AudioProces
auto* slot = slots[idx];
old = std::move(slot->processor);
slot->processor = std::move(staging.processor);
// Swap succeeded: adopt the new identity so getChainState()/preset save
// report the swapped-in processor, not the one it replaced. Only when
// provided — the sandbox-promotion caller passes none and keeps identity.
if (newName.isNotEmpty()) slot->name = newName;
if (newPath.isNotEmpty()) slot->path = newPath;
}
// Tear the old processor down OUTSIDE the audio lock: releaseResources() (and
// a VST3 destructor) can block, and must never stall process() on it.
+6 -1
View File
@@ -63,7 +63,12 @@ public:
// swaps under the audio lock; the old processor is torn down off the lock.
// Returns false if the slot is gone or the incoming processor faulted in
// prepareToPlay (in which case the existing processor is left untouched).
bool replaceProcessor(int slotId, std::unique_ptr<juce::AudioProcessor> processor);
// The OLD slot name/path are preserved during the prepare/fault window (so a
// fault is blocklisted against the right path); on SUCCESS, if newName/newPath
// are non-empty they replace the slot's identity so getChainState()/preset
// metadata reflect the swapped-in processor (used by replaceIR for cab swaps).
bool replaceProcessor(int slotId, std::unique_ptr<juce::AudioProcessor> processor,
const juce::String& newName = {}, const juce::String& newPath = {});
// Snapshot a slot's state for sandbox promotion, SAFELY. Runs hasEditor()
// and getStateInformation() under the audio lock (so they can't race
// process()'s processBlock on the same instance) and under the SEH/signal