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

RT-safe hot-swap slot for a Processor. More...

#include <pulp/audio/buffer.hpp>
#include <pulp/format/processor.hpp>
#include <pulp/format/state_restore_gate.hpp>
#include <pulp/midi/buffer.hpp>
#include <algorithm>
#include <pulp/signal/transition_mixer.hpp>
#include <atomic>
#include <cstddef>
#include <cstdint>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <vector>

Go to the source code of this file.

Classes

class  pulp::format::reload::ProcessorHotSwapSlot

Namespaces

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

Typedefs

using pulp::format::reload::CrossfadeCurve = signal::TransitionCurve
 The crossfade amplitude law is the shared signal::TransitionMixer primitive — extracted so the hot-swap slot, the convolver IR swapper, and the Phase-2 SwapUnit transitions all fade identically and are covered by one click-free fixture.

Detailed Description

RT-safe hot-swap slot for a Processor.

The lifetime invariant: a Processor instance is destroyed only on the control thread, and only after that thread has proven no audio callback is inside the old instance's process() body. A try_lock around entry is not enough — a reader already inside process() is invisible to the swapper.

Mechanism (the simplest correct one; RCU/epoch is a later optimization):

  • Audio thread takes a NON-BLOCKING shared lock for the WHOLE process() body. On contention (a swap is installing) it emits one passthrough block and returns — it never blocks, never allocates, never destroys.
  • Control thread takes the unique (writer) lock to install the new instance. Acquiring it proves no shared reader is inside process(). The displaced old instance is returned to the caller and destroyed on the control thread after the lock is released — safe, because no audio reader can still hold it.

A naive atomic<shared_ptr<Processor>> is intentionally NOT used: the last reference can drop on the audio thread, running ~Processor() in the callback (an RT violation).

── Click-free crossfade (opt-in) ─────────────────────────────────────────── An instantaneous swap cuts from the old DSP's output to the new one's at a block boundary — if they disagree there (different gain, phase, waveform) the discontinuity clicks. With a crossfade configured (set_crossfade_samples() + prepare_crossfade() to allocate the parallel-render scratch off the audio thread), a swap instead keeps the OLD processor and runs it in parallel with the new for a short window, mixing old→new along a smoothstep ramp (zero slope at both ends, so neither the swap instant nor the fade end clicks). The faded-out processor is then retired the RT-safe way: the audio thread only marks the fade done; the control thread frees it in reclaim() (or the next swap()/the destructor), never on the audio thread. With no crossfade configured the swap is instantaneous and returns the displaced instance, as before.