Pulp 1.88.0
Cross-platform audio plugin framework
Loading...
Searching...
No Matches
state_restore_gate.hpp File Reference

Proving the audio thread is outside process() while plugin state is restored under it. More...

#include <pulp/audio/buffer.hpp>
#include <algorithm>
#include <cstdint>
#include <atomic>
#include <mutex>
#include <thread>

Go to the source code of this file.

Classes

class  pulp::format::StateRestoreGate
class  pulp::format::StateRestoreGate::RenderLock
 Audio-thread pin. More...
class  pulp::format::StateRestoreGate::RestoreLock
 Control-thread hold. More...

Namespaces

namespace  pulp
namespace  pulp::format
 What the product grants a device: the quota table indexed by capability tier and live thermal state.

Functions

template<typename Sample>
void pulp::format::silence_block (audio::BufferView< Sample > &out)
 Zero an output bus for a block the plug-in did not render.
template<typename Sample>
void pulp::format::passthrough_block (audio::BufferView< Sample > &out, const audio::BufferView< const Sample > &in)
 Copy input to output for a block the plug-in did not render.

Detailed Description

Proving the audio thread is outside process() while plugin state is restored under it.

Processor::deserialize_plugin_state() is documented as being "called on a host/main thread with the audio thread stopped". No format's host API actually guarantees that: VST3 setState, AU setFullState:, AU v2 kAudioUnitProperty_ClassInfo, and CLAP state.load may all arrive while the plug-in is active and rendering. Pulp's own host-quirk catalog records row R6 for a DAW that calls the VST3 entry point at any time.

Without a gate the restoring thread mutates processor-owned state while the audio thread reads it. StateStore parameters are atomic and survive that, but a plug-in's own deserialize_plugin_state() payload — sample buffers, filter coefficients, wavetables — has no such protection, and the contract above tells plug-in authors they may assume there is none needed.

Mechanism:

  • The audio thread registers itself as an in-flight render with one atomic increment around its call into Processor::process(). If a restore has already claimed the gate it backs out and the adapter emits silence (or a passthrough) for that block. It never blocks, never takes a mutex, never allocates.
  • The restoring thread raises the "restoring" flag on the same atomic and then waits for the in-flight render count to drain to zero: a short spin, then an atomic wait that a render finishing under the flag wakes. Both sides read-modify-write one atomic word, so every render either registered before the flag went up (and the restore waits for it) or sees the flag (and refuses). Once the count is zero, no audio reader is inside process() and the restore is safe.

The gate is deliberately separate from ProcessorHotSwapSlot: that type owns and swaps the Processor, whereas a format adapter only borrows one it neither owns nor replaces.