@Christoph-Hart here you go (all the fixes my pet and I tried until today haven't worked so my hacky solution... Using a processingSpecs BC works too, because the root problem as I understand it is the samplerate not being set before the module are constructed/connected)
HISE bug: extra_mod runtime targets broken in exported plugins
Setup
NUM_HARDCODED_FX_MODS > 0, hardcoded FX modules with networks containing core::extra_mod nodes driven by Matrix Modulators in the FX's modulator chains.
Works in HISE backend, broken in exported plugin (compiled DLL and interpreted both).
Symptoms
Network parameters don't respond to UI changes or preset recalls.
Matrix modulator's Value updates correctly but its output never reaches the extra_mod node.
Root cause
Order-of-operations bug between project-state restore and prepareToPlay:
-
Host loads project state → restoreHardcodedData → setEffect(name) → connectToRuntimeTargets(*newNode, true).
-
ExtraModulatorRuntimeTargetSource::addConnection (ModulatorChain.cpp:2155) builds a SignalSource with sampleRate_cr / numSamples_cr from getSampleRate() — which is 0 because the host hasn't called prepareToPlay yet.
-
target->onValue(signal) stores that zero-rate signal in every extra_mod node.
-
When prepareToPlay finally runs, extra_mod::prepare() reads the stored signal, checkSignalRatio() sees rate 0, and the node ends up in a non-functional state — modulation values never reach the parameters.
In the HISE backend the engine is already prepared when networks are loaded, so step 2 captures a valid rate.
The early-return in HardcodedSwappableEffect::setEffect (if (factoryId == currentEffect) return true;) makes the broken signal sticky — no later preset load triggers a reconnect.
Confirmed workaround
After init delay (so prepareToPlay has run), force-reload each FX:
fxSlot.setEffect("");
fxSlot.setEffect(originalDLLName);
This forces a full disconnect + recreate with a valid sample rate.
Related
Same class of bug as the recent GlobalModulator::prepareToPlay reorder fix ("Fix SmoothedValue assertion when sample rate is uninitialised"), but on a different code path (setEffect → connectToRuntimeTargets, not prepareToPlay).
Suggested fix
In HardcodedSwappableEffect::setEffect, skip connectToRuntimeTargets(*newNode, true) when getSampleRate() <= 0, and call it instead from prepareToPlay once the rate is valid. The scriptnode FX (JavascriptMasterEffect / JavascriptPolyphonicEffect) and HardcodedSynthesiser variants need the same treatment.